* UI revamp for companion radios

This commit is contained in:
Scott Powell
2025-08-08 20:01:31 +10:00
parent a310a5c4d5
commit 4b95c981bb
26 changed files with 840 additions and 323 deletions

View File

@@ -267,6 +267,7 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
} }
memcpy(p->pubkey_prefix, contact.id.pub_key, sizeof(p->pubkey_prefix)); memcpy(p->pubkey_prefix, contact.id.pub_key, sizeof(p->pubkey_prefix));
strcpy(p->name, contact.name);
p->recv_timestamp = getRTCClock()->getCurrentTime(); p->recv_timestamp = getRTCClock()->getCurrentTime();
p->path_len = path_len; p->path_len = path_len;
memcpy(p->path, path, p->path_len); memcpy(p->path, path, p->path_len);
@@ -275,6 +276,20 @@ void MyMesh::onDiscoveredContact(ContactInfo &contact, bool is_new, uint8_t path
dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY); dirty_contacts_expiry = futureMillis(LAZY_CONTACTS_WRITE_DELAY);
} }
static int sort_by_recent(const void *a, const void *b) {
return ((AdvertPath *) b)->recv_timestamp - ((AdvertPath *) a)->recv_timestamp;
}
int MyMesh::getRecentlyHeard(AdvertPath dest[], int max_num) {
if (max_num > ADVERT_PATH_TABLE_SIZE) max_num = ADVERT_PATH_TABLE_SIZE;
qsort(advert_paths, ADVERT_PATH_TABLE_SIZE, sizeof(advert_paths[0]), sort_by_recent);
for (int i = 0; i < max_num; i++) {
dest[i] = advert_paths[i];
}
return max_num;
}
void MyMesh::onContactPathUpdated(const ContactInfo &contact) { void MyMesh::onContactPathUpdated(const ContactInfo &contact) {
out_frame[0] = PUSH_CODE_PATH_UPDATED; out_frame[0] = PUSH_CODE_PATH_UPDATED;
memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE); memcpy(&out_frame[1], contact.id.pub_key, PUB_KEY_SIZE);

View File

@@ -77,6 +77,14 @@
#define REQ_TYPE_KEEP_ALIVE 0x02 #define REQ_TYPE_KEEP_ALIVE 0x02
#define REQ_TYPE_GET_TELEMETRY_DATA 0x03 #define REQ_TYPE_GET_TELEMETRY_DATA 0x03
struct AdvertPath {
uint8_t pubkey_prefix[7];
uint8_t path_len;
char name[32];
uint32_t recv_timestamp;
uint8_t path[MAX_PATH_SIZE];
};
class MyMesh : public BaseChatMesh, public DataStoreHost { class MyMesh : public BaseChatMesh, public DataStoreHost {
public: public:
MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store); MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store);
@@ -93,6 +101,8 @@ public:
bool advert(); bool advert();
void enterCLIRescue(); void enterCLIRescue();
int getRecentlyHeard(AdvertPath dest[], int max_num);
protected: protected:
float getAirtimeBudgetFactor() const override; float getAirtimeBudgetFactor() const override;
int getInterferenceThreshold() const override; int getInterferenceThreshold() const override;
@@ -201,12 +211,6 @@ private:
AckTableEntry expected_ack_table[EXPECTED_ACK_TABLE_SIZE]; // circular table AckTableEntry expected_ack_table[EXPECTED_ACK_TABLE_SIZE]; // circular table
int next_ack_idx; int next_ack_idx;
struct AdvertPath {
uint8_t pubkey_prefix[7];
uint8_t path_len;
uint32_t recv_timestamp;
uint8_t path[MAX_PATH_SIZE];
};
#define ADVERT_PATH_TABLE_SIZE 16 #define ADVERT_PATH_TABLE_SIZE 16
AdvertPath advert_paths[ADVERT_PATH_TABLE_SIZE]; // circular table AdvertPath advert_paths[ADVERT_PATH_TABLE_SIZE]; // circular table
}; };

View File

@@ -1,8 +1,8 @@
#include "UITask.h" #include "UITask.h"
#include <Arduino.h>
#include <helpers/TxtDataHelpers.h> #include <helpers/TxtDataHelpers.h>
#include "NodePrefs.h" #include "NodePrefs.h"
#include "MyMesh.h" #include "MyMesh.h"
#include "target.h"
#define AUTO_OFF_MILLIS 15000 // 15 seconds #define AUTO_OFF_MILLIS 15000 // 15 seconds
#define BOOT_SCREEN_MILLIS 3000 // 3 seconds #define BOOT_SCREEN_MILLIS 3000 // 3 seconds
@@ -13,80 +13,366 @@
#define LED_CYCLE_MILLIS 4000 #define LED_CYCLE_MILLIS 4000
#endif #endif
#ifndef USER_BTN_PRESSED #define LONG_PRESS_MILLIS 1200
#define USER_BTN_PRESSED LOW
#endif
// 'meshcore', 128x13px #define PRESS_LABEL "long press"
static const uint8_t meshcore_logo [] PROGMEM = {
0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, #include "icons.h"
0x3c, 0x03, 0xe3, 0xff, 0xc7, 0xff, 0x8e, 0x03, 0x8f, 0xfe, 0x3f, 0xfe, 0x1f, 0xff, 0x1f, 0xfe,
0x3e, 0x03, 0xc3, 0xff, 0x8f, 0xff, 0x0e, 0x07, 0x8f, 0xfe, 0x7f, 0xfe, 0x1f, 0xff, 0x1f, 0xfc, class SplashScreen : public UIScreen {
0x3e, 0x07, 0xc7, 0x80, 0x0e, 0x00, 0x0e, 0x07, 0x9e, 0x00, 0x78, 0x0e, 0x3c, 0x0f, 0x1c, 0x00, UITask* _task;
0x3e, 0x0f, 0xc7, 0x80, 0x1e, 0x00, 0x0e, 0x07, 0x1e, 0x00, 0x70, 0x0e, 0x38, 0x0f, 0x3c, 0x00, unsigned long dismiss_after;
0x7f, 0x0f, 0xc7, 0xfe, 0x1f, 0xfc, 0x1f, 0xff, 0x1c, 0x00, 0x70, 0x0e, 0x38, 0x0e, 0x3f, 0xf8, char _version_info[12];
0x7f, 0x1f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x0e, 0x38, 0x0e, 0x3f, 0xf8,
0x7f, 0x3f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x1e, 0x3f, 0xfe, 0x3f, 0xf0, public:
0x77, 0x3b, 0x87, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xfc, 0x38, 0x00, SplashScreen(UITask* task) : _task(task) {
0x77, 0xfb, 0x8f, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xf8, 0x38, 0x00, // strip off dash and commit hash by changing dash to null terminator
0x73, 0xf3, 0x8f, 0xff, 0x0f, 0xff, 0x1c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x78, 0x7f, 0xf8, // e.g: v1.2.3-abcdef -> v1.2.3
0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfe, 0x3c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x3c, 0x7f, 0xf8, const char *ver = FIRMWARE_VERSION;
0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, const char *dash = strchr(ver, '-');
int len = dash ? dash - ver : strlen(ver);
if (len >= sizeof(_version_info)) len = sizeof(_version_info) - 1;
memcpy(_version_info, ver, len);
_version_info[len] = 0;
dismiss_after = millis() + BOOT_SCREEN_MILLIS;
}
int render(DisplayDriver& display) override {
// meshcore logo
display.setColor(DisplayDriver::BLUE);
int logoWidth = 128;
display.drawXbm((display.width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
// version info
display.setColor(DisplayDriver::LIGHT);
display.setTextSize(2);
display.drawTextCentered(display.width()/2, 22, _version_info);
display.setTextSize(1);
display.drawTextCentered(display.width()/2, 42, FIRMWARE_BUILD_DATE);
return 1000;
}
void poll() override {
if (millis() >= dismiss_after) {
_task->gotoHomeScreen();
}
}
};
class HomeScreen : public UIScreen {
enum HomePage {
FIRST,
RECENT,
RADIO,
BLUETOOTH,
ADVERT,
SHUTDOWN,
Count // keep as last
};
UITask* _task;
mesh::RTCClock* _rtc;
SensorManager* _sensors;
NodePrefs* _node_prefs;
uint8_t _page;
bool _shutdown_init;
AdvertPath recent[4];
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 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);
// battery outline
display.drawRect(iconX, iconY, iconWidth, iconHeight);
// battery "cap"
display.fillRect(iconX + iconWidth, iconY + (iconHeight / 4), 3, iconHeight / 2);
// fill the battery based on the percentage
int fillWidth = (batteryPercentage * (iconWidth - 4)) / 100;
display.fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4);
}
public:
HomeScreen(UITask* task, mesh::RTCClock* rtc, SensorManager* sensors, NodePrefs* node_prefs)
: _task(task), _rtc(rtc), _sensors(sensors), _node_prefs(node_prefs), _page(0), _shutdown_init(false) { }
void poll() override {
if (_shutdown_init && !_task->isButtonPressed()) { // must wait for USR button to be released
_task->shutdown();
}
}
int render(DisplayDriver& display) override {
char tmp[80];
// node name
display.setCursor(0, 0);
display.setTextSize(1);
display.setColor(DisplayDriver::GREEN);
display.print(_node_prefs->node_name);
// battery voltage
renderBatteryIndicator(display, _task->getBattMilliVolts());
// curr page indicator
int y = 14;
int x = display.width() / 2 - 25;
for (uint8_t i = 0; i < HomePage::Count; i++, x += 10) {
if (i == _page) {
display.fillRect(x-1, y-1, 3, 3);
} else {
display.fillRect(x, y, 1, 1);
}
}
if (_page == HomePage::FIRST) {
display.setColor(DisplayDriver::YELLOW);
display.setTextSize(2);
sprintf(tmp, "MSG: %d", _task->getMsgCount());
display.drawTextCentered(display.width() / 2, 20, tmp);
if (_task->hasConnection()) {
display.setColor(DisplayDriver::GREEN);
display.setTextSize(1);
display.drawTextCentered(display.width() / 2, 43, "< Connected >");
} else if (the_mesh.getBLEPin() != 0) { // BT pin
display.setColor(DisplayDriver::RED);
display.setTextSize(2);
sprintf(tmp, "Pin:%d", the_mesh.getBLEPin());
display.drawTextCentered(display.width() / 2, 43, tmp);
}
} else if (_page == HomePage::RECENT) {
the_mesh.getRecentlyHeard(recent, 4);
display.setColor(DisplayDriver::GREEN);
int y = 20;
for (int i = 0; i < 4; i++, y += 11) {
auto a = &recent[i];
if (a->name[0] == 0) continue; // empty slot
display.setCursor(0, y);
display.print(a->name);
int secs = _rtc->getCurrentTime() - a->recv_timestamp;
if (secs < 60) {
sprintf(tmp, "%ds", secs);
} else if (secs < 60*60) {
sprintf(tmp, "%dm", secs / 60);
} else {
sprintf(tmp, "%dh", secs / (60*60));
}
display.setCursor(display.width() - display.getTextWidth(tmp) - 1, y);
display.print(tmp);
}
} else if (_page == HomePage::RADIO) {
display.setColor(DisplayDriver::YELLOW);
display.setTextSize(1);
// freq / sf
display.setCursor(0, 20);
sprintf(tmp, "FQ: %06.3f SF: %d", _node_prefs->freq, _node_prefs->sf);
display.print(tmp);
display.setCursor(0, 31);
sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
display.print(tmp);
// tx power, noise floor
display.setCursor(0, 42);
sprintf(tmp, "TX: %ddBm", _node_prefs->tx_power_dbm);
display.print(tmp);
display.setCursor(0, 53);
sprintf(tmp, "Noise floor: %d", radio_driver.getNoiseFloor());
display.print(tmp);
} else if (_page == HomePage::BLUETOOTH) {
display.setColor(DisplayDriver::GREEN);
display.drawXbm((display.width() - 32) / 2, 18,
_task->isSerialEnabled() ? bluetooth_on : bluetooth_off,
32, 32);
display.setTextSize(1);
display.drawTextCentered(display.width() / 2, 64 - 11, "toggle: " PRESS_LABEL);
} else if (_page == HomePage::ADVERT) {
display.setColor(DisplayDriver::GREEN);
display.drawXbm((display.width() - 32) / 2, 18, advert_icon, 32, 32);
display.drawTextCentered(display.width() / 2, 64 - 11, "advert: " PRESS_LABEL);
} else if (_page == HomePage::SHUTDOWN) {
display.setColor(DisplayDriver::GREEN);
display.setTextSize(1);
if (_shutdown_init) {
display.drawTextCentered(display.width() / 2, 34, "shutting down...");
} else {
display.drawXbm((display.width() - 32) / 2, 18, power_icon, 32, 32);
display.drawTextCentered(display.width() / 2, 64 - 11, "off: " PRESS_LABEL);
}
}
return 5000; // next render after 5000 ms
}
bool handleInput(char c) override {
if (c == KEY_LEFT) {
_page = (_page + HomePage::Count - 1) % HomePage::Count;
return true;
}
if (c == KEY_RIGHT || c == KEY_SELECT) {
_page = (_page + 1) % HomePage::Count;
if (_page == HomePage::RECENT) {
_task->showAlert("Recent adverts", 800);
}
return true;
}
if (c == KEY_ENTER && _page == HomePage::BLUETOOTH) {
if (_task->isSerialEnabled()) { // toggle Bluetooth on/off
_task->disableSerial();
} else {
_task->enableSerial();
}
return true;
}
if (c == KEY_ENTER && _page == HomePage::ADVERT) {
#ifdef PIN_BUZZER
_task->soundBuzzer(UIEventType::ack);
#endif
if (the_mesh.advert()) {
_task->showAlert("Advert sent!", 1000);
} else {
_task->showAlert("Advert failed..", 1000);
}
return true;
}
if (c == KEY_ENTER && _page == HomePage::SHUTDOWN) {
_shutdown_init = true; // need to wait for button to be released
return true;
}
return false;
}
};
class MsgPreviewScreen : public UIScreen {
UITask* _task;
mesh::RTCClock* _rtc;
struct MsgEntry {
uint32_t timestamp;
char origin[62];
char msg[78];
};
#define MAX_UNREAD_MSGS 32
int num_unread;
MsgEntry unread[MAX_UNREAD_MSGS];
public:
MsgPreviewScreen(UITask* task, mesh::RTCClock* rtc) : _task(task), _rtc(rtc) { num_unread = 0; }
void addPreview(uint8_t path_len, const char* from_name, const char* msg) {
if (num_unread >= MAX_UNREAD_MSGS) return; // full
auto p = &unread[num_unread++];
p->timestamp = _rtc->getCurrentTime();
if (path_len == 0xFF) {
sprintf(p->origin, "(D) %s:", from_name);
} else {
sprintf(p->origin, "(%d) %s:", (uint32_t) path_len, from_name);
}
StrHelper::strncpy(p->msg, msg, sizeof(p->msg));
}
int render(DisplayDriver& display) override {
char tmp[16];
display.setCursor(0, 0);
display.setTextSize(1);
display.setColor(DisplayDriver::GREEN);
sprintf(tmp, "Unread: %d", num_unread);
display.print(tmp);
auto p = &unread[0];
int secs = _rtc->getCurrentTime() - p->timestamp;
if (secs < 60) {
sprintf(tmp, "%ds", secs);
} else if (secs < 60*60) {
sprintf(tmp, "%dm", secs / 60);
} else {
sprintf(tmp, "%dh", secs / (60*60));
}
display.setCursor(display.width() - display.getTextWidth(tmp), 0);
display.print(tmp);
display.drawRect(0, 11, display.width(), 1); // horiz line
display.setCursor(0, 14);
display.setColor(DisplayDriver::YELLOW);
display.print(p->origin);
display.setCursor(0, 25);
display.setColor(DisplayDriver::LIGHT);
display.printWordWrap(p->msg, display.width());
return 1000; // next render after 1000 ms
}
bool handleInput(char c) override {
if (c == KEY_SELECT || c == KEY_RIGHT) {
num_unread--;
if (num_unread == 0) {
_task->gotoHomeScreen();
} else {
// delete first/curr item from unread queue
for (int i = 0; i < num_unread; i++) {
unread[i] = unread[i + 1];
}
}
return true;
}
if (c == KEY_ENTER) {
num_unread = 0; // clear unread queue
_task->gotoHomeScreen();
return true;
}
return false;
}
}; };
void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs) { void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* node_prefs) {
_display = display; _display = display;
_sensors = sensors; _sensors = sensors;
_auto_off = millis() + AUTO_OFF_MILLIS; _auto_off = millis() + AUTO_OFF_MILLIS;
clearMsgPreview();
#if defined(PIN_USER_BTN)
user_btn.begin();
#endif
_node_prefs = node_prefs; _node_prefs = node_prefs;
if (_display != NULL) { if (_display != NULL) {
_display->turnOn(); _display->turnOn();
} }
// strip off dash and commit hash by changing dash to null terminator
// e.g: v1.2.3-abcdef -> v1.2.3
char *version = strdup(FIRMWARE_VERSION);
char *dash = strchr(version, '-');
if (dash) {
*dash = 0;
}
// v1.2.3 (1 Jan 2025)
sprintf(_version_info, "%s (%s)", version, FIRMWARE_BUILD_DATE);
#ifdef PIN_BUZZER #ifdef PIN_BUZZER
buzzer.begin(); buzzer.begin();
#endif #endif
// Initialize digital button if available
#ifdef PIN_USER_BTN
_userButton = new Button(PIN_USER_BTN, USER_BTN_PRESSED);
_userButton->begin();
// Set up digital button callbacks
_userButton->onShortPress([this]() { handleButtonShortPress(); });
_userButton->onDoublePress([this]() { handleButtonDoublePress(); });
_userButton->onTriplePress([this]() { handleButtonTriplePress(); });
_userButton->onQuadruplePress([this]() { handleButtonQuadruplePress(); });
_userButton->onLongPress([this]() { handleButtonLongPress(); });
_userButton->onAnyPress([this]() { handleButtonAnyPress(); });
#endif
// Initialize analog button if available
#ifdef PIN_USER_BTN_ANA
_userButtonAnalog = new Button(PIN_USER_BTN_ANA, USER_BTN_PRESSED, true, 20);
_userButtonAnalog->begin();
// Set up analog button callbacks
_userButtonAnalog->onShortPress([this]() { handleButtonShortPress(); });
_userButtonAnalog->onDoublePress([this]() { handleButtonDoublePress(); });
_userButtonAnalog->onTriplePress([this]() { handleButtonTriplePress(); });
_userButtonAnalog->onQuadruplePress([this]() { handleButtonQuadruplePress(); });
_userButtonAnalog->onLongPress([this]() { handleButtonLongPress(); });
_userButtonAnalog->onAnyPress([this]() { handleButtonAnyPress(); });
#endif
ui_started_at = millis(); ui_started_at = millis();
_alert_expiry = 0;
splash = new SplashScreen(this);
home = new HomeScreen(this, &rtc_clock, sensors, node_prefs);
msg_preview = new MsgPreviewScreen(this, &rtc_clock);
setCurrScreen(splash);
}
void UITask::showAlert(const char* text, int duration_millis) {
strcpy(_alert, text);
_alert_expiry = millis() + duration_millis;
} }
void UITask::soundBuzzer(UIEventType bet) { void UITask::soundBuzzer(UIEventType bet) {
@@ -109,147 +395,28 @@ switch(bet){
break; break;
} }
#endif #endif
// Serial.print("DBG: Buzzzzzz -> ");
// Serial.println((int) bet);
} }
void UITask::msgRead(int msgcount) { void UITask::msgRead(int msgcount) {
_msgcount = msgcount; _msgcount = msgcount;
if (msgcount == 0) { if (msgcount == 0) {
clearMsgPreview(); gotoHomeScreen();
} }
} }
void UITask::clearMsgPreview() {
_origin[0] = 0;
_msg[0] = 0;
_need_refresh = true;
}
void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) { void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount) {
_msgcount = msgcount; _msgcount = msgcount;
if (path_len == 0xFF) { ((MsgPreviewScreen *) msg_preview)->addPreview(path_len, from_name, text);
sprintf(_origin, "(F) %s", from_name); setCurrScreen(msg_preview);
} else {
sprintf(_origin, "(%d) %s", (uint32_t) path_len, from_name);
}
StrHelper::strncpy(_msg, text, sizeof(_msg));
if (_display != NULL) { if (_display != NULL) {
if (!_display->isOn()) _display->turnOn(); if (!_display->isOn()) _display->turnOn();
_auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer _auto_off = millis() + AUTO_OFF_MILLIS; // extend the auto-off timer
_need_refresh = true; _next_refresh = 0; // trigger refresh
} }
} }
void UITask::renderBatteryIndicator(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 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 = 12;
int iconX = _display->width() - iconWidth - 5; // Position the icon near the top-right corner
int iconY = 0;
_display->setColor(DisplayDriver::GREEN);
// battery outline
_display->drawRect(iconX, iconY, iconWidth, iconHeight);
// battery "cap"
_display->fillRect(iconX + iconWidth, iconY + (iconHeight / 4), 3, iconHeight / 2);
// fill the battery based on the percentage
int fillWidth = (batteryPercentage * (iconWidth - 4)) / 100;
_display->fillRect(iconX + 2, iconY + 2, fillWidth, iconHeight - 4);
}
void UITask::renderCurrScreen() {
if (_display == NULL) return; // assert() ??
char tmp[80];
if (_alert[0]) {
_display->setTextSize(1.4);
uint16_t textWidth = _display->getTextWidth(_alert);
_display->setCursor((_display->width() - textWidth) / 2, 22);
_display->setColor(DisplayDriver::GREEN);
_display->print(_alert);
_alert[0] = 0;
_need_refresh = true;
return;
} else if (_origin[0] && _msg[0]) { // message preview
// render message preview
_display->setCursor(0, 0);
_display->setTextSize(1);
_display->setColor(DisplayDriver::GREEN);
_display->print(_node_prefs->node_name);
_display->setCursor(0, 12);
_display->setColor(DisplayDriver::YELLOW);
_display->print(_origin);
_display->setCursor(0, 24);
_display->setColor(DisplayDriver::LIGHT);
_display->print(_msg);
_display->setCursor(_display->width() - 28, 9);
_display->setTextSize(2);
_display->setColor(DisplayDriver::ORANGE);
sprintf(tmp, "%d", _msgcount);
_display->print(tmp);
_display->setColor(DisplayDriver::YELLOW); // last color will be kept on T114
} else if ((millis() - ui_started_at) < BOOT_SCREEN_MILLIS) { // boot screen
// meshcore logo
_display->setColor(DisplayDriver::BLUE);
int logoWidth = 128;
_display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13);
// version info
_display->setColor(DisplayDriver::LIGHT);
_display->setTextSize(1);
uint16_t textWidth = _display->getTextWidth(_version_info);
_display->setCursor((_display->width() - textWidth) / 2, 22);
_display->print(_version_info);
} else { // home screen
// node name
_display->setCursor(0, 0);
_display->setTextSize(1);
_display->setColor(DisplayDriver::GREEN);
_display->print(_node_prefs->node_name);
// battery voltage
renderBatteryIndicator(_board->getBattMilliVolts());
// freq / sf
_display->setCursor(0, 20);
_display->setColor(DisplayDriver::YELLOW);
sprintf(tmp, "FREQ: %06.3f SF%d", _node_prefs->freq, _node_prefs->sf);
_display->print(tmp);
// bw / cr
_display->setCursor(0, 30);
sprintf(tmp, "BW: %03.2f CR: %d", _node_prefs->bw, _node_prefs->cr);
_display->print(tmp);
// BT pin
if (!_connected && the_mesh.getBLEPin() != 0) {
_display->setColor(DisplayDriver::RED);
_display->setTextSize(2);
_display->setCursor(0, 43);
sprintf(tmp, "Pin:%d", the_mesh.getBLEPin());
_display->print(tmp);
_display->setColor(DisplayDriver::GREEN);
} else {
_display->setColor(DisplayDriver::LIGHT);
}
}
_need_refresh = false;
}
void UITask::userLedHandler() { void UITask::userLedHandler() {
#ifdef PIN_STATUS_LED #ifdef PIN_STATUS_LED
static int state = 0; static int state = 0;
@@ -275,6 +442,11 @@ void UITask::userLedHandler() {
#endif #endif
} }
void UITask::setCurrScreen(UIScreen* c) {
curr = c;
_next_refresh = 0;
}
/* /*
hardware-agnostic pre-shutdown activity should be done here hardware-agnostic pre-shutdown activity should be done here
*/ */
@@ -293,96 +465,103 @@ void UITask::shutdown(bool restart){
#endif // PIN_BUZZER #endif // PIN_BUZZER
if (restart) if (restart) {
_board->reboot(); _board->reboot();
else } else {
_display->turnOff();
_board->powerOff(); _board->powerOff();
}
}
bool UITask::isButtonPressed() const {
#ifdef PIN_USER_BTN
return user_btn.isPressed();
#else
return false;
#endif
} }
void UITask::loop() { void UITask::loop() {
#ifdef PIN_USER_BTN char c = 0;
if (_userButton) { #if defined(PIN_USER_BTN)
_userButton->update(); int ev = user_btn.check();
} if (ev == BUTTON_EVENT_CLICK) {
#endif c = checkDisplayOn(KEY_SELECT);
#ifdef PIN_USER_BTN_ANA } else if (ev == BUTTON_EVENT_LONG_PRESS) {
if (_userButtonAnalog) { c = handleLongPress(KEY_ENTER);
_userButtonAnalog->update(); }
} #endif
#endif
if (c != 0 && curr) {
curr->handleInput(c);
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
_next_refresh = 0; // trigger refresh
}
userLedHandler(); userLedHandler();
#ifdef PIN_BUZZER #ifdef PIN_BUZZER
if (buzzer.isPlaying()) buzzer.loop(); if (buzzer.isPlaying()) buzzer.loop();
#endif #endif
if (_display != NULL && _display->isOn()) { if (curr) curr->poll();
static bool _firstBoot = true;
if(_firstBoot && (millis() - ui_started_at) >= BOOT_SCREEN_MILLIS) {
_need_refresh = true;
_firstBoot = false;
}
if (millis() >= _next_refresh && _need_refresh) {
_display->startFrame();
renderCurrScreen();
_display->endFrame();
_next_refresh = millis() + 1000; // refresh every second if (_display != NULL && _display->isOn()) {
if (millis() >= _next_refresh && curr) {
_display->startFrame();
int delay_millis = curr->render(*_display);
if (millis() < _alert_expiry) { // render alert popup
_display->setTextSize(1);
int y = _display->height() / 3;
int p = _display->height() / 32;
_display->setColor(DisplayDriver::DARK);
_display->fillRect(p, y, _display->width() - p*2, y);
_display->setColor(DisplayDriver::LIGHT); // draw box border
_display->drawRect(p, y, _display->width() - p*2, y);
_display->drawTextCentered(_display->width() / 2, y + p*3, _alert);
_next_refresh = _alert_expiry; // will need refresh when alert is dismissed
} else {
_next_refresh = millis() + delay_millis;
}
_display->endFrame();
} }
if (millis() > _auto_off) { if (millis() > _auto_off) {
_display->turnOff(); _display->turnOff();
} }
} }
#ifdef AUTO_SHUTDOWN_MILLIVOLTS
if (millis() > next_batt_chck) {
uint16_t milliVolts = getBattMilliVolts();
if (milliVolts > 0 && milliVolts < AUTO_SHUTDOWN_MILLIVOLTS) {
shutdown();
}
next_batt_chck = millis() + 8000;
}
#endif
} }
void UITask::handleButtonAnyPress() { char UITask::checkDisplayOn(char c) {
MESH_DEBUG_PRINTLN("UITask: any press triggered");
// called on any button press before other events, to wake up the display quickly
// do not refresh the display here, as it may block the button handler
if (_display != NULL) { if (_display != NULL) {
_displayWasOn = _display->isOn(); // Track display state before any action if (!_display->isOn()) {
if (!_displayWasOn) { _display->turnOn(); // turn display on and consume event
_display->turnOn(); c = 0;
} }
_auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer
_next_refresh = 0; // trigger refresh
} }
return c;
} }
void UITask::handleButtonShortPress() { char UITask::handleLongPress(char c) {
MESH_DEBUG_PRINTLN("UITask: short press triggered"); if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue
if (_display != NULL) { the_mesh.enterCLIRescue();
// Only clear message preview if display was already on before button press c = 0; // consume event
if (_displayWasOn) {
// If display was on and showing message preview, clear it
if (_origin[0] && _msg[0]) {
clearMsgPreview();
} else {
// Otherwise, refresh the display
_need_refresh = true;
}
} else {
_need_refresh = true; // display just turned on, so we need to refresh
}
// Note: Display turn-on and auto-off timer extension are handled by handleButtonAnyPress
} }
return c;
} }
void UITask::handleButtonDoublePress() { /*
MESH_DEBUG_PRINTLN("UITask: double press triggered, sending advert");
// ADVERT
#ifdef PIN_BUZZER
soundBuzzer(UIEventType::ack);
#endif
if (the_mesh.advert()) {
MESH_DEBUG_PRINTLN("Advert sent!");
sprintf(_alert, "Advert sent!");
} else {
MESH_DEBUG_PRINTLN("Advert failed!");
sprintf(_alert, "Advert failed..");
}
_need_refresh = true;
}
void UITask::handleButtonTriplePress() { void UITask::handleButtonTriplePress() {
MESH_DEBUG_PRINTLN("UITask: triple press triggered"); MESH_DEBUG_PRINTLN("UITask: triple press triggered");
// Toggle buzzer quiet mode // Toggle buzzer quiet mode
@@ -390,43 +569,12 @@ void UITask::handleButtonTriplePress() {
if (buzzer.isQuiet()) { if (buzzer.isQuiet()) {
buzzer.quiet(false); buzzer.quiet(false);
soundBuzzer(UIEventType::ack); soundBuzzer(UIEventType::ack);
sprintf(_alert, "Buzzer: ON"); showAlert("Buzzer: ON", 600);
} else { } else {
buzzer.quiet(true); buzzer.quiet(true);
sprintf(_alert, "Buzzer: OFF"); showAlert("Buzzer: OFF", 600);
} }
_need_refresh = true; _next_refresh = 0; // trigger refresh
#endif #endif
} }
*/
void UITask::handleButtonQuadruplePress() {
MESH_DEBUG_PRINTLN("UITask: quad press triggered");
if (_sensors != NULL) {
// toggle GPS onn/off
int num = _sensors->getNumSettings();
for (int i = 0; i < num; i++) {
if (strcmp(_sensors->getSettingName(i), "gps") == 0) {
if (strcmp(_sensors->getSettingValue(i), "1") == 0) {
_sensors->setSettingValue("gps", "0");
soundBuzzer(UIEventType::ack);
sprintf(_alert, "GPS: Disabled");
} else {
_sensors->setSettingValue("gps", "1");
soundBuzzer(UIEventType::ack);
sprintf(_alert, "GPS: Enabled");
}
break;
}
}
}
_need_refresh = true;
}
void UITask::handleButtonLongPress() {
MESH_DEBUG_PRINTLN("UITask: long press triggered");
if (millis() - ui_started_at < 8000) { // long press in first 8 seconds since startup -> CLI/rescue
the_mesh.enterCLIRescue();
} else {
shutdown();
}
}

View File

@@ -2,18 +2,18 @@
#include <MeshCore.h> #include <MeshCore.h>
#include <helpers/ui/DisplayDriver.h> #include <helpers/ui/DisplayDriver.h>
#include <helpers/ui/UIScreen.h>
#include <helpers/SensorManager.h> #include <helpers/SensorManager.h>
#include <stddef.h> #include <helpers/BaseSerialInterface.h>
#include <Arduino.h>
#ifdef PIN_BUZZER #ifdef PIN_BUZZER
#include <helpers/ui/buzzer.h> #include <helpers/ui/buzzer.h>
#endif #endif
#include "NodePrefs.h" #include "NodePrefs.h"
#include "Button.h"
enum class UIEventType enum class UIEventType {
{
none, none,
contactMessage, contactMessage,
channelMessage, channelMessage,
@@ -22,9 +22,12 @@
ack ack
}; };
#define MAX_TOP_LEVEL 8
class UITask { class UITask {
DisplayDriver* _display; DisplayDriver* _display;
mesh::MainBoard* _board; mesh::MainBoard* _board;
BaseSerialInterface* _serial;
SensorManager* _sensors; SensorManager* _sensors;
#ifdef PIN_BUZZER #ifdef PIN_BUZZER
genericBuzzer buzzer; genericBuzzer buzzer;
@@ -32,48 +35,45 @@ class UITask {
unsigned long _next_refresh, _auto_off; unsigned long _next_refresh, _auto_off;
bool _connected; bool _connected;
NodePrefs* _node_prefs; NodePrefs* _node_prefs;
char _version_info[32];
char _origin[62];
char _msg[80];
char _alert[80]; char _alert[80];
unsigned long _alert_expiry;
int _msgcount; int _msgcount;
bool _need_refresh = true; unsigned long ui_started_at, next_batt_chck;
bool _displayWasOn = false; // Track display state before button press
unsigned long ui_started_at;
// Button handlers UIScreen* splash;
#ifdef PIN_USER_BTN UIScreen* home;
Button* _userButton = nullptr; UIScreen* msg_preview;
#endif UIScreen* curr;
#ifdef PIN_USER_BTN_ANA
Button* _userButtonAnalog = nullptr;
#endif
void renderCurrScreen();
void userLedHandler(); void userLedHandler();
void renderBatteryIndicator(uint16_t batteryMilliVolts);
// Button action handlers // Button action handlers
void handleButtonAnyPress(); char checkDisplayOn(char c);
void handleButtonShortPress(); char handleLongPress(char c);
void handleButtonDoublePress();
void handleButtonTriplePress(); void setCurrScreen(UIScreen* c);
void handleButtonQuadruplePress();
void handleButtonLongPress();
public: public:
UITask(mesh::MainBoard* board) : _board(board), _display(NULL), _sensors(NULL) { UITask(mesh::MainBoard* board, BaseSerialInterface* serial) : _board(board), _serial(serial), _display(NULL), _sensors(NULL) {
_next_refresh = 0; next_batt_chck = _next_refresh = 0;
ui_started_at = 0; ui_started_at = 0;
_connected = false; _connected = false;
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);
void setHasConnection(bool connected) { _connected = connected; } void setHasConnection(bool connected) { _connected = connected; }
bool hasConnection() const { return _connected; }
uint16_t getBattMilliVolts() const { return _board->getBattMilliVolts(); }
bool isSerialEnabled() const { return _serial->isEnabled(); }
void enableSerial() { _serial->enable(); }
void disableSerial() { _serial->disable(); }
int getMsgCount() const { return _msgcount; }
bool hasDisplay() const { return _display != NULL; } bool hasDisplay() const { return _display != NULL; }
void clearMsgPreview(); bool isButtonPressed() const;
void msgRead(int msgcount); void msgRead(int msgcount);
void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount); void newMsg(uint8_t path_len, const char* from_name, const char* text, int msgcount);
void soundBuzzer(UIEventType bet = UIEventType::none); void soundBuzzer(UIEventType bet = UIEventType::none);

View File

@@ -0,0 +1,118 @@
#pragma once
#include <stdint.h>
// 'meshcore', 128x13px
static const uint8_t meshcore_logo [] = {
0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe,
0x3c, 0x03, 0xe3, 0xff, 0xc7, 0xff, 0x8e, 0x03, 0x8f, 0xfe, 0x3f, 0xfe, 0x1f, 0xff, 0x1f, 0xfe,
0x3e, 0x03, 0xc3, 0xff, 0x8f, 0xff, 0x0e, 0x07, 0x8f, 0xfe, 0x7f, 0xfe, 0x1f, 0xff, 0x1f, 0xfc,
0x3e, 0x07, 0xc7, 0x80, 0x0e, 0x00, 0x0e, 0x07, 0x9e, 0x00, 0x78, 0x0e, 0x3c, 0x0f, 0x1c, 0x00,
0x3e, 0x0f, 0xc7, 0x80, 0x1e, 0x00, 0x0e, 0x07, 0x1e, 0x00, 0x70, 0x0e, 0x38, 0x0f, 0x3c, 0x00,
0x7f, 0x0f, 0xc7, 0xfe, 0x1f, 0xfc, 0x1f, 0xff, 0x1c, 0x00, 0x70, 0x0e, 0x38, 0x0e, 0x3f, 0xf8,
0x7f, 0x1f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x0e, 0x38, 0x0e, 0x3f, 0xf8,
0x7f, 0x3f, 0xc7, 0xfe, 0x0f, 0xff, 0x1f, 0xff, 0x1c, 0x00, 0xf0, 0x1e, 0x3f, 0xfe, 0x3f, 0xf0,
0x77, 0x3b, 0x87, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xfc, 0x38, 0x00,
0x77, 0xfb, 0x8f, 0x00, 0x00, 0x07, 0x1c, 0x0f, 0x3c, 0x00, 0xe0, 0x1c, 0x7f, 0xf8, 0x38, 0x00,
0x73, 0xf3, 0x8f, 0xff, 0x0f, 0xff, 0x1c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x78, 0x7f, 0xf8,
0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfe, 0x3c, 0x0e, 0x3f, 0xf8, 0xff, 0xfc, 0x70, 0x3c, 0x7f, 0xf8,
0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8,
};
static const uint8_t bluetooth_on[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x30, 0x00, 0x00,
0x00, 0x3C, 0x00, 0x00,
0x00, 0x3E, 0x00, 0x00,
0x00, 0x3F, 0x80, 0x00,
0x00, 0x3F, 0xC0, 0x00,
0x00, 0x3B, 0xE0, 0x00,
0x30, 0x38, 0xF8, 0x00,
0x3C, 0x38, 0x7C, 0x00,
0x3E, 0x38, 0x7C, 0x00,
0x1F, 0xB8, 0xF8, 0x70,
0x07, 0xF9, 0xF0, 0x78,
0x03, 0xFF, 0xC0, 0x78,
0x00, 0xFF, 0x80, 0x3C,
0x00, 0x7F, 0x07, 0x1C,
0x00, 0x7E, 0x07, 0x1C,
0x03, 0xFF, 0x82, 0x1C,
0x03, 0xFF, 0xC0, 0x78,
0x07, 0xFB, 0xE0, 0x78,
0x0F, 0xB8, 0xF8, 0x70,
0x3E, 0x38, 0x7C, 0x00,
0x3C, 0x38, 0x7C, 0x00,
0x38, 0x38, 0xF8, 0x00,
0x00, 0x39, 0xF0, 0x00,
0x00, 0x3F, 0xC0, 0x00,
0x00, 0x3F, 0x80, 0x00,
0x00, 0x3E, 0x00, 0x00,
0x00, 0x3C, 0x00, 0x00,
0x00, 0x38, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
static const uint8_t bluetooth_off[] = {
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x03, 0x80, 0x00,
0x00, 0x03, 0xC0, 0x00,
0x00, 0x03, 0xE0, 0x00,
0x38, 0x03, 0xF8, 0x00,
0x3C, 0x03, 0xFC, 0x00,
0x3E, 0x03, 0xBF, 0x00,
0x0F, 0x83, 0x8F, 0x80,
0x07, 0xC3, 0x87, 0xC0,
0x03, 0xF0, 0x03, 0xC0,
0x00, 0xF8, 0x0F, 0x80,
0x00, 0x7C, 0x0F, 0x00,
0x00, 0x1F, 0x0E, 0x00,
0x00, 0x0F, 0x80, 0x00,
0x00, 0x07, 0xE0, 0x00,
0x00, 0x07, 0xF0, 0x00,
0x00, 0x0F, 0xF8, 0x00,
0x00, 0x3F, 0xBE, 0x00,
0x00, 0x7F, 0x9F, 0x00,
0x00, 0xFB, 0x8F, 0xC0,
0x03, 0xE3, 0x83, 0xE0,
0x03, 0xC3, 0x87, 0xF0,
0x03, 0x83, 0x8F, 0xFC,
0x00, 0x03, 0xBF, 0x3C,
0x00, 0x03, 0xFC, 0x1C,
0x00, 0x03, 0xF8, 0x00,
0x00, 0x03, 0xE0, 0x00,
0x00, 0x03, 0xC0, 0x00,
0x00, 0x03, 0x80, 0x00,
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
};
static const uint8_t power_icon[] = {
0x00, 0x01, 0x80, 0x00, 0x00, 0x03, 0xC0, 0x00, 0x00, 0x03, 0xC0, 0x00,
0x00, 0x33, 0xCC, 0x00, 0x00, 0xF3, 0xCF, 0x00, 0x01, 0xF3, 0xCF, 0x80,
0x03, 0xF3, 0xCF, 0xC0, 0x07, 0xF3, 0xCF, 0xE0, 0x0F, 0xE3, 0xC7, 0xF0,
0x1F, 0xC3, 0xC3, 0xF8, 0x1F, 0x83, 0xC1, 0xF8, 0x3F, 0x03, 0xC0, 0xFC,
0x3E, 0x03, 0xC0, 0x7C, 0x3E, 0x03, 0xC0, 0x7C, 0x7E, 0x01, 0x80, 0x7E,
0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E,
0x7C, 0x00, 0x00, 0x3E, 0x7C, 0x00, 0x00, 0x3E, 0x3E, 0x00, 0x00, 0x7C,
0x3E, 0x00, 0x00, 0x7C, 0x3F, 0x00, 0x00, 0xFC, 0x1F, 0x80, 0x01, 0xF8,
0x1F, 0xC0, 0x03, 0xF8, 0x0F, 0xE0, 0x07, 0xF0, 0x0F, 0xF8, 0x1F, 0xF0,
0x07, 0xFF, 0xFF, 0xE0, 0x03, 0xFF, 0xFF, 0xC0, 0x00, 0xFF, 0xFF, 0x00,
0x00, 0x3F, 0xFC, 0x00, 0x00, 0x0F, 0xF0, 0x00,
};
static const uint8_t advert_icon[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x30,
0x1C, 0x00, 0x00, 0x38, 0x18, 0x00, 0x00, 0x18, 0x30, 0x00, 0x00, 0x0C,
0x30, 0x60, 0x06, 0x0C, 0x60, 0xE0, 0x07, 0x06, 0x61, 0xC0, 0x03, 0x86,
0xE1, 0x81, 0x81, 0x87, 0xC3, 0x07, 0xE0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3,
0xC3, 0x0F, 0xF0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3, 0xC3, 0x0F, 0xF0, 0xC3,
0xC3, 0x07, 0xE0, 0xC3, 0xC1, 0x83, 0xC1, 0x83, 0x61, 0x80, 0x01, 0x86,
0x60, 0xC0, 0x03, 0x06, 0x70, 0xE0, 0x07, 0x0E, 0x30, 0x40, 0x02, 0x0C,
0x38, 0x00, 0x00, 0x1C, 0x18, 0x00, 0x00, 0x18, 0x0C, 0x00, 0x00, 0x30,
0x04, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

View File

@@ -81,7 +81,7 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store);
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
#include "UITask.h" #include "UITask.h"
UITask ui_task(&board); UITask ui_task(&board, &serial_interface);
#endif #endif
/* END GLOBAL OBJECTS */ /* END GLOBAL OBJECTS */
@@ -99,7 +99,10 @@ void setup() {
if (display.begin()) { if (display.begin()) {
disp = &display; disp = &display;
disp->startFrame(); disp->startFrame();
disp->print("Please wait..."); #ifdef ST7789
disp->setTextSize(2);
#endif
disp->drawTextCentered(disp->width() / 2, 28, "Loading...");
disp->endFrame(); disp->endFrame();
} }
#endif #endif

View File

@@ -83,6 +83,7 @@ void SerialBLEInterface::onConnect(BLEServer* pServer) {
void SerialBLEInterface::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) { void SerialBLEInterface::onConnect(BLEServer* pServer, esp_ble_gatts_cb_param_t *param) {
BLE_DEBUG_PRINTLN("onConnect(), conn_id=%d, mtu=%d", param->connect.conn_id, pServer->getPeerMTU(param->connect.conn_id)); BLE_DEBUG_PRINTLN("onConnect(), conn_id=%d, mtu=%d", param->connect.conn_id, pServer->getPeerMTU(param->connect.conn_id));
last_conn_id = param->connect.conn_id;
} }
void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) { void SerialBLEInterface::onMtuChanged(BLEServer* pServer, esp_ble_gatts_cb_param_t* param) {
@@ -143,6 +144,7 @@ void SerialBLEInterface::disable() {
BLE_DEBUG_PRINTLN("SerialBLEInterface::disable"); BLE_DEBUG_PRINTLN("SerialBLEInterface::disable");
pServer->getAdvertising()->stop(); pServer->getAdvertising()->stop();
pServer->disconnect(last_conn_id);
pService->stop(); pService->stop();
oldDeviceConnected = deviceConnected = false; oldDeviceConnected = deviceConnected = false;
adv_restart_time = 0; adv_restart_time = 0;

View File

@@ -13,6 +13,7 @@ class SerialBLEInterface : public BaseSerialInterface, BLESecurityCallbacks, BLE
bool deviceConnected; bool deviceConnected;
bool oldDeviceConnected; bool oldDeviceConnected;
bool _isEnabled; bool _isEnabled;
uint16_t last_conn_id;
uint32_t _pin_code; uint32_t _pin_code;
unsigned long _last_write; unsigned long _last_write;
unsigned long adv_restart_time; unsigned long adv_restart_time;
@@ -56,6 +57,7 @@ public:
adv_restart_time = 0; adv_restart_time = 0;
_isEnabled = false; _isEnabled = false;
_last_write = 0; _last_write = 0;
last_conn_id = 0;
send_queue_len = recv_queue_len = 0; send_queue_len = recv_queue_len = 0;
} }

View File

@@ -115,6 +115,16 @@ void SerialBLEInterface::enable() {
void SerialBLEInterface::disable() { void SerialBLEInterface::disable() {
_isEnabled = false; _isEnabled = false;
BLE_DEBUG_PRINTLN("SerialBLEInterface::disable"); BLE_DEBUG_PRINTLN("SerialBLEInterface::disable");
uint16_t conn_id;
if (Bluefruit.getConnectedHandles(&conn_id, 1) > 0) {
Bluefruit.disconnect(conn_id);
}
Bluefruit.Advertising.restartOnDisconnect(false);
Bluefruit.Advertising.stop();
Bluefruit.Advertising.clearData();
stopAdv(); stopAdv();
} }

View File

@@ -60,5 +60,9 @@ public:
NVIC_SystemReset(); NVIC_SystemReset();
} }
void powerOff() override {
sd_power_system_off();
}
bool startOTAUpdate(const char* id, char reply[]) override; bool startOTAUpdate(const char* id, char reply[]) override;
}; };

View File

@@ -55,4 +55,8 @@ public:
void reboot() override { void reboot() override {
NVIC_SystemReset(); NVIC_SystemReset();
} }
void powerOff() override {
sd_power_system_off();
}
}; };

View File

@@ -21,9 +21,15 @@ public:
virtual void setColor(Color c) = 0; virtual void setColor(Color c) = 0;
virtual void setCursor(int x, int y) = 0; virtual void setCursor(int x, int y) = 0;
virtual void print(const char* str) = 0; virtual void print(const char* str) = 0;
virtual void printWordWrap(const char* str, int max_width) { print(str); } // fallback to basic print() if no override
virtual void fillRect(int x, int y, int w, int h) = 0; virtual void fillRect(int x, int y, int w, int h) = 0;
virtual void drawRect(int x, int y, int w, int h) = 0; virtual void drawRect(int x, int y, int w, int h) = 0;
virtual void drawXbm(int x, int y, const uint8_t* bits, int w, int h) = 0; virtual void drawXbm(int x, int y, const uint8_t* bits, int w, int h) = 0;
virtual uint16_t getTextWidth(const char* str) = 0; virtual uint16_t getTextWidth(const char* str) = 0;
virtual void drawTextCentered(int mid_x, int y, const char* str) { // helper method (override to optimise)
int w = getTextWidth(str);
setCursor(mid_x - w/2, y);
print(str);
}
virtual void endFrame() = 0; virtual void endFrame() = 0;
}; };

View File

@@ -47,6 +47,7 @@ void GxEPDDisplay::clear() {
void GxEPDDisplay::startFrame(Color bkg) { void GxEPDDisplay::startFrame(Color bkg) {
display.fillScreen(GxEPD_WHITE); display.fillScreen(GxEPD_WHITE);
display.setTextColor(_curr_color = GxEPD_BLACK);
} }
void GxEPDDisplay::setTextSize(int sz) { void GxEPDDisplay::setTextSize(int sz) {
@@ -67,7 +68,11 @@ void GxEPDDisplay::setTextSize(int sz) {
} }
void GxEPDDisplay::setColor(Color c) { void GxEPDDisplay::setColor(Color c) {
display.setTextColor(GxEPD_BLACK); if (c == DARK) {
display.setTextColor(_curr_color = GxEPD_BLACK);
} else {
display.setTextColor(_curr_color = GxEPD_WHITE);
}
} }
void GxEPDDisplay::setCursor(int x, int y) { void GxEPDDisplay::setCursor(int x, int y) {
@@ -79,11 +84,11 @@ void GxEPDDisplay::print(const char* str) {
} }
void GxEPDDisplay::fillRect(int x, int y, int w, int h) { void GxEPDDisplay::fillRect(int x, int y, int w, int h) {
display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK); display.fillRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _curr_color);
} }
void GxEPDDisplay::drawRect(int x, int y, int w, int h) { void GxEPDDisplay::drawRect(int x, int y, int w, int h) {
display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, GxEPD_BLACK); display.drawRect(x*SCALE_X, y*SCALE_Y, w*SCALE_X, h*SCALE_Y, _curr_color);
} }
void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
@@ -116,7 +121,7 @@ void GxEPDDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) {
// If the bit is set, draw a block of pixels // If the bit is set, draw a block of pixels
if (bitSet) { if (bitSet) {
// Draw the block as a filled rectangle // Draw the block as a filled rectangle
display.fillRect(x1, y1, block_w, block_h, GxEPD_BLACK); display.fillRect(x1, y1, block_w, block_h, _curr_color);
} }
} }
} }

View File

@@ -28,6 +28,7 @@ class GxEPDDisplay : public DisplayDriver {
GxEPD2_BW<GxEPD2_150_BN, 200> display; GxEPD2_BW<GxEPD2_150_BN, 200> display;
bool _init = false; bool _init = false;
bool _isOn = false; bool _isOn = false;
uint16_t _curr_color;
public: public:
// there is a margin in y... // there is a margin in y...

View File

@@ -0,0 +1,74 @@
#include "MomentaryButton.h"
MomentaryButton::MomentaryButton(int8_t pin, int long_press_millis, bool reverse) {
_pin = pin;
_reverse = reverse;
down_at = 0;
prev = _reverse ? HIGH : LOW;
cancel = 0;
_long_millis = long_press_millis;
}
void MomentaryButton::begin(bool pulldownup) {
if (_pin >= 0) {
pinMode(_pin, pulldownup ? (_reverse ? INPUT_PULLUP : INPUT_PULLDOWN) : INPUT);
}
}
bool MomentaryButton::isPressed() const {
return isPressed(digitalRead(_pin));
}
void MomentaryButton::cancelClick() {
cancel = 1;
}
bool MomentaryButton::isPressed(int level) const {
if (_reverse) {
return level == LOW;
} else {
return level != LOW;
}
}
int MomentaryButton::check(bool repeat_click) {
if (_pin < 0) return BUTTON_EVENT_NONE;
int event = BUTTON_EVENT_NONE;
int btn = digitalRead(_pin);
if (btn != prev) {
if (isPressed(btn)) {
down_at = millis();
} else {
// button UP
if (_long_millis > 0) {
if (down_at > 0 && (unsigned long)(millis() - down_at) < _long_millis) { // only a CLICK if still within the long_press millis
event = BUTTON_EVENT_CLICK;
}
} else {
event = BUTTON_EVENT_CLICK; // any UP results in CLICK event when NOT using long_press feature
}
if (event == BUTTON_EVENT_CLICK && cancel) {
event = BUTTON_EVENT_NONE;
}
down_at = 0;
}
prev = btn;
}
if (!isPressed(btn) && cancel) { // always clear the pending 'cancel' once button is back in UP state
cancel = 0;
}
if (_long_millis > 0 && down_at > 0 && (unsigned long)(millis() - down_at) >= _long_millis) {
event = BUTTON_EVENT_LONG_PRESS;
down_at = 0;
}
if (down_at > 0 && repeat_click) {
unsigned long diff = (unsigned long)(millis() - down_at);
if (diff >= 700) {
event = BUTTON_EVENT_CLICK; // wait 700 millis before repeating the click events
}
}
return event;
}

View File

@@ -0,0 +1,25 @@
#pragma once
#include <Arduino.h>
#define BUTTON_EVENT_NONE 0
#define BUTTON_EVENT_CLICK 1
#define BUTTON_EVENT_LONG_PRESS 2
class MomentaryButton {
int8_t _pin;
int8_t prev, cancel;
bool _reverse;
int _long_millis;
unsigned long down_at;
bool isPressed(int level) const;
public:
MomentaryButton(int8_t pin, int long_press_mills=0, bool reverse=false);
void begin(bool pulldownup=false);
int check(bool repeat_click=false); // returns one of BUTTON_EVENT_*
void cancelClick(); // suppress next BUTTON_EVENT_CLICK (if already in DOWN state)
uint8_t getPin() { return _pin; }
bool isPressed() const;
};

View File

@@ -62,6 +62,9 @@ void ST7789Display::clear() {
void ST7789Display::startFrame(Color bkg) { void ST7789Display::startFrame(Color bkg) {
display.clear(); display.clear();
_color = ST77XX_WHITE;
display.setRGB(_color);
display.setFont(ArialMT_Plain_16);
} }
void ST7789Display::setTextSize(int sz) { void ST7789Display::setTextSize(int sz) {
@@ -81,7 +84,9 @@ void ST7789Display::setColor(Color c) {
switch (c) { switch (c) {
case DisplayDriver::DARK : case DisplayDriver::DARK :
_color = ST77XX_BLACK; _color = ST77XX_BLACK;
display.setColor(OLEDDISPLAY_COLOR::BLACK);
break; break;
#if 0
case DisplayDriver::LIGHT : case DisplayDriver::LIGHT :
_color = ST77XX_WHITE; _color = ST77XX_WHITE;
break; break;
@@ -100,8 +105,10 @@ void ST7789Display::setColor(Color c) {
case DisplayDriver::ORANGE : case DisplayDriver::ORANGE :
_color = ST77XX_ORANGE; _color = ST77XX_ORANGE;
break; break;
#endif
default: default:
_color = ST77XX_WHITE; _color = ST77XX_WHITE;
display.setColor(OLEDDISPLAY_COLOR::WHITE);
break; break;
} }
display.setRGB(_color); display.setRGB(_color);
@@ -116,6 +123,10 @@ void ST7789Display::print(const char* str) {
display.drawString(_x, _y, str); display.drawString(_x, _y, str);
} }
void ST7789Display::printWordWrap(const char* str, int max_width) {
display.drawStringMaxWidth(_x, _y, max_width*SCALE_X, str);
}
void ST7789Display::fillRect(int x, int y, int w, int h) { void ST7789Display::fillRect(int x, int y, int w, int h) {
display.fillRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y); display.fillRect(x*SCALE_X + X_OFFSET, y*SCALE_Y + Y_OFFSET, w*SCALE_X, h*SCALE_Y);
} }

View File

@@ -27,6 +27,7 @@ public:
void setColor(Color c) override; void setColor(Color c) override;
void setCursor(int x, int y) override; void setCursor(int x, int y) override;
void print(const char* str) override; void print(const char* str) override;
void printWordWrap(const char* str, int max_width) override;
void fillRect(int x, int y, int w, int h) override; void fillRect(int x, int y, int w, int h) override;
void drawRect(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; void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override;

21
src/helpers/ui/UIScreen.h Normal file
View File

@@ -0,0 +1,21 @@
#pragma once
#include "DisplayDriver.h"
#define KEY_LEFT 0xB4
#define KEY_UP 0xB5
#define KEY_DOWN 0xB6
#define KEY_RIGHT 0xB7
#define KEY_SELECT 10
#define KEY_ENTER 13
#define KEY_BACK 27 // Esc
class UIScreen {
protected:
UIScreen() { }
public:
virtual int render(DisplayDriver& display) =0; // return value is number of millis until next render
virtual bool handleInput(char c) { return false; }
virtual void poll() { }
};

View File

@@ -6,6 +6,7 @@ build_flags =
${sensor_base.build_flags} ${sensor_base.build_flags}
-I variants/heltec_v3 -I variants/heltec_v3
-D HELTEC_LORA_V3 -D HELTEC_LORA_V3
-D ESP32_CPU_FREQ=80
-D RADIO_CLASS=CustomSX1262 -D RADIO_CLASS=CustomSX1262
-D WRAPPER_CLASS=CustomSX1262Wrapper -D WRAPPER_CLASS=CustomSX1262Wrapper
-D LORA_TX_POWER=22 -D LORA_TX_POWER=22
@@ -46,6 +47,7 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter}
lib_deps = lib_deps =
${Heltec_lora32_v3.lib_deps} ${Heltec_lora32_v3.lib_deps}
${esp32_ota.lib_deps} ${esp32_ota.lib_deps}
bakercp/CRC32 @ ^2.0.0
[env:Heltec_v3_room_server] [env:Heltec_v3_room_server]
extends = Heltec_lora32_v3 extends = Heltec_lora32_v3
@@ -91,6 +93,7 @@ build_flags =
; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 ; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter} build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp> +<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<../examples/companion_radio> +<../examples/companion_radio>
lib_deps = lib_deps =
${Heltec_lora32_v3.lib_deps} ${Heltec_lora32_v3.lib_deps}
@@ -100,16 +103,18 @@ lib_deps =
extends = Heltec_lora32_v3 extends = Heltec_lora32_v3
build_flags = build_flags =
${Heltec_lora32_v3.build_flags} ${Heltec_lora32_v3.build_flags}
-D MAX_CONTACTS=100 -D MAX_CONTACTS=160
-D MAX_GROUP_CHANNELS=8 -D MAX_GROUP_CHANNELS=8
-D DISPLAY_CLASS=SSD1306Display -D DISPLAY_CLASS=SSD1306Display
-D BLE_PIN_CODE=123456 ; dynamic, random PIN -D BLE_PIN_CODE=123456 ; dynamic, random PIN
-D AUTO_SHUTDOWN_MILLIVOLTS=3400
-D BLE_DEBUG_LOGGING=1 -D BLE_DEBUG_LOGGING=1
-D OFFLINE_QUEUE_SIZE=256 -D OFFLINE_QUEUE_SIZE=256
; -D MESH_PACKET_LOGGING=1 ; -D MESH_PACKET_LOGGING=1
; -D MESH_DEBUG=1 ; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter} build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp> +<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/esp32/*.cpp> +<helpers/esp32/*.cpp>
+<../examples/companion_radio> +<../examples/companion_radio>
lib_deps = lib_deps =
@@ -130,6 +135,7 @@ build_flags =
; -D MESH_DEBUG=1 ; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter} build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<helpers/ui/SSD1306Display.cpp> +<helpers/ui/SSD1306Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/esp32/*.cpp> +<helpers/esp32/*.cpp>
+<../examples/companion_radio> +<../examples/companion_radio>
lib_deps = lib_deps =
@@ -172,6 +178,7 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter}
lib_deps = lib_deps =
${Heltec_lora32_v3.lib_deps} ${Heltec_lora32_v3.lib_deps}
${esp32_ota.lib_deps} ${esp32_ota.lib_deps}
bakercp/CRC32 @ ^2.0.0
[env:Heltec_WSL3_room_server] [env:Heltec_WSL3_room_server]
extends = Heltec_lora32_v3 extends = Heltec_lora32_v3
@@ -237,3 +244,49 @@ build_src_filter = ${Heltec_lora32_v3.build_src_filter}
lib_deps = lib_deps =
${Heltec_lora32_v3.lib_deps} ${Heltec_lora32_v3.lib_deps}
${esp32_ota.lib_deps} ${esp32_ota.lib_deps}
[env:Heltec_WSL3_espnow_bridge]
extends = Heltec_lora32_v3
build_flags =
${Heltec_lora32_v3.build_flags}
; -D LORA_FREQ=915.8
-D MESH_PACKET_LOGGING=1
-D ENV_INCLUDE_AHTX0=0
-D ENV_INCLUDE_BME280=0
-D ENV_INCLUDE_BMP280=0
-D ENV_INCLUDE_INA3221=0
-D ENV_INCLUDE_INA219=0
-D ENV_INCLUDE_MLX90614=0
-D ENV_INCLUDE_VL53L0X=0
-D ENV_INCLUDE_GPS=0
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<../examples/simple_bridge/main.cpp>
+<helpers/esp32/ESPNOWRadio.cpp>
lib_deps =
${Heltec_lora32_v3.lib_deps}
bakercp/CRC32 @ ^2.0.0
[env:Heltec_WSL3_serial_bridge]
extends = Heltec_lora32_v3
build_flags =
${Heltec_lora32_v3.build_flags}
; -D LORA_FREQ=915.8
-D MESH_PACKET_LOGGING=1
-D SERIAL_BRIDGE_RX=47
-D SERIAL_BRIDGE_TX=48
-D ENV_INCLUDE_AHTX0=0
-D ENV_INCLUDE_BME280=0
-D ENV_INCLUDE_BMP280=0
-D ENV_INCLUDE_INA3221=0
-D ENV_INCLUDE_INA219=0
-D ENV_INCLUDE_MLX90614=0
-D ENV_INCLUDE_VL53L0X=0
-D ENV_INCLUDE_GPS=0
; -D MESH_DEBUG=1
build_src_filter = ${Heltec_lora32_v3.build_src_filter}
+<../examples/simple_bridge/main.cpp>
+<../examples/simple_bridge/SerialBridgeRadio.cpp>
lib_deps =
${Heltec_lora32_v3.lib_deps}
bakercp/CRC32 @ ^2.0.0

View File

@@ -25,6 +25,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock);
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
DISPLAY_CLASS display; DISPLAY_CLASS display;
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
#endif #endif
bool radio_init() { bool radio_init() {

View File

@@ -10,6 +10,7 @@
#include <helpers/sensors/EnvironmentSensorManager.h> #include <helpers/sensors/EnvironmentSensorManager.h>
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
#include <helpers/ui/SSD1306Display.h> #include <helpers/ui/SSD1306Display.h>
#include <helpers/ui/MomentaryButton.h>
#endif #endif
extern HeltecV3Board board; extern HeltecV3Board board;
@@ -19,6 +20,7 @@ extern EnvironmentSensorManager sensors;
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
extern DISPLAY_CLASS display; extern DISPLAY_CLASS display;
extern MomentaryButton user_btn;
#endif #endif
bool radio_init(); bool radio_init();

View File

@@ -30,6 +30,7 @@ build_src_filter = ${nrf52840_t114.build_src_filter}
+<helpers/nrf52/T114Board.cpp> +<helpers/nrf52/T114Board.cpp>
+<../variants/t114> +<../variants/t114>
+<helpers/ui/ST7789Display.cpp> +<helpers/ui/ST7789Display.cpp>
+<helpers/ui/MomentaryButton.cpp>
+<helpers/ui/OLEDDisplay.cpp> +<helpers/ui/OLEDDisplay.cpp>
+<helpers/ui/OLEDDisplayFonts.cpp> +<helpers/ui/OLEDDisplayFonts.cpp>
lib_deps = lib_deps =

View File

@@ -16,6 +16,7 @@ T114SensorManager sensors = T114SensorManager(nmea);
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
DISPLAY_CLASS display; DISPLAY_CLASS display;
MomentaryButton user_btn(PIN_USER_BTN, 1000, true);
#endif #endif
bool radio_init() { bool radio_init() {

View File

@@ -10,6 +10,7 @@
#include <helpers/sensors/LocationProvider.h> #include <helpers/sensors/LocationProvider.h>
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
#include <helpers/ui/ST7789Display.h> #include <helpers/ui/ST7789Display.h>
#include <helpers/ui/MomentaryButton.h>
#endif #endif
class T114SensorManager : public SensorManager { class T114SensorManager : public SensorManager {
@@ -37,6 +38,7 @@ extern T114SensorManager sensors;
#ifdef DISPLAY_CLASS #ifdef DISPLAY_CLASS
extern DISPLAY_CLASS display; extern DISPLAY_CLASS display;
extern MomentaryButton user_btn;
#endif #endif
bool radio_init(); bool radio_init();

View File

@@ -61,6 +61,7 @@ build_flags =
lib_deps = lib_deps =
${Xiao_esp32_C3.lib_deps} ${Xiao_esp32_C3.lib_deps}
${esp32_ota.lib_deps} ${esp32_ota.lib_deps}
bakercp/CRC32 @ ^2.0.0
[env:Xiao_C3_companion_radio_ble] [env:Xiao_C3_companion_radio_ble]
extends = Xiao_esp32_C3 extends = Xiao_esp32_C3
@@ -127,6 +128,7 @@ build_flags =
lib_deps = lib_deps =
${Xiao_esp32_C3_custom.lib_deps} ${Xiao_esp32_C3_custom.lib_deps}
${esp32_ota.lib_deps} ${esp32_ota.lib_deps}
bakercp/CRC32 @ ^2.0.0
[env:Xiao_C3_Repeater_sx1268_custom] [env:Xiao_C3_Repeater_sx1268_custom]
extends = Xiao_esp32_C3_custom extends = Xiao_esp32_C3_custom
@@ -146,4 +148,5 @@ build_flags =
; -D MESH_DEBUG=1 ; -D MESH_DEBUG=1
lib_deps = lib_deps =
${Xiao_esp32_C3_custom.lib_deps} ${Xiao_esp32_C3_custom.lib_deps}
${esp32_ota.lib_deps} ${esp32_ota.lib_deps}
bakercp/CRC32 @ ^2.0.0