mirror of
https://github.com/alexhopeoconnor/firmware.git
synced 2026-10-04 03:18:10 +10:00
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:
co-authored by
Thomas Göttgens
parent
b59cd58d75
commit
74b9f6ff02
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user