diff --git a/docs/faq.md b/docs/faq.md index fd578ad2..632f369c 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -372,4 +372,4 @@ You can update repeater and room server firmware with a bluetooth connection bet --- - + \ No newline at end of file diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h new file mode 100644 index 00000000..691eec42 --- /dev/null +++ b/examples/companion_radio/NodePrefs.h @@ -0,0 +1,22 @@ +#ifndef NODE_PREFS_H +#define NODE_PREFS_H + +#include // For uint8_t, uint32_t + +struct NodePrefs { // persisted to file + float airtime_factor; + char node_name[32]; + double node_lat, node_lon; + float freq; + uint8_t sf; + uint8_t cr; + uint8_t reserved1; + uint8_t manual_add_contacts; + float bw; + uint8_t tx_power_dbm; + uint8_t unused[3]; + float rx_delay_base; + uint32_t ble_pin; +}; + +#endif // NODE_PREFS_H \ No newline at end of file diff --git a/examples/companion_radio/UITask.cpp b/examples/companion_radio/UITask.cpp index 04209f2a..bf78927d 100644 --- a/examples/companion_radio/UITask.cpp +++ b/examples/companion_radio/UITask.cpp @@ -1,8 +1,10 @@ #include "UITask.h" #include #include +#include "NodePrefs.h" -#define AUTO_OFF_MILLIS 15000 // 15 seconds +#define AUTO_OFF_MILLIS 15000 // 15 seconds +#define BOOT_SCREEN_MILLIS 4000 // 4 seconds #ifdef PIN_STATUS_LED #define LED_ON_MILLIS 20 @@ -31,11 +33,11 @@ static const uint8_t meshcore_logo [] PROGMEM = { 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, }; -void UITask::begin(DisplayDriver* display, const char* node_name, const char* build_date, const char* firmware_version, uint32_t pin_code) { +void UITask::begin(DisplayDriver* display, NodePrefs* node_prefs, const char* build_date, const char* firmware_version, uint32_t pin_code) { _display = display; _auto_off = millis() + AUTO_OFF_MILLIS; clearMsgPreview(); - _node_name = node_name; + _node_prefs = node_prefs; _pin_code = pin_code; if (_display != NULL) { _display->turnOn(); @@ -83,16 +85,42 @@ void UITask::newMsg(uint8_t path_len, const char* from_name, const char* text, i } } +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 = 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 - 2)) / 100; + _display->fillRect(iconX + 1, iconY + 1, fillWidth, iconHeight - 2); +} + void UITask::renderCurrScreen() { if (_display == NULL) return; // assert() ?? char tmp[80]; - if (_origin[0] && _msg[0]) { + if (_origin[0] && _msg[0]) { // message preview // render message preview _display->setCursor(0, 0); _display->setTextSize(1); _display->setColor(DisplayDriver::GREEN); - _display->print(_node_name); + _display->print(_node_prefs->node_name); _display->setCursor(0, 12); _display->setColor(DisplayDriver::YELLOW); @@ -107,24 +135,41 @@ void UITask::renderCurrScreen() { sprintf(tmp, "%d", _msgcount); _display->print(tmp); _display->setColor(DisplayDriver::YELLOW); // last color will be kept on T114 - } else { - // render 'home' screen + } else if (millis() < BOOT_SCREEN_MILLIS) { // boot screen + // meshcore logo _display->setColor(DisplayDriver::BLUE); - _display->drawXbm(0, 0, meshcore_logo, 128, 13); - _display->setCursor(0, 20); - _display->setTextSize(1); + int logoWidth = 128; + _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); + // version info _display->setColor(DisplayDriver::LIGHT); - _display->print(_node_name); - - _display->setCursor(0, 32); + _display->setTextSize(1); + int textWidth = strlen(_version_info) * 5; // Assuming each character is 5 pixels wide + _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); - if (_connected) { - _display->setColor(DisplayDriver::BLUE); - //_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf); - //_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr); - } else if (_pin_code != 0) { + // battery voltage + renderBatteryIndicator(_display, _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 && _pin_code != 0) { _display->setColor(DisplayDriver::RED); _display->setTextSize(2); _display->setCursor(0, 43); @@ -132,7 +177,7 @@ void UITask::renderCurrScreen() { _display->print(tmp); _display->setColor(DisplayDriver::GREEN); } else { - _display->setColor(DisplayDriver::LIGHT); + _display->setColor(DisplayDriver::LIGHT); } } _need_refresh = false; @@ -204,6 +249,11 @@ void UITask::loop() { userLedHandler(); if (_display != NULL && _display->isOn()) { + static bool _firstBoot = true; + if(_firstBoot && millis() >= BOOT_SCREEN_MILLIS) { + _need_refresh = true; + _firstBoot = false; + } if (millis() >= _next_refresh && _need_refresh) { _display->startFrame(); renderCurrScreen(); diff --git a/examples/companion_radio/UITask.h b/examples/companion_radio/UITask.h index 9050bc42..5edaa8e2 100644 --- a/examples/companion_radio/UITask.h +++ b/examples/companion_radio/UITask.h @@ -4,13 +4,15 @@ #include #include +#include "NodePrefs.h" + class UITask { DisplayDriver* _display; mesh::MainBoard* _board; unsigned long _next_refresh, _auto_off; bool _connected; uint32_t _pin_code; - const char* _node_name; + NodePrefs* _node_prefs; char _version_info[32]; char _origin[62]; char _msg[80]; @@ -27,7 +29,7 @@ public: _next_refresh = 0; _connected = false; } - void begin(DisplayDriver* display, const char* node_name, const char* build_date, const char* firmware_version, uint32_t pin_code); + void begin(DisplayDriver* display, NodePrefs* node_prefs, const char* build_date, const char* firmware_version, uint32_t pin_code); void setHasConnection(bool connected) { _connected = connected; } bool hasDisplay() const { return _display != NULL; } diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index e2817045..3e692b53 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -14,6 +14,7 @@ #include #include #include +#include "NodePrefs.h" #include #include @@ -185,22 +186,6 @@ static uint32_t _atoi(const char* sp) { #define MAX_SIGN_DATA_LEN (8*1024) // 8K -struct NodePrefs { // persisted to file - float airtime_factor; - char node_name[32]; - double node_lat, node_lon; - float freq; - uint8_t sf; - uint8_t cr; - uint8_t reserved1; - uint8_t manual_add_contacts; - float bw; - uint8_t tx_power_dbm; - uint8_t unused[3]; - float rx_delay_base; - uint32_t ble_pin; -}; - class MyMesh : public BaseChatMesh { FILESYSTEM* _fs; IdentityStore* _identity_store; @@ -866,6 +851,9 @@ public: } const char* getNodeName() { return _prefs.node_name; } + NodePrefs* getNodePrefs() { + return &_prefs; + } uint32_t getBLEPin() { return _active_ble_pin; } void startInterface(BaseSerialInterface& serial) { @@ -1605,7 +1593,7 @@ void setup() { #endif #ifdef HAS_UI - ui_task.begin(disp, the_mesh.getNodeName(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION, the_mesh.getBLEPin()); + ui_task.begin(disp, the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION, the_mesh.getBLEPin()); #endif } diff --git a/examples/simple_repeater/UITask.cpp b/examples/simple_repeater/UITask.cpp index 6fff675e..39ca1c81 100644 --- a/examples/simple_repeater/UITask.cpp +++ b/examples/simple_repeater/UITask.cpp @@ -1,7 +1,9 @@ #include "UITask.h" #include +#include -#define AUTO_OFF_MILLIS 20000 // 20 seconds +#define AUTO_OFF_MILLIS 20000 // 20 seconds +#define BOOT_SCREEN_MILLIS 4000 // 4 seconds // 'meshcore', 128x13px static const uint8_t meshcore_logo [] PROGMEM = { @@ -20,10 +22,10 @@ static const uint8_t meshcore_logo [] PROGMEM = { 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, }; -void UITask::begin(const char* node_name, const char* build_date, const char* firmware_version) { +void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version) { _prevBtnState = HIGH; _auto_off = millis() + AUTO_OFF_MILLIS; - _node_name = node_name; + _node_prefs = node_prefs; _display->turnOn(); // strip off dash and commit hash by changing dash to null terminator @@ -39,18 +41,43 @@ void UITask::begin(const char* node_name, const char* build_date, const char* fi } void UITask::renderCurrScreen() { - // render 'home' screen - _display->drawXbm(0, 0, meshcore_logo, 128, 13); - _display->setCursor(0, 20); - _display->setTextSize(1); - _display->print(_node_name); + char tmp[80]; + if (millis() < 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); - _display->setCursor(0, 32); - _display->print(_version_info); - _display->setCursor(0, 43); - _display->print("< Repeater >"); - //_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf); - //_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr); + // version info + _display->setColor(DisplayDriver::LIGHT); + _display->setTextSize(1); + int versionWidth = strlen(_version_info) * 6; + _display->setCursor((_display->width() - versionWidth) / 2, 22); + _display->print(_version_info); + + // node type + const char* node_type = "< Repeater >"; + int nodeTypeWidth = strlen(node_type) * 6; + _display->setCursor((_display->width() - nodeTypeWidth) / 2, 35); + _display->print(node_type); + } else { // home screen + // node name + _display->setCursor(0, 0); + _display->setTextSize(1); + _display->setColor(DisplayDriver::GREEN); + _display->print(_node_prefs->node_name); + + // 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); + } } void UITask::loop() { diff --git a/examples/simple_repeater/UITask.h b/examples/simple_repeater/UITask.h index 13005957..a27259f1 100644 --- a/examples/simple_repeater/UITask.h +++ b/examples/simple_repeater/UITask.h @@ -1,18 +1,19 @@ #pragma once #include +#include class UITask { DisplayDriver* _display; unsigned long _next_read, _next_refresh, _auto_off; int _prevBtnState; - const char* _node_name; + NodePrefs* _node_prefs; char _version_info[32]; void renderCurrScreen(); public: UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; } - void begin(const char* node_name, const char* build_date, const char* firmware_version); + void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version); void loop(); }; \ No newline at end of file diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 1e5a092e..5fdb919a 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -575,6 +575,9 @@ public: const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } const char* getRole() override { return FIRMWARE_ROLE; } const char* getNodeName() { return _prefs.node_name; } + NodePrefs* getNodePrefs() { + return &_prefs; + } void savePrefs() override { _cli.savePrefs(_fs); @@ -751,7 +754,7 @@ void setup() { the_mesh.begin(fs); #ifdef DISPLAY_CLASS - ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); + ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif // send out initial Advertisement to the mesh diff --git a/examples/simple_room_server/UITask.cpp b/examples/simple_room_server/UITask.cpp index eb7be78e..2f559111 100644 --- a/examples/simple_room_server/UITask.cpp +++ b/examples/simple_room_server/UITask.cpp @@ -1,7 +1,9 @@ #include "UITask.h" #include +#include -#define AUTO_OFF_MILLIS 20000 // 20 seconds +#define AUTO_OFF_MILLIS 20000 // 20 seconds +#define BOOT_SCREEN_MILLIS 4000 // 4 seconds // 'meshcore', 128x13px static const uint8_t meshcore_logo [] PROGMEM = { @@ -20,10 +22,10 @@ static const uint8_t meshcore_logo [] PROGMEM = { 0xe3, 0xe3, 0x8f, 0xff, 0x1f, 0xfc, 0x3c, 0x0e, 0x1f, 0xf8, 0xff, 0xf8, 0x70, 0x3c, 0x7f, 0xf8, }; -void UITask::begin(const char* node_name, const char* build_date, const char* firmware_version) { +void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version) { _prevBtnState = HIGH; _auto_off = millis() + AUTO_OFF_MILLIS; - _node_name = node_name; + _node_prefs = node_prefs; _display->turnOn(); // strip off dash and commit hash by changing dash to null terminator @@ -39,18 +41,43 @@ void UITask::begin(const char* node_name, const char* build_date, const char* fi } void UITask::renderCurrScreen() { - // render 'home' screen - _display->drawXbm(0, 0, meshcore_logo, 128, 13); - _display->setCursor(0, 20); - _display->setTextSize(1); - _display->print(_node_name); + char tmp[80]; + if (millis() < 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); - _display->setCursor(0, 32); - _display->print(_version_info); - _display->setCursor(0, 43); - _display->print("< Room Server >"); - //_display->printf("freq : %03.2f sf %d\n", _prefs.freq, _prefs.sf); - //_display->printf("bw : %03.2f cr %d\n", _prefs.bw, _prefs.cr); + // version info + _display->setColor(DisplayDriver::LIGHT); + _display->setTextSize(1); + int versionWidth = strlen(_version_info) * 6; + _display->setCursor((_display->width() - versionWidth) / 2, 22); + _display->print(_version_info); + + // node type + const char* node_type = "< Room Server >"; + int nodeTypeWidth = strlen(node_type) * 6; + _display->setCursor((_display->width() - nodeTypeWidth) / 2, 35); + _display->print(node_type); + } else { // home screen + // node name + _display->setCursor(0, 0); + _display->setTextSize(1); + _display->setColor(DisplayDriver::GREEN); + _display->print(_node_prefs->node_name); + + // 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); + } } void UITask::loop() { diff --git a/examples/simple_room_server/UITask.h b/examples/simple_room_server/UITask.h index 13005957..a27259f1 100644 --- a/examples/simple_room_server/UITask.h +++ b/examples/simple_room_server/UITask.h @@ -1,18 +1,19 @@ #pragma once #include +#include class UITask { DisplayDriver* _display; unsigned long _next_read, _next_refresh, _auto_off; int _prevBtnState; - const char* _node_name; + NodePrefs* _node_prefs; char _version_info[32]; void renderCurrScreen(); public: UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; } - void begin(const char* node_name, const char* build_date, const char* firmware_version); + void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version); void loop(); }; \ No newline at end of file diff --git a/examples/simple_room_server/main.cpp b/examples/simple_room_server/main.cpp index d80b0d4b..a6eafc5d 100644 --- a/examples/simple_room_server/main.cpp +++ b/examples/simple_room_server/main.cpp @@ -721,6 +721,9 @@ public: const char* getBuildDate() override { return FIRMWARE_BUILD_DATE; } const char* getRole() override { return FIRMWARE_ROLE; } const char* getNodeName() { return _prefs.node_name; } + NodePrefs* getNodePrefs() { + return &_prefs; + } void savePrefs() override { _cli.savePrefs(_fs); @@ -918,7 +921,7 @@ void setup() { the_mesh.begin(fs); #ifdef DISPLAY_CLASS - ui_task.begin(the_mesh.getNodeName(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); + ui_task.begin(the_mesh.getNodePrefs(), FIRMWARE_BUILD_DATE, FIRMWARE_VERSION); #endif // send out initial Advertisement to the mesh