BaseUI: Select Environmental Data Source (#11209)

* Select Source

* Fixes

* remove per sensor name

---------

Co-authored-by: Thomas Göttgens <tgoettgens@gmail.com>
This commit is contained in:
HarukiToreda
2026-08-05 09:55:18 +00:00
committed by GitHub
co-authored by Thomas Göttgens
parent b59cd58d75
commit 74b9f6ff02
5 changed files with 245 additions and 20 deletions
+10
View File
@@ -51,6 +51,9 @@ extern NicheGraphics::BaseUIEInkDisplay *setupNicheGraphicsBaseUI();
#include "draw/UIRenderer.h"
#include "graphics/TFTColorRegions.h"
#include "modules/CannedMessageModule.h"
#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "modules/Telemetry/EnvironmentTelemetry.h"
#endif
#include "security/LockdownDisplay.h"
#if !MESHTASTIC_EXCLUDE_GPS
@@ -2333,6 +2336,13 @@ int Screen::handleInputEvent(const InputEvent *event)
menuHandler::textMessageBaseMenu();
}
}
} else if (this->ui->getUiState()->currentFrame < moduleFrames.size()) {
#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
const MeshModule *currentModule = moduleFrames.at(this->ui->getUiState()->currentFrame);
if (environmentTelemetryModule != nullptr && environmentTelemetryModule->ownsFrame(currentModule)) {
menuHandler::environmentTelemetryMenu();
}
#endif
} else if (framesetInfo.positions.firstFavorite != 255 &&
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) {
+79
View File
@@ -26,6 +26,9 @@
#include "modules/CannedMessageModule.h"
#include "modules/ExternalNotificationModule.h"
#include "modules/KeyVerificationModule.h"
#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
#include "modules/Telemetry/EnvironmentTelemetry.h"
#endif
#include "modules/TraceRouteModule.h"
#include <algorithm>
#include <array>
@@ -1471,6 +1474,76 @@ void menuHandler::positionBaseMenu()
screen->showOverlayBanner(bannerOptions);
}
void menuHandler::environmentTelemetryMenu()
{
#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
enum optionsNumbers { Back, Source, enumEnd };
static const char *optionsArray[] = {"Back", "Source"};
static int optionsEnumArray[] = {Back, Source};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Environment";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = enumEnd;
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Source) {
menuQueue = EnvironmentTelemetrySourceMenu;
screen->runNow();
}
};
screen->showOverlayBanner(bannerOptions);
#endif
}
void menuHandler::environmentTelemetrySourceMenu()
{
#if HAS_TELEMETRY && HAS_SENSOR && !MESHTASTIC_EXCLUDE_ENVIRONMENTAL_SENSOR
enum optionsNumbers { Back, LocalSensor, Mesh, FavoritesOnly, enumEnd };
static const char *optionsArray[enumEnd] = {"Back", "Local Sensor", "Mesh", "Favorite Nodes Only"};
static int optionsEnumArray[enumEnd] = {Back, LocalSensor, Mesh, FavoritesOnly};
BannerOverlayOptions bannerOptions;
bannerOptions.message = "Source";
bannerOptions.optionsArrayPtr = optionsArray;
bannerOptions.optionsEnumPtr = optionsEnumArray;
bannerOptions.optionsCount = enumEnd;
switch (EnvironmentTelemetryModule::getDisplaySource()) {
case EnvironmentTelemetryModule::DisplaySource::LocalSensor:
bannerOptions.InitialSelected = LocalSensor;
break;
case EnvironmentTelemetryModule::DisplaySource::FavoriteNodesOnly:
bannerOptions.InitialSelected = FavoritesOnly;
break;
case EnvironmentTelemetryModule::DisplaySource::Mesh:
default:
bannerOptions.InitialSelected = Mesh;
break;
}
bannerOptions.bannerCallback = [](int selected) -> void {
if (selected == Back) {
menuQueue = EnvironmentTelemetryMenu;
screen->runNow();
return;
}
if (selected == LocalSensor) {
EnvironmentTelemetryModule::setDisplaySource(EnvironmentTelemetryModule::DisplaySource::LocalSensor);
} else if (selected == Mesh) {
EnvironmentTelemetryModule::setDisplaySource(EnvironmentTelemetryModule::DisplaySource::Mesh);
} else if (selected == FavoritesOnly) {
EnvironmentTelemetryModule::setDisplaySource(EnvironmentTelemetryModule::DisplaySource::FavoriteNodesOnly);
}
screen->runNow();
};
screen->showOverlayBanner(bannerOptions);
#endif
}
void menuHandler::nodeListMenu()
{
enum optionsNumbers { Back, NodePicker, TraceRoute, Verify, Reset, NodeNameLength, enumEnd };
@@ -2890,6 +2963,12 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
case NumberTest:
numberTest();
break;
case EnvironmentTelemetryMenu:
environmentTelemetryMenu();
break;
case EnvironmentTelemetrySourceMenu:
environmentTelemetrySourceMenu();
break;
case WifiToggleMenu:
wifiToggleMenu();
break;
+4
View File
@@ -38,6 +38,8 @@ class menuHandler
RemoveFavorite,
TestMenu,
NumberTest,
EnvironmentTelemetryMenu,
EnvironmentTelemetrySourceMenu,
WifiToggleMenu,
BluetoothToggleMenu,
ScreenOptionsMenu,
@@ -104,6 +106,8 @@ class menuHandler
static void traceRouteMenu();
static void testMenu();
static void numberTest();
static void environmentTelemetryMenu();
static void environmentTelemetrySourceMenu();
static void wifiBaseMenu();
static void wifiToggleMenu();
static void screenOptionsMenu();
+130 -20
View File
@@ -142,7 +142,115 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
#include "graphics/ScreenFonts.h"
#include <Throttle.h>
EnvironmentTelemetryModule *environmentTelemetryModule = nullptr;
namespace
{
EnvironmentTelemetryModule::DisplaySource gDisplaySource = EnvironmentTelemetryModule::DisplaySource::Mesh;
} // namespace
static constexpr uint16_t TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY = 0x8002;
static constexpr uint32_t LOCAL_DISPLAY_REFRESH_INTERVAL_MS = 1000;
EnvironmentTelemetryModule::DisplaySource EnvironmentTelemetryModule::getDisplaySource()
{
return gDisplaySource;
}
void EnvironmentTelemetryModule::setDisplaySource(DisplaySource source)
{
gDisplaySource = source;
if (environmentTelemetryModule != nullptr) {
environmentTelemetryModule->lastLocalDisplayRefreshMs = 0;
environmentTelemetryModule->refreshDisplayedMeasurement();
}
}
void EnvironmentTelemetryModule::clearMeasurementPacket()
{
if (lastMeasurementPacket != nullptr) {
packetPool.release(lastMeasurementPacket);
lastMeasurementPacket = nullptr;
}
}
bool EnvironmentTelemetryModule::shouldDisplayRemoteNode(NodeNum nodeNum) const
{
if (nodeNum == 0 || nodeNum == nodeDB->getNodeNum()) {
return false;
}
switch (getDisplaySource()) {
case DisplaySource::LocalSensor:
return false;
case DisplaySource::Mesh:
return true;
case DisplaySource::FavoriteNodesOnly:
return nodeDB->isFavorite(nodeNum);
}
return false;
}
bool EnvironmentTelemetryModule::shouldKeepCurrentRemoteDisplay() const
{
if (lastMeasurementPacket == nullptr) {
return false;
}
return shouldDisplayRemoteNode(getFrom(lastMeasurementPacket));
}
bool EnvironmentTelemetryModule::shouldDisplayLocalMeasurement() const
{
switch (getDisplaySource()) {
case DisplaySource::LocalSensor:
return true;
case DisplaySource::Mesh:
return !shouldKeepCurrentRemoteDisplay();
case DisplaySource::FavoriteNodesOnly:
return false;
}
return false;
}
bool EnvironmentTelemetryModule::refreshLocalMeasurementPacket()
{
meshtastic_Telemetry local = meshtastic_Telemetry_init_zero;
if (!getEnvironmentTelemetry(&local)) {
return false;
}
meshtastic_MeshPacket *localPacket = allocDataProtobuf(local);
if (localPacket == nullptr) {
return false;
}
clearMeasurementPacket();
lastMeasurementPacket = packetPool.allocCopy(*localPacket);
packetPool.release(localPacket);
return lastMeasurementPacket != nullptr;
}
void EnvironmentTelemetryModule::refreshDisplayedMeasurement()
{
if (shouldDisplayLocalMeasurement()) {
if (lastMeasurementPacket != nullptr &&
Throttle::isWithinTimespanMs(lastLocalDisplayRefreshMs, LOCAL_DISPLAY_REFRESH_INTERVAL_MS)) {
return;
}
lastLocalDisplayRefreshMs = millis();
if (!refreshLocalMeasurementPacket()) {
clearMeasurementPacket();
}
return;
}
if (!shouldKeepCurrentRemoteDisplay()) {
clearMeasurementPacket();
}
}
void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
{
@@ -299,6 +407,7 @@ int32_t EnvironmentTelemetryModule::runOnce()
result = rak9154Sensor.runOnce();
#endif
#endif
refreshDisplayedMeasurement();
}
// it's possible to have this module enabled, only for displaying values on the screen.
// therefore, we should only enable the sensor loop if measurement is also enabled
@@ -315,6 +424,7 @@ int32_t EnvironmentTelemetryModule::runOnce()
result = delay;
}
}
refreshDisplayedMeasurement();
uint32_t lastTelemetry =
transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY) : 0;
@@ -411,14 +521,17 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
}
// === 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 + ")";
const bool isLocalTelemetry = (getFrom(lastMeasurementPacket) == nodeDB->getNodeNum());
const char *sender = isLocalTelemetry ? "Local Sensor" : getSenderShortName(*lastMeasurementPacket);
String leftStr = String(sender);
if (!isLocalTelemetry) {
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";
leftStr += " (" + agoStr + ")";
}
display->drawString(x, currentY, leftStr); // Left side: who and when
// === Collect sensor readings as label strings (no icons) ===
@@ -463,7 +576,7 @@ void EnvironmentTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiSt
static uint32_t lastAlertTime = 0;
uint32_t now = millis();
bool isOwnTelemetry = lastMeasurementPacket->from == nodeDB->getNodeNum();
bool isOwnTelemetry = isLocalTelemetry;
bool isCooldownOver = (now - lastAlertTime > 60000);
if (isOwnTelemetry && bannerMsg && isCooldownOver) {
@@ -542,15 +655,17 @@ bool EnvironmentTelemetryModule::handleReceivedProtobuf(const meshtastic_MeshPac
LOG_INFO("(Received from %s): radiation=%fµR/h", sender, t->variant.environment_metrics.radiation);
#endif
// release previous packet before occupying a new spot
if (lastMeasurementPacket != nullptr)
packetPool.release(lastMeasurementPacket);
lastMeasurementPacket = packetPool.allocCopy(mp);
// Cache the latest env metrics per node on NodeDB so the phone can
// pull last-known values across reboots and replays.
nodeDB->updateTelemetry(getFrom(&mp), *t, RX_SRC_RADIO);
const NodeNum senderNode = getFrom(&mp);
if (shouldDisplayRemoteNode(senderNode)) {
clearMeasurementPacket();
lastMeasurementPacket = packetPool.allocCopy(mp);
} else if (!shouldKeepCurrentRemoteDisplay()) {
refreshDisplayedMeasurement();
}
}
return false; // Let others look at this message also if they want
@@ -670,11 +785,6 @@ bool EnvironmentTelemetryModule::sendTelemetry(NodeNum dest, bool phoneOnly)
p->priority = meshtastic_MeshPacket_Priority_RELIABLE;
else
p->priority = meshtastic_MeshPacket_Priority_BACKGROUND;
// release previous packet before occupying a new spot
if (lastMeasurementPacket != nullptr)
packetPool.release(lastMeasurementPacket);
lastMeasurementPacket = packetPool.allocCopy(*p);
if (phoneOnly) {
LOG_INFO("Send packet to phone");
service->sendToPhone(p);
@@ -17,6 +17,9 @@
#include <OLEDDisplay.h>
#include <OLEDDisplayUi.h>
class EnvironmentTelemetryModule;
extern EnvironmentTelemetryModule *environmentTelemetryModule;
class EnvironmentTelemetryModule : private concurrency::OSThread,
public ScanI2CConsumer,
public BaseTelemetryModule,
@@ -27,15 +30,26 @@ class EnvironmentTelemetryModule : private concurrency::OSThread,
&EnvironmentTelemetryModule::handleStatusUpdate);
public:
enum class DisplaySource : uint8_t {
LocalSensor = 0,
Mesh = 1,
FavoriteNodesOnly = 2,
};
EnvironmentTelemetryModule()
: concurrency::OSThread("EnvironmentTelemetry"), ScanI2CConsumer(),
ProtobufModule("EnvironmentTelemetry", meshtastic_PortNum_TELEMETRY_APP, &meshtastic_Telemetry_msg)
{
environmentTelemetryModule = this;
(void)getDisplaySource();
lastMeasurementPacket = nullptr;
nodeStatusObserver.observe(&nodeStatus->onNewStatus);
setIntervalFromNow(10 * 1000);
}
virtual bool wantUIFrame() override;
static DisplaySource getDisplaySource();
static void setDisplaySource(DisplaySource source);
bool ownsFrame(const MeshModule *module) const { return module == this; }
#if !HAS_SCREEN
void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
#else
@@ -65,8 +79,16 @@ class EnvironmentTelemetryModule : private concurrency::OSThread,
void i2cScanFinished(ScanI2C *i2cScanner);
private:
void clearMeasurementPacket();
bool refreshLocalMeasurementPacket();
void refreshDisplayedMeasurement();
bool shouldDisplayLocalMeasurement() const;
bool shouldKeepCurrentRemoteDisplay() const;
bool shouldDisplayRemoteNode(NodeNum nodeNum) const;
bool firstTime = 1;
meshtastic_MeshPacket *lastMeasurementPacket;
uint32_t lastLocalDisplayRefreshMs = 0;
uint32_t sendToPhoneIntervalMs = SECONDS_IN_MINUTE * 1000; // Send to phone every minute
uint32_t lastSentToPhone = 0;
};