mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-29 22:20:37 +00:00
Unify the native display config between legacy display and MUI (#6838)
* Add missed include * Another Warning fix * Add another HAS_SCREEN * Namespace fixes * Removed depricated destination types and re-factored destination screen * Get rid of Arduino Strings * Clean up after Copilot * SixthLine Def, Screen Rename Added Sixth Line Definition Screen Rename, and Automatic Line Adjustment * Consistency is hard - fixed "Sixth" * System Frame Updates Adjusted line construction to ensure we fit maximum content per screen. * Fix up notifications * Add a couple more ifdef HAS_SCREEN lines * Add screen->isOverlayBannerShowing() * Don't forget the invert! * Adjust Nodelist Center Divider Adjust Nodelist Center Divider * Fix variable casting * Fix entryText variable as empty before update to fix validation * Altitude is int32_t * Update PowerTelemetry to have correct data type * Fix cppcheck warnings (#6945) * Fix cppcheck warnings * Adjust logic in Power.cpp for power sensor --------- Co-authored-by: Jason P <applewiz@mac.com> * More pixel wrangling so things line up NodeList edition * Adjust NodeList alignments and plumb some background padding for a possible title fix * Better alignment for banner notifications * Move title into drawCommonHeader; initial screen tested * Fonts make spacing items difficult * Improved beeping booping and other buzzer based feedback (#6947) * Improved beeping booping and other buzzer based feedback * audible button feedback (#6949) * Refactor --------- Co-authored-by: todd-herbert <herbert.todd@gmail.com> * Sandpapered the corners of the notification popup * Finalize drawCommonHeader migration * Update Title of Favorite Node Screens * Update node metric alignment on LoRa screen * Update the border for popups to separate it from background * Update PaxcounterModule.cpp with CommonHeader * Update WiFi screen with CommonHeader and related data reflow * It was not, in fact, pointing up * Fix build on wismeshtap * T-deck trackball debounce * Fix uptime on Device Focused page to actually detail * Update Sys screen for new uptime, add label to Freq/Chan on LoRa * Don't display DOP any longer, make Uptime consistent * Revert Uptime change on Favorites, Apply to Device Focused * Label the satelite number to avoid confusion * Boop boop boop boop * Correct GPS positioning and string consistency across strings for GPS * Fix GPS text alignment * Enable canned messages by default * Don't wake screen on new nodes * Cannedmessage list emote support added * Fn+e emote picker for freetext screen * Actually block CannedInput actions while display is shown * Add selection menu to bannerOverlay * Off by one * Move to unified text layouts and spacing * Still my Fav without an "e" * Fully remove EVENT_NODEDB_UPDATED * Simply LoRa screen * Make some char pointers const to fix compilation on native targets * Update drawCompassNorth to include radius * Fix warning * button thread cleanup * Pull OneButton handling from PowerFSM and add MUI switch (#6973) * Trunk * Onebutton Menu Support * Add temporary clock icon * Add gps location to fsi * Banner message state reset * Cast to char to satisfy compiler * Better fast handling of input during banner * Fix warning * Derp * oops * Update ref * Wire buzzer_mode * remove legacy string->print() * Only init screen if one found * Unsigned Char * More buttonThread cleaning * screen.cpp button handling cleanup * The Great Event Rename of 2025 * Fix the Radiomaster * Missed trackball type change * Remove unused function * Make ButtonThread an InputBroker * Coffee hadn't kicked in yet * Add clock icon for Navigation Bar * Restore clock screen definition code - whoops * ExternalNotifications now observe inputBroker * Clock rework (#6992) * Move Clock bits into ClockRenderer space * Rework clock into all device navigation * T-Watch Actually Builds Different * Compile fix --------- Co-authored-by: Jonathan Bennett <jbennett@incomsystems.biz> * Add AM/PM to Digital Clock * Flip Seconds and AM/PM on Clock Display * Tik-tok pixels are hard * Fix builds on Thinknode M1 * Check for GPS and don't crash * Don't endif til the end * Rework the OneButton thread to be much less of a mess. (#6997) * Rework the OneButton thread to be much less of a mess. And break lots of targets temporarily * Update src/input/ButtonThread.h Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * fix GPS toggle * Send the shutdown event, not just the kbchar * Honor the back button in a notificaiton popup * Draw the right size box for popup with options * Try to un-break all the things --------- Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * 24-hour Clock Should have leading zero, but not 12-hour * Fixup some compile errors * Add intRoutine to ButtonThread init, to get more responsive user button back * Add Timezone picker * Fix Warning * Optionally set the initial selection for the chooser popup * Make back buttons work in canned messages * Drop the wrapper classes * LonPressTime now configurable * Clock Frame can not longer be blank; just add valid time * Back buttons everywhere! * Key Verification confirm banner * Make Elecrow M* top button a back button * Add settings saves * EInk responsiveness fixes * Linux Input Fixes * Add Native Trackball/Joystick support, and move UserButton to Input * No Flight Stick Mode * Send input event * Add Channel Utilization to Device Focused frame * Don't shift screens when we draw new ones * Add showOverlayBanner arguments to no-op * trunk * Default Native trackball to NC * Fix crash in simulator mode * Add longLong button press * Get the args right * Adjust Bluetooth Pairing Screen to account for bottom navigation. * Trackball everywhere, and unPhone buttons * Remap visionmaster secondary button to TB_UP * Kill ScanAndSelect * trunk * No longer need the canned messages input filter * All Canned All the time * Fix stm32 compile error regarding inputBroker * Unify tft lineheights (#7033) * Create variable line heights based upon SCREEN_HEIGHT * Refactor textPositions into method -> getTextPositions * Update SharedUIDisplay.h --------- Co-authored-by: Jason P <applewiz@mac.com> * Adjust top distance for larger displays * Adjust icon sizes for larger displays * Fix Paxcounter compile errors after code updates * Pixel wrangling to make larger screens fit better * Alert frame has precedence over banner -- for now * Unify on ALT_BUTTON * Align AM/PM to the digit, not the segment on larger displays * Move some global pin defines into configuration.h * Scaffolding for BMM150 9-axis gyro * Alt button behavior * Don't add the blank GPS frames without HAS_GPS * EVENT_NODEDB_UPDATED has been retired * Clean out LOG_WARN messages from debugging * Add dismiss message function * Minor buttonThread cleanup * Add BMM150 support * Clean up last warning from dev * Simplify bmm150 init return logic * Add option to reply to messages * Add minimal menu upon selecting home screen * Move Messages to slot 2, rename GPS to Position, move variables nearer functional usage in Screen.cpp * Properly dismiss message * T-Deck Trackball press is not user button * Add select on favorite frame to launch cannedMessage DM * Minor wording change * Less capital letters * Fix empty message check, time isn't reliable * drop dead code * Make UIRenderer a static class instead of namespace * Fix the select on favorite * Check if message is empty early and then 'return' * Add kb_found, and show the option to launch freetype if appropriate * Ignore impossible touchscreen touches * Auto scroll fix * Move linebreak after "from" for banners to maximize screen usage. * Center "No messages to show" on Message frame * Start consolidating buzzer behavior * Fixed signed / unsigned warning * Cast second parameter of max() to make some targets happy * Cast kbchar to (char) to make arduino string happy * Shorten the notice of "No messages" * Add buzzer mode chooser * Add regionPicker to Lora icon * Reduce line spacing and reorder Position screen to resolve overlapping issues * Update message titles, fix GPS icons, add Back options * Leftover boops * Remove chirp * Make the region selection dismissable when a region is already set * Add read-aloud functionality on messages w/ esp8266sam * "Last Heard" is a better label * tweak the beep * 5 options * properly tear down freetext upon cancel * de-convelute canned messages just a bit * Correct height of Mail icon in navigation bar * Remove unused warning * Consolidate time methods into TimeFormatters * Oops * Change LoRa Picker Cancel to Back * Tweak selection characters on Banner * Message render not scrolling on 5th line * More fixes for message scrolling * Remove the safety next on text overflow - we found that root cause * Add pin definitions to fix compilation for obscure target * Don't let the touchscreen send unitialized kbchar values * Make virtual KB just a bit quicker * No more double tap, swipe! * Left is left, and Right is right * Update horizontal lightning bolt design * Move from solid to dashed separator for Message Frame * Single emote feature fix * Manually sort overlapping elements for now * Freetext and clearer choices * Fix ESP32 InkHUD builds on the unify-tft branch (#7087) * Remove BaseUI branding * Capitalization is fun * Revert Meshtastic Boot Frame Changes * Add ANZ_433 LoRa region to picker * Update settings.json --------- Co-authored-by: HarukiToreda <116696711+HarukiToreda@users.noreply.github.com> Co-authored-by: Ben Meadors <benmmeadors@gmail.com> Co-authored-by: Jason P <applewiz@mac.com> Co-authored-by: todd-herbert <herbert.todd@gmail.com> Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include("Adafruit_PM25AQI.h")
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR && __has_include("Adafruit_PM25AQI.h")
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "AirQualityTelemetry.h"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#include "configuration.h"
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
#if HAS_TELEMETRY && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
|
||||
|
||||
#include "../mesh/generated/meshtastic/telemetry.pb.h"
|
||||
#include "Default.h"
|
||||
@@ -11,12 +11,15 @@
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "UnitConversions.h"
|
||||
#include "buzz.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "graphics/images.h"
|
||||
#include "main.h"
|
||||
#include "modules/ExternalNotificationModule.h"
|
||||
#include "power.h"
|
||||
#include "sleep.h"
|
||||
#include "target_specific.h"
|
||||
#include <OLEDDisplay.h>
|
||||
#include <OLEDDisplayUi.h>
|
||||
|
||||
#if !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR_EXTERNAL
|
||||
|
||||
@@ -25,6 +28,10 @@
|
||||
#include "Sensor/RCWL9620Sensor.h"
|
||||
#include "Sensor/nullSensor.h"
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr);
|
||||
}
|
||||
#if __has_include(<Adafruit_AHTX0.h>)
|
||||
#include "Sensor/AHT10.h"
|
||||
AHT10Sensor aht10Sensor;
|
||||
@@ -344,120 +351,152 @@ bool EnvironmentTelemetryModule::wantUIFrame()
|
||||
|
||||
void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
// === Setup display ===
|
||||
display->clear();
|
||||
display->setFont(FONT_SMALL);
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
int line = 1;
|
||||
|
||||
if (lastMeasurementPacket == nullptr) {
|
||||
// If there's no valid packet, display "Environment"
|
||||
display->drawString(x, y, "Environment");
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL), "No measurement");
|
||||
// === Set Title
|
||||
const char *titleStr = (SCREEN_WIDTH > 128) ? "Environment" : "Env.";
|
||||
|
||||
// === Header ===
|
||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||
|
||||
// === Row spacing setup ===
|
||||
const int rowHeight = FONT_HEIGHT_SMALL - 4;
|
||||
int currentY = graphics::getTextPositions(display)[line++];
|
||||
|
||||
// === Show "No Telemetry" if no data available ===
|
||||
if (!lastMeasurementPacket) {
|
||||
display->drawString(x, currentY, "No Telemetry");
|
||||
return;
|
||||
}
|
||||
|
||||
// Decode the last measurement packet
|
||||
meshtastic_Telemetry lastMeasurement;
|
||||
uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket);
|
||||
const char *lastSender = getSenderShortName(*lastMeasurementPacket);
|
||||
|
||||
// Decode the telemetry message from the latest received packet
|
||||
const meshtastic_Data &p = lastMeasurementPacket->decoded;
|
||||
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) {
|
||||
display->drawString(x, y, "Measurement Error");
|
||||
LOG_ERROR("Unable to decode last packet");
|
||||
meshtastic_Telemetry telemetry;
|
||||
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &telemetry)) {
|
||||
display->drawString(x, currentY, "No Telemetry");
|
||||
return;
|
||||
}
|
||||
|
||||
// Display "Env. From: ..." on its own
|
||||
display->drawString(x, y, "Env. From: " + String(lastSender) + " (" + String(agoSecs) + "s)");
|
||||
const auto &m = telemetry.variant.environment_metrics;
|
||||
|
||||
// Prepare sensor data strings
|
||||
String sensorData[10];
|
||||
int sensorCount = 0;
|
||||
// Check if any telemetry field has valid data
|
||||
bool hasAny = m.has_temperature || m.has_relative_humidity || m.barometric_pressure != 0 || m.iaq != 0 || m.voltage != 0 ||
|
||||
m.current != 0 || m.lux != 0 || m.white_lux != 0 || m.weight != 0 || m.distance != 0 || m.radiation != 0;
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.has_temperature ||
|
||||
lastMeasurement.variant.environment_metrics.has_relative_humidity) {
|
||||
String last_temp = String(lastMeasurement.variant.environment_metrics.temperature, 0) + "°C";
|
||||
if (moduleConfig.telemetry.environment_display_fahrenheit) {
|
||||
last_temp =
|
||||
String(UnitConversions::CelsiusToFahrenheit(lastMeasurement.variant.environment_metrics.temperature), 0) + "°F";
|
||||
if (!hasAny) {
|
||||
display->drawString(x, currentY, "No Telemetry");
|
||||
return;
|
||||
}
|
||||
|
||||
// === First line: Show sender name + time since received (left), and first metric (right) ===
|
||||
const char *sender = getSenderShortName(*lastMeasurementPacket);
|
||||
uint32_t agoSecs = service->GetTimeSinceMeshPacket(lastMeasurementPacket);
|
||||
String agoStr = (agoSecs > 864000) ? "?"
|
||||
: (agoSecs > 3600) ? String(agoSecs / 3600) + "h"
|
||||
: (agoSecs > 60) ? String(agoSecs / 60) + "m"
|
||||
: String(agoSecs) + "s";
|
||||
|
||||
String leftStr = String(sender) + " (" + agoStr + ")";
|
||||
display->drawString(x, currentY, leftStr); // Left side: who and when
|
||||
|
||||
// === Collect sensor readings as label strings (no icons) ===
|
||||
std::vector<String> entries;
|
||||
|
||||
if (m.has_temperature) {
|
||||
String tempStr = moduleConfig.telemetry.environment_display_fahrenheit
|
||||
? "Tmp: " + String(UnitConversions::CelsiusToFahrenheit(m.temperature), 1) + "°F"
|
||||
: "Tmp: " + String(m.temperature, 1) + "°C";
|
||||
entries.push_back(tempStr);
|
||||
}
|
||||
if (m.has_relative_humidity)
|
||||
entries.push_back("Hum: " + String(m.relative_humidity, 0) + "%");
|
||||
if (m.barometric_pressure != 0)
|
||||
entries.push_back("Prss: " + String(m.barometric_pressure, 0) + " hPa");
|
||||
if (m.iaq != 0) {
|
||||
String aqi = "IAQ: " + String(m.iaq);
|
||||
const char *bannerMsg = nullptr; // Default: no banner
|
||||
|
||||
if (m.iaq <= 25)
|
||||
aqi += " (Excellent)";
|
||||
else if (m.iaq <= 50)
|
||||
aqi += " (Good)";
|
||||
else if (m.iaq <= 100)
|
||||
aqi += " (Moderate)";
|
||||
else if (m.iaq <= 150)
|
||||
aqi += " (Poor)";
|
||||
else if (m.iaq <= 200) {
|
||||
aqi += " (Unhealthy)";
|
||||
bannerMsg = "Unhealthy IAQ";
|
||||
} else if (m.iaq <= 300) {
|
||||
aqi += " (Very Unhealthy)";
|
||||
bannerMsg = "Very Unhealthy IAQ";
|
||||
} else {
|
||||
aqi += " (Hazardous)";
|
||||
bannerMsg = "Hazardous IAQ";
|
||||
}
|
||||
|
||||
sensorData[sensorCount++] =
|
||||
"Temp/Hum: " + last_temp + " / " + String(lastMeasurement.variant.environment_metrics.relative_humidity, 0) + "%";
|
||||
}
|
||||
entries.push_back(aqi);
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.barometric_pressure != 0) {
|
||||
sensorData[sensorCount++] =
|
||||
"Press: " + String(lastMeasurement.variant.environment_metrics.barometric_pressure, 0) + "hPA";
|
||||
}
|
||||
// === IAQ alert logic ===
|
||||
static uint32_t lastAlertTime = 0;
|
||||
uint32_t now = millis();
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.voltage != 0) {
|
||||
sensorData[sensorCount++] = "Volt/Cur: " + String(lastMeasurement.variant.environment_metrics.voltage, 0) + "V / " +
|
||||
String(lastMeasurement.variant.environment_metrics.current, 0) + "mA";
|
||||
}
|
||||
bool isOwnTelemetry = lastMeasurementPacket->from == nodeDB->getNodeNum();
|
||||
bool isCooldownOver = (now - lastAlertTime > 60000);
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.iaq != 0) {
|
||||
sensorData[sensorCount++] = "IAQ: " + String(lastMeasurement.variant.environment_metrics.iaq);
|
||||
}
|
||||
if (isOwnTelemetry && bannerMsg && isCooldownOver) {
|
||||
LOG_INFO("drawFrame: IAQ %d (own) — showing banner: %s", m.iaq, bannerMsg);
|
||||
screen->showOverlayBanner(bannerMsg, 3000);
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.distance != 0) {
|
||||
sensorData[sensorCount++] = "Water Level: " + String(lastMeasurement.variant.environment_metrics.distance, 0) + "mm";
|
||||
}
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.weight != 0) {
|
||||
sensorData[sensorCount++] = "Weight: " + String(lastMeasurement.variant.environment_metrics.weight, 0) + "kg";
|
||||
}
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.radiation != 0) {
|
||||
sensorData[sensorCount++] = "Rad: " + String(lastMeasurement.variant.environment_metrics.radiation, 2) + "µR/h";
|
||||
}
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.lux != 0) {
|
||||
sensorData[sensorCount++] = "Illuminance: " + String(lastMeasurement.variant.environment_metrics.lux, 2) + "lx";
|
||||
}
|
||||
|
||||
if (lastMeasurement.variant.environment_metrics.white_lux != 0) {
|
||||
sensorData[sensorCount++] = "W_Lux: " + String(lastMeasurement.variant.environment_metrics.white_lux, 2) + "lx";
|
||||
}
|
||||
|
||||
static int scrollOffset = 0;
|
||||
static bool scrollingDown = true;
|
||||
static uint32_t lastScrollTime = millis();
|
||||
|
||||
// Determine how many lines we can fit on display
|
||||
// Calculated once only: display dimensions don't change during runtime.
|
||||
static int maxLines = 0;
|
||||
if (!maxLines) {
|
||||
const int16_t paddingTop = _fontHeight(FONT_SMALL); // Heading text
|
||||
const int16_t paddingBottom = 8; // Indicator dots
|
||||
maxLines = (display->getHeight() - paddingTop - paddingBottom) / _fontHeight(FONT_SMALL);
|
||||
assert(maxLines > 0);
|
||||
}
|
||||
|
||||
// Draw as many lines of data as we can fit
|
||||
int linesToShow = min(maxLines, sensorCount);
|
||||
for (int i = 0; i < linesToShow; i++) {
|
||||
int index = (scrollOffset + i) % sensorCount;
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL), sensorData[index]);
|
||||
}
|
||||
|
||||
// Only scroll if there are more than 3 sensor data lines
|
||||
if (sensorCount > 3) {
|
||||
// Update scroll offset every 5 seconds
|
||||
if (millis() - lastScrollTime > 5000) {
|
||||
if (scrollingDown) {
|
||||
scrollOffset++;
|
||||
if (scrollOffset + linesToShow >= sensorCount) {
|
||||
scrollingDown = false;
|
||||
}
|
||||
} else {
|
||||
scrollOffset--;
|
||||
if (scrollOffset <= 0) {
|
||||
scrollingDown = true;
|
||||
}
|
||||
// Only buzz if IAQ is over 200
|
||||
if (m.iaq > 200 && moduleConfig.external_notification.enabled && !externalNotificationModule->getMute()) {
|
||||
playLongBeep();
|
||||
}
|
||||
lastScrollTime = millis();
|
||||
|
||||
lastAlertTime = now;
|
||||
}
|
||||
}
|
||||
if (m.voltage != 0 || m.current != 0)
|
||||
entries.push_back(String(m.voltage, 1) + "V / " + String(m.current, 0) + "mA");
|
||||
if (m.lux != 0)
|
||||
entries.push_back("Light: " + String(m.lux, 0) + "lx");
|
||||
if (m.white_lux != 0)
|
||||
entries.push_back("White: " + String(m.white_lux, 0) + "lx");
|
||||
if (m.weight != 0)
|
||||
entries.push_back("Weight: " + String(m.weight, 0) + "kg");
|
||||
if (m.distance != 0)
|
||||
entries.push_back("Level: " + String(m.distance, 0) + "mm");
|
||||
if (m.radiation != 0)
|
||||
entries.push_back("Rad: " + String(m.radiation, 2) + " µR/h");
|
||||
|
||||
// === Show first available metric on top-right of first line ===
|
||||
if (!entries.empty()) {
|
||||
String valueStr = entries.front();
|
||||
int rightX = SCREEN_WIDTH - display->getStringWidth(valueStr);
|
||||
display->drawString(rightX, currentY, valueStr);
|
||||
entries.erase(entries.begin()); // Remove from queue
|
||||
}
|
||||
|
||||
// === Advance to next line for remaining telemetry entries ===
|
||||
currentY += rowHeight;
|
||||
|
||||
// === Draw remaining entries in 2-column format (left and right) ===
|
||||
for (size_t i = 0; i < entries.size(); i += 2) {
|
||||
// Left column
|
||||
display->drawString(x, currentY, entries[i]);
|
||||
|
||||
// Right column if it exists
|
||||
if (i + 1 < entries.size()) {
|
||||
int rightX = SCREEN_WIDTH / 2;
|
||||
display->drawString(rightX, currentY, entries[i + 1]);
|
||||
}
|
||||
|
||||
currentY += rowHeight;
|
||||
}
|
||||
}
|
||||
|
||||
bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPacket &mp, meshtastic_Telemetry *t)
|
||||
|
||||
@@ -118,22 +118,31 @@ void HealthTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *
|
||||
}
|
||||
|
||||
// Display "Health From: ..." on its own
|
||||
display->drawString(x, y, "Health From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
|
||||
char headerStr[64];
|
||||
snprintf(headerStr, sizeof(headerStr), "Health From: %s(%ds)", lastSender, (int)agoSecs);
|
||||
display->drawString(x, y, headerStr);
|
||||
|
||||
String last_temp = String(lastMeasurement.variant.health_metrics.temperature, 0) + "°C";
|
||||
char last_temp[16];
|
||||
if (moduleConfig.telemetry.environment_display_fahrenheit) {
|
||||
last_temp = String(UnitConversions::CelsiusToFahrenheit(lastMeasurement.variant.health_metrics.temperature), 0) + "°F";
|
||||
snprintf(last_temp, sizeof(last_temp), "%.0f°F",
|
||||
UnitConversions::CelsiusToFahrenheit(lastMeasurement.variant.health_metrics.temperature));
|
||||
} else {
|
||||
snprintf(last_temp, sizeof(last_temp), "%.0f°C", lastMeasurement.variant.health_metrics.temperature);
|
||||
}
|
||||
|
||||
// Continue with the remaining details
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL), "Temp: " + last_temp);
|
||||
char tempStr[32];
|
||||
snprintf(tempStr, sizeof(tempStr), "Temp: %s", last_temp);
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL), tempStr);
|
||||
if (lastMeasurement.variant.health_metrics.has_heart_bpm) {
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Heart Rate: " + String(lastMeasurement.variant.health_metrics.heart_bpm, 0) + " bpm");
|
||||
char heartStr[32];
|
||||
snprintf(heartStr, sizeof(heartStr), "Heart Rate: %.0f bpm", lastMeasurement.variant.health_metrics.heart_bpm);
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL), heartStr);
|
||||
}
|
||||
if (lastMeasurement.variant.health_metrics.has_spO2) {
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"spO2: " + String(lastMeasurement.variant.health_metrics.spO2, 0) + " %");
|
||||
char spo2Str[32];
|
||||
snprintf(spo2Str, sizeof(spo2Str), "spO2: %.0f %%", lastMeasurement.variant.health_metrics.spO2);
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL), spo2Str);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include "PowerTelemetry.h"
|
||||
#include "RTC.h"
|
||||
#include "Router.h"
|
||||
#include "graphics/SharedUIDisplay.h"
|
||||
#include "main.h"
|
||||
#include "power.h"
|
||||
#include "sleep.h"
|
||||
@@ -21,6 +22,11 @@
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
namespace graphics
|
||||
{
|
||||
extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const char *titleStr);
|
||||
}
|
||||
|
||||
int32_t PowerTelemetryModule::runOnce()
|
||||
{
|
||||
if (sleepOnNextExecution == true) {
|
||||
@@ -103,13 +109,20 @@ bool PowerTelemetryModule::wantUIFrame()
|
||||
|
||||
void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
|
||||
{
|
||||
display->clear();
|
||||
display->setTextAlignment(TEXT_ALIGN_LEFT);
|
||||
display->setFont(FONT_SMALL);
|
||||
int line = 1;
|
||||
|
||||
// === Set Title
|
||||
const char *titleStr = (SCREEN_WIDTH > 128) ? "Power Telem." : "Power";
|
||||
|
||||
// === Header ===
|
||||
graphics::drawCommonHeader(display, x, y, titleStr);
|
||||
|
||||
if (lastMeasurementPacket == nullptr) {
|
||||
// In case of no valid packet, display "Power Telemetry", "No measurement"
|
||||
display->drawString(x, y, "Power Telemetry");
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL), "No measurement");
|
||||
// In case of no valid packet, display "Power Telemetry", "No measurement"
|
||||
display->drawString(x, graphics::getTextPositions(display)[line++], "No measurement");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -120,29 +133,35 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s
|
||||
|
||||
const meshtastic_Data &p = lastMeasurementPacket->decoded;
|
||||
if (!pb_decode_from_bytes(p.payload.bytes, p.payload.size, &meshtastic_Telemetry_msg, &lastMeasurement)) {
|
||||
display->drawString(x, y, "Measurement Error");
|
||||
display->drawString(x, graphics::getTextPositions(display)[line++], "Measurement Error");
|
||||
LOG_ERROR("Unable to decode last packet");
|
||||
return;
|
||||
}
|
||||
|
||||
// Display "Pow. From: ..."
|
||||
display->drawString(x, y, "Pow. From: " + String(lastSender) + "(" + String(agoSecs) + "s)");
|
||||
char fromStr[64];
|
||||
snprintf(fromStr, sizeof(fromStr), "Pow. From: %s (%us)", lastSender, agoSecs);
|
||||
display->drawString(x, graphics::getTextPositions(display)[line++], fromStr);
|
||||
|
||||
// Display current and voltage based on ...power_metrics.has_[channel/voltage/current]... flags
|
||||
if (lastMeasurement.variant.power_metrics.has_ch1_voltage || lastMeasurement.variant.power_metrics.has_ch1_current) {
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Ch1: " + String(lastMeasurement.variant.power_metrics.ch1_voltage, 2) + "V " +
|
||||
String(lastMeasurement.variant.power_metrics.ch1_current, 0) + "mA");
|
||||
const auto &m = lastMeasurement.variant.power_metrics;
|
||||
int lineY = textSecondLine;
|
||||
|
||||
auto drawLine = [&](const char *label, float voltage, float current) {
|
||||
char lineStr[64];
|
||||
snprintf(lineStr, sizeof(lineStr), "%s: %.2fV %.0fmA", label, voltage, current);
|
||||
display->drawString(x, lineY, lineStr);
|
||||
lineY += _fontHeight(FONT_SMALL);
|
||||
};
|
||||
|
||||
if (m.has_ch1_voltage || m.has_ch1_current) {
|
||||
drawLine("Ch1", m.ch1_voltage, m.ch1_current);
|
||||
}
|
||||
if (lastMeasurement.variant.power_metrics.has_ch2_voltage || lastMeasurement.variant.power_metrics.has_ch2_current) {
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Ch2: " + String(lastMeasurement.variant.power_metrics.ch2_voltage, 2) + "V " +
|
||||
String(lastMeasurement.variant.power_metrics.ch2_current, 0) + "mA");
|
||||
if (m.has_ch2_voltage || m.has_ch2_current) {
|
||||
drawLine("Ch2", m.ch2_voltage, m.ch2_current);
|
||||
}
|
||||
if (lastMeasurement.variant.power_metrics.has_ch3_voltage || lastMeasurement.variant.power_metrics.has_ch3_current) {
|
||||
display->drawString(x, y += _fontHeight(FONT_SMALL),
|
||||
"Ch3: " + String(lastMeasurement.variant.power_metrics.ch3_voltage, 2) + "V " +
|
||||
String(lastMeasurement.variant.power_metrics.ch3_current, 0) + "mA");
|
||||
if (m.has_ch3_voltage || m.has_ch3_current) {
|
||||
drawLine("Ch3", m.ch3_voltage, m.ch3_current);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,17 +137,17 @@ void BME680Sensor::updateState()
|
||||
#endif
|
||||
}
|
||||
|
||||
void BME680Sensor::checkStatus(String functionName)
|
||||
void BME680Sensor::checkStatus(const char *functionName)
|
||||
{
|
||||
if (bme680.status < BSEC_OK)
|
||||
LOG_ERROR("%s BSEC2 code: %s", functionName.c_str(), String(bme680.status).c_str());
|
||||
LOG_ERROR("%s BSEC2 code: %d", functionName, bme680.status);
|
||||
else if (bme680.status > BSEC_OK)
|
||||
LOG_WARN("%s BSEC2 code: %s", functionName.c_str(), String(bme680.status).c_str());
|
||||
LOG_WARN("%s BSEC2 code: %d", functionName, bme680.status);
|
||||
|
||||
if (bme680.sensor.status < BME68X_OK)
|
||||
LOG_ERROR("%s BME68X code: %s", functionName.c_str(), String(bme680.sensor.status).c_str());
|
||||
LOG_ERROR("%s BME68X code: %d", functionName, bme680.sensor.status);
|
||||
else if (bme680.sensor.status > BME68X_OK)
|
||||
LOG_WARN("%s BME68X code: %s", functionName.c_str(), String(bme680.sensor.status).c_str());
|
||||
LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -34,7 +34,7 @@ class BME680Sensor : public TelemetrySensor
|
||||
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY};
|
||||
void loadState();
|
||||
void updateState();
|
||||
void checkStatus(String functionName);
|
||||
void checkStatus(const char *functionName);
|
||||
|
||||
public:
|
||||
BME680Sensor();
|
||||
|
||||
Reference in New Issue
Block a user