Compare commits
12
Commits
halow
...
Temp_Offset
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89d0221174 | ||
|
|
471017697f | ||
|
|
4f222ce1e8 | ||
|
|
7bf905d86d | ||
|
|
400d2d604d | ||
|
|
eaf15f78de | ||
|
|
4bb2c5634f | ||
|
|
9bd16cf474 | ||
|
|
1961c59a60 | ||
|
|
5fc914cb87 | ||
|
|
5fdbdecd41 | ||
|
|
466408e195 |
@@ -276,6 +276,42 @@ void Screen::showNumberPicker(const char *message, uint32_t durationMs, uint8_t
|
||||
ui->update();
|
||||
}
|
||||
|
||||
void Screen::showSignedDecimalPicker(const char *message, uint32_t durationMs, int initialValueTenths, int minValueTenths,
|
||||
int maxValueTenths, std::function<void(int)> bannerCallback)
|
||||
{
|
||||
#ifdef USE_EINK
|
||||
EINK_ADD_FRAMEFLAG(dispdev, DEMAND_FAST); // Skip full refresh for all overlay menus
|
||||
#endif
|
||||
if (minValueTenths > maxValueTenths) {
|
||||
int temp = minValueTenths;
|
||||
minValueTenths = maxValueTenths;
|
||||
maxValueTenths = temp;
|
||||
}
|
||||
|
||||
if (initialValueTenths < minValueTenths) {
|
||||
initialValueTenths = minValueTenths;
|
||||
} else if (initialValueTenths > maxValueTenths) {
|
||||
initialValueTenths = maxValueTenths;
|
||||
}
|
||||
|
||||
strncpy(NotificationRenderer::alertBannerMessage, message, 255);
|
||||
NotificationRenderer::alertBannerMessage[255] = '\0'; // Ensure null termination
|
||||
NotificationRenderer::alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs;
|
||||
NotificationRenderer::alertBannerCallback = bannerCallback;
|
||||
NotificationRenderer::pauseBanner = false;
|
||||
NotificationRenderer::curSelected = 0;
|
||||
NotificationRenderer::current_notification_type = notificationTypeEnum::signed_decimal_picker;
|
||||
NotificationRenderer::signedDecimalValueTenths = static_cast<int16_t>(initialValueTenths);
|
||||
NotificationRenderer::signedDecimalMinTenths = static_cast<int16_t>(minValueTenths);
|
||||
NotificationRenderer::signedDecimalMaxTenths = static_cast<int16_t>(maxValueTenths);
|
||||
NotificationRenderer::signedDecimalIsNegative = (initialValueTenths < 0);
|
||||
|
||||
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
|
||||
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
|
||||
ui->setTargetFPS(60);
|
||||
ui->update();
|
||||
}
|
||||
|
||||
void Screen::showTextInput(const char *header, const char *initialText, uint32_t durationMs,
|
||||
std::function<void(const std::string &)> textCallback)
|
||||
{
|
||||
@@ -1269,6 +1305,8 @@ void Screen::setFrames(FrameFocus focus)
|
||||
fsi.positions.focusedModule = numframes;
|
||||
if (m && m == waypointModule)
|
||||
fsi.positions.waypoint = numframes;
|
||||
if (m && strcmp(m->getName(), "EnvironmentTelemetry") == 0)
|
||||
fsi.positions.environment = numframes;
|
||||
|
||||
indicatorIcons.push_back(icon_module);
|
||||
numframes++;
|
||||
@@ -1982,6 +2020,8 @@ int Screen::handleInputEvent(const InputEvent *event)
|
||||
menuHandler::textMessageBaseMenu();
|
||||
}
|
||||
}
|
||||
} else if (this->ui->getUiState()->currentFrame == framesetInfo.positions.environment) {
|
||||
menuHandler::environmentTelemetryBaseMenu();
|
||||
} else if (framesetInfo.positions.firstFavorite != 255 &&
|
||||
this->ui->getUiState()->currentFrame >= framesetInfo.positions.firstFavorite &&
|
||||
this->ui->getUiState()->currentFrame <= framesetInfo.positions.lastFavorite) {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#define getStringCenteredX(s) ((SCREEN_WIDTH - display->getStringWidth(s)) / 2)
|
||||
namespace graphics
|
||||
{
|
||||
enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker, text_input };
|
||||
enum notificationTypeEnum { none, text_banner, selection_picker, node_picker, number_picker, signed_decimal_picker, text_input };
|
||||
|
||||
struct BannerOverlayOptions {
|
||||
const char *message;
|
||||
@@ -312,6 +312,8 @@ class Screen : public concurrency::OSThread
|
||||
|
||||
void showNodePicker(const char *message, uint32_t durationMs, std::function<void(uint32_t)> bannerCallback);
|
||||
void showNumberPicker(const char *message, uint32_t durationMs, uint8_t digits, std::function<void(uint32_t)> bannerCallback);
|
||||
void showSignedDecimalPicker(const char *message, uint32_t durationMs, int initialValueTenths, int minValueTenths,
|
||||
int maxValueTenths, std::function<void(int)> bannerCallback);
|
||||
void showTextInput(const char *header, const char *initialText, uint32_t durationMs,
|
||||
std::function<void(const std::string &)> textCallback);
|
||||
|
||||
@@ -707,6 +709,7 @@ class Screen : public concurrency::OSThread
|
||||
uint8_t firstFavorite = 255;
|
||||
uint8_t lastFavorite = 255;
|
||||
uint8_t lora = 255;
|
||||
uint8_t environment = 255;
|
||||
} positions;
|
||||
|
||||
uint8_t frameCount = 0;
|
||||
|
||||
@@ -58,6 +58,32 @@ BannerOverlayOptions createStaticBannerOptions(const char *message, const MenuOp
|
||||
return bannerOptions;
|
||||
}
|
||||
|
||||
constexpr float kTemperatureOffsetMinC = -20.0f;
|
||||
constexpr float kTemperatureOffsetMaxC = 20.0f;
|
||||
constexpr float kTemperatureOffsetDeltaFPerC = 1.8f;
|
||||
|
||||
bool useImperialTemperatureOffsetUnits()
|
||||
{
|
||||
return config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL ||
|
||||
moduleConfig.telemetry.environment_display_fahrenheit;
|
||||
}
|
||||
|
||||
float clampTemperatureOffsetC(float offsetC)
|
||||
{
|
||||
if (offsetC < kTemperatureOffsetMinC) {
|
||||
return kTemperatureOffsetMinC;
|
||||
}
|
||||
if (offsetC > kTemperatureOffsetMaxC) {
|
||||
return kTemperatureOffsetMaxC;
|
||||
}
|
||||
return offsetC;
|
||||
}
|
||||
|
||||
int toTenthsRounded(float value)
|
||||
{
|
||||
return (value >= 0.0f) ? static_cast<int>(value * 10.0f + 0.5f) : static_cast<int>(value * 10.0f - 0.5f);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
menuHandler::screenMenus menuHandler::menuQueue = MenuNone;
|
||||
@@ -1009,6 +1035,52 @@ void menuHandler::textMessageMenu()
|
||||
cannedMessageModule->LaunchWithDestination(NODENUM_BROADCAST);
|
||||
}
|
||||
|
||||
void menuHandler::environmentTelemetryBaseMenu()
|
||||
{
|
||||
enum optionsNumbers { Back, SetTempOffset };
|
||||
|
||||
static const char *optionsArray[] = {"Back", "Set Temp Offset"};
|
||||
static int optionsEnumArray[] = {Back, SetTempOffset};
|
||||
|
||||
BannerOverlayOptions bannerOptions;
|
||||
bannerOptions.message = "Env Actions";
|
||||
bannerOptions.optionsArrayPtr = optionsArray;
|
||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||
bannerOptions.optionsCount = 2;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == SetTempOffset) {
|
||||
menuQueue = EnvironmentTempOffsetPicker;
|
||||
screen->runNow();
|
||||
}
|
||||
};
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::environmentTemperatureOffsetPicker()
|
||||
{
|
||||
static char pickerTitle[40];
|
||||
|
||||
const bool useImperial = useImperialTemperatureOffsetUnits();
|
||||
const char displayUnit = useImperial ? 'F' : 'C';
|
||||
const float currentOffsetC = clampTemperatureOffsetC(moduleConfig.telemetry.environment_temperature_offset_c);
|
||||
const float displayOffset = useImperial ? (currentOffsetC * kTemperatureOffsetDeltaFPerC) : currentOffsetC;
|
||||
const float minDisplay = useImperial ? (kTemperatureOffsetMinC * kTemperatureOffsetDeltaFPerC) : kTemperatureOffsetMinC;
|
||||
const float maxDisplay = useImperial ? (kTemperatureOffsetMaxC * kTemperatureOffsetDeltaFPerC) : kTemperatureOffsetMaxC;
|
||||
|
||||
snprintf(pickerTitle, sizeof(pickerTitle), "Set Temp Offset (%c)", displayUnit);
|
||||
screen->showSignedDecimalPicker(pickerTitle, 60000, toTenthsRounded(displayOffset), toTenthsRounded(minDisplay),
|
||||
toTenthsRounded(maxDisplay), [useImperial](int pickedTenths) -> void {
|
||||
float selectedOffset = static_cast<float>(pickedTenths) / 10.0f;
|
||||
if (useImperial) {
|
||||
selectedOffset /= kTemperatureOffsetDeltaFPerC;
|
||||
}
|
||||
|
||||
moduleConfig.telemetry.environment_temperature_offset_c =
|
||||
clampTemperatureOffsetC(selectedOffset);
|
||||
nodeDB->saveToDisk(SEGMENT_MODULECONFIG);
|
||||
});
|
||||
}
|
||||
|
||||
void menuHandler::textMessageBaseMenu()
|
||||
{
|
||||
enum optionsNumbers { Back, Preset, Freetext, enumEnd };
|
||||
@@ -2218,9 +2290,6 @@ void menuHandler::testMenu()
|
||||
static int optionsEnumArray[5] = {Back};
|
||||
int options = 1;
|
||||
|
||||
optionsArray[options] = "Number Picker";
|
||||
optionsEnumArray[options++] = NumberPicker;
|
||||
|
||||
optionsArray[options] = screen->isFrameHidden("chirpy") ? "Show Chirpy" : "Hide Chirpy";
|
||||
optionsEnumArray[options++] = ShowChirpy;
|
||||
#ifdef HAS_I2S
|
||||
@@ -2234,10 +2303,7 @@ void menuHandler::testMenu()
|
||||
bannerOptions.optionsCount = options;
|
||||
bannerOptions.optionsEnumPtr = optionsEnumArray;
|
||||
bannerOptions.bannerCallback = [](int selected) -> void {
|
||||
if (selected == NumberPicker) {
|
||||
menuQueue = NumberTest;
|
||||
screen->runNow();
|
||||
} else if (selected == ShowChirpy) {
|
||||
if (selected == ShowChirpy) {
|
||||
screen->toggleFrameVisibility("chirpy");
|
||||
screen->setFrames(Screen::FOCUS_SYSTEM);
|
||||
|
||||
@@ -2253,12 +2319,6 @@ void menuHandler::testMenu()
|
||||
screen->showOverlayBanner(bannerOptions);
|
||||
}
|
||||
|
||||
void menuHandler::numberTest()
|
||||
{
|
||||
screen->showNumberPicker("Pick a number\n ", 30000, 4,
|
||||
[](int number_picked) -> void { LOG_WARN("Nodenum: %u", number_picked); });
|
||||
}
|
||||
|
||||
void menuHandler::wifiBaseMenu()
|
||||
{
|
||||
enum optionsNumbers { Back, Wifi_toggle };
|
||||
@@ -2754,8 +2814,8 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display)
|
||||
case TestMenu:
|
||||
testMenu();
|
||||
break;
|
||||
case NumberTest:
|
||||
numberTest();
|
||||
case EnvironmentTempOffsetPicker:
|
||||
environmentTemperatureOffsetPicker();
|
||||
break;
|
||||
case WifiToggleMenu:
|
||||
wifiToggleMenu();
|
||||
@@ -2810,4 +2870,4 @@ void menuHandler::saveUIConfig()
|
||||
|
||||
} // namespace graphics
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
||||
@@ -38,7 +38,7 @@ class menuHandler
|
||||
ManageNodeMenu,
|
||||
RemoveFavorite,
|
||||
TestMenu,
|
||||
NumberTest,
|
||||
EnvironmentTempOffsetPicker,
|
||||
WifiToggleMenu,
|
||||
BluetoothToggleMenu,
|
||||
ScreenOptionsMenu,
|
||||
@@ -78,6 +78,7 @@ class menuHandler
|
||||
static void deleteMessagesMenu();
|
||||
static void homeBaseMenu();
|
||||
static void textMessageBaseMenu();
|
||||
static void environmentTelemetryBaseMenu();
|
||||
static void systemBaseMenu();
|
||||
static void favoriteBaseMenu();
|
||||
static void positionBaseMenu();
|
||||
@@ -101,7 +102,7 @@ class menuHandler
|
||||
static void removeFavoriteMenu();
|
||||
static void traceRouteMenu();
|
||||
static void testMenu();
|
||||
static void numberTest();
|
||||
static void environmentTemperatureOffsetPicker();
|
||||
static void wifiBaseMenu();
|
||||
static void wifiToggleMenu();
|
||||
static void screenOptionsMenu();
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#endif
|
||||
#include "main.h"
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#if HAS_TRACKBALL
|
||||
@@ -52,16 +53,292 @@ bool NotificationRenderer::pauseBanner = false;
|
||||
notificationTypeEnum NotificationRenderer::current_notification_type = notificationTypeEnum::none;
|
||||
uint32_t NotificationRenderer::numDigits = 0;
|
||||
uint32_t NotificationRenderer::currentNumber = 0;
|
||||
int16_t NotificationRenderer::signedDecimalValueTenths = 0;
|
||||
int16_t NotificationRenderer::signedDecimalMinTenths = -999;
|
||||
int16_t NotificationRenderer::signedDecimalMaxTenths = 999;
|
||||
bool NotificationRenderer::signedDecimalIsNegative = false;
|
||||
VirtualKeyboard *NotificationRenderer::virtualKeyboard = nullptr;
|
||||
std::function<void(const std::string &)> NotificationRenderer::textInputCallback = nullptr;
|
||||
|
||||
uint32_t pow_of_10(uint32_t n)
|
||||
struct NumericSlotPickerState {
|
||||
std::vector<uint8_t> digits;
|
||||
bool hasSign = false;
|
||||
bool isNegative = false;
|
||||
uint8_t decimalDigits = 0;
|
||||
int32_t minValue = 0;
|
||||
int32_t maxValue = 0;
|
||||
};
|
||||
|
||||
int32_t maxValueForDigits(uint8_t digitCount)
|
||||
{
|
||||
uint32_t ret = 1;
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
ret *= 10;
|
||||
int64_t value = 0;
|
||||
for (uint8_t i = 0; i < digitCount; i++) {
|
||||
value = (value * 10) + 9;
|
||||
if (value > std::numeric_limits<int32_t>::max()) {
|
||||
return std::numeric_limits<int32_t>::max();
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
return static_cast<int32_t>(value);
|
||||
}
|
||||
|
||||
uint8_t pickerSlotCount(const NumericSlotPickerState &state)
|
||||
{
|
||||
return static_cast<uint8_t>(state.digits.size() + (state.hasSign ? 1 : 0));
|
||||
}
|
||||
|
||||
int32_t clampPickerValue(int32_t value, int32_t minValue, int32_t maxValue)
|
||||
{
|
||||
if (value < minValue) {
|
||||
return minValue;
|
||||
}
|
||||
if (value > maxValue) {
|
||||
return maxValue;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
uint32_t combinePickerDigits(const std::vector<uint8_t> &digits)
|
||||
{
|
||||
uint32_t value = 0;
|
||||
for (const uint8_t digit : digits) {
|
||||
value = (value * 10) + digit;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void splitPickerDigits(uint32_t value, std::vector<uint8_t> &digits)
|
||||
{
|
||||
if (digits.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = static_cast<int>(digits.size()) - 1; i >= 0; i--) {
|
||||
digits[static_cast<size_t>(i)] = static_cast<uint8_t>(value % 10);
|
||||
value /= 10;
|
||||
}
|
||||
}
|
||||
|
||||
int32_t composePickerValue(const NumericSlotPickerState &state)
|
||||
{
|
||||
int64_t value = static_cast<int64_t>(combinePickerDigits(state.digits));
|
||||
if (state.hasSign && state.isNegative && value != 0) {
|
||||
value = -value;
|
||||
}
|
||||
return clampPickerValue(static_cast<int32_t>(value), state.minValue, state.maxValue);
|
||||
}
|
||||
|
||||
void normalizePickerState(NumericSlotPickerState &state)
|
||||
{
|
||||
const bool keepNegativeZero = state.isNegative;
|
||||
const int32_t clampedValue = composePickerValue(state);
|
||||
const uint32_t absValue =
|
||||
(clampedValue < 0) ? static_cast<uint32_t>(-static_cast<int64_t>(clampedValue)) : static_cast<uint32_t>(clampedValue);
|
||||
splitPickerDigits(absValue, state.digits);
|
||||
|
||||
if (!state.hasSign) {
|
||||
state.isNegative = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (clampedValue < 0) {
|
||||
state.isNegative = true;
|
||||
} else if (clampedValue > 0) {
|
||||
state.isNegative = false;
|
||||
} else {
|
||||
// Keep selected sign for zero so users can pick +/- before entering digits.
|
||||
state.isNegative = keepNegativeZero;
|
||||
}
|
||||
}
|
||||
|
||||
bool isPickerIncrementEvent(uint8_t eventType)
|
||||
{
|
||||
return eventType == INPUT_BROKER_UP || eventType == INPUT_BROKER_ALT_PRESS || eventType == INPUT_BROKER_UP_LONG;
|
||||
}
|
||||
|
||||
bool isPickerDecrementEvent(uint8_t eventType)
|
||||
{
|
||||
return eventType == INPUT_BROKER_DOWN || eventType == INPUT_BROKER_USER_PRESS || eventType == INPUT_BROKER_DOWN_LONG;
|
||||
}
|
||||
|
||||
void applyPickerDelta(NumericSlotPickerState &state, int8_t selectedSlot, int8_t delta)
|
||||
{
|
||||
const uint8_t slotCount = pickerSlotCount(state);
|
||||
if (selectedSlot < 0 || selectedSlot >= static_cast<int8_t>(slotCount)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.hasSign && selectedSlot == 0) {
|
||||
state.isNegative = !state.isNegative;
|
||||
normalizePickerState(state);
|
||||
return;
|
||||
}
|
||||
|
||||
const int8_t digitOffset = state.hasSign ? 1 : 0;
|
||||
const int8_t digitIndex = selectedSlot - digitOffset;
|
||||
if (digitIndex < 0 || digitIndex >= static_cast<int8_t>(state.digits.size())) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t &digit = state.digits[static_cast<size_t>(digitIndex)];
|
||||
if (delta > 0) {
|
||||
digit = static_cast<uint8_t>((digit + 1) % 10);
|
||||
} else {
|
||||
digit = static_cast<uint8_t>((digit == 0) ? 9 : (digit - 1));
|
||||
}
|
||||
normalizePickerState(state);
|
||||
}
|
||||
|
||||
bool applyPickerKeypress(NumericSlotPickerState &state, int8_t selectedSlot, char key)
|
||||
{
|
||||
const uint8_t slotCount = pickerSlotCount(state);
|
||||
if (selectedSlot < 0 || selectedSlot >= static_cast<int8_t>(slotCount)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state.hasSign && selectedSlot == 0 && (key == '+' || key == '-')) {
|
||||
state.isNegative = (key == '-');
|
||||
normalizePickerState(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (key < '0' || key > '9') {
|
||||
return false;
|
||||
}
|
||||
|
||||
const int8_t digitOffset = state.hasSign ? 1 : 0;
|
||||
const int8_t digitIndex = selectedSlot - digitOffset;
|
||||
if (digitIndex < 0 || digitIndex >= static_cast<int8_t>(state.digits.size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
state.digits[static_cast<size_t>(digitIndex)] = static_cast<uint8_t>(key - '0');
|
||||
normalizePickerState(state);
|
||||
return true;
|
||||
}
|
||||
|
||||
void parseBannerLines(const char *lineStarts[MAX_LINES + 1], uint16_t &lineCount)
|
||||
{
|
||||
lineCount = 0;
|
||||
char *alertEnd = NotificationRenderer::alertBannerMessage +
|
||||
strnlen(NotificationRenderer::alertBannerMessage, sizeof(NotificationRenderer::alertBannerMessage));
|
||||
lineStarts[lineCount] = NotificationRenderer::alertBannerMessage;
|
||||
while ((lineCount < MAX_LINES) && (lineStarts[lineCount] < alertEnd)) {
|
||||
lineStarts[lineCount + 1] = std::find((char *)lineStarts[lineCount], alertEnd, '\n');
|
||||
if (lineStarts[lineCount + 1][0] == '\n') {
|
||||
lineStarts[lineCount + 1] += 1;
|
||||
}
|
||||
lineCount++;
|
||||
}
|
||||
}
|
||||
|
||||
void buildNumericPickerDisplay(const NumericSlotPickerState &state, std::string &formattedValue,
|
||||
std::vector<int16_t> &slotCharIndexBySlot)
|
||||
{
|
||||
const uint8_t slotCount = pickerSlotCount(state);
|
||||
slotCharIndexBySlot.assign(slotCount, -1);
|
||||
|
||||
formattedValue = " ";
|
||||
formattedValue.reserve(24);
|
||||
int8_t slot = 0;
|
||||
|
||||
if (state.hasSign) {
|
||||
slotCharIndexBySlot[slot++] = static_cast<int16_t>(formattedValue.size());
|
||||
formattedValue += state.isNegative ? '-' : '+';
|
||||
formattedValue += ' ';
|
||||
}
|
||||
|
||||
const size_t digitCount = state.digits.size();
|
||||
const size_t decimalBreak =
|
||||
(state.decimalDigits > 0 && state.decimalDigits < digitCount) ? (digitCount - state.decimalDigits) : digitCount;
|
||||
for (size_t i = 0; i < digitCount; i++) {
|
||||
slotCharIndexBySlot[slot++] = static_cast<int16_t>(formattedValue.size());
|
||||
formattedValue += static_cast<char>('0' + state.digits[i]);
|
||||
formattedValue += ' ';
|
||||
|
||||
if (state.decimalDigits > 0 && i + 1 == decimalBreak) {
|
||||
formattedValue += '.';
|
||||
formattedValue += ' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Number picker renderer
|
||||
// Reuse by building `formattedValue` as displayed, and mapping each editable slot
|
||||
// to its character index in that string via `slotCharIndexBySlot`.
|
||||
void NumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lineStarts[MAX_LINES + 1], uint16_t lineCount,
|
||||
const std::string &formattedValue, const int16_t *slotCharIndexBySlot, uint8_t slotCount, int8_t selectedSlot)
|
||||
{
|
||||
if (formattedValue.empty() || slotCharIndexBySlot == nullptr || slotCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string spacer(formattedValue.size(), ' ');
|
||||
uint16_t totalLines = lineCount + 3;
|
||||
const char *linePointers[totalLines + 1] = {0};
|
||||
for (uint16_t i = 0; i < lineCount; i++) {
|
||||
linePointers[i] = lineStarts[i];
|
||||
}
|
||||
const uint16_t topGuideLineIndex = lineCount;
|
||||
linePointers[lineCount++] = spacer.c_str();
|
||||
const uint16_t valueLineIndex = lineCount;
|
||||
linePointers[lineCount++] = formattedValue.c_str();
|
||||
const uint16_t bottomGuideLineIndex = lineCount;
|
||||
linePointers[lineCount++] = spacer.c_str();
|
||||
|
||||
NotificationRenderer::drawNotificationBox(display, state, linePointers, totalLines, 0);
|
||||
|
||||
constexpr uint16_t hPadding = 5;
|
||||
constexpr uint16_t vPadding = 2;
|
||||
uint16_t maxWidth = 0;
|
||||
uint16_t lineWidths[MAX_LINES + 3] = {0};
|
||||
for (uint16_t i = 0; i < totalLines; i++) {
|
||||
const uint16_t lineLength = static_cast<uint16_t>(strlen(linePointers[i]));
|
||||
lineWidths[i] = display->getStringWidth(linePointers[i], lineLength, true);
|
||||
if (lineWidths[i] > maxWidth) {
|
||||
maxWidth = lineWidths[i];
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t boxWidth = hPadding * 2 + maxWidth;
|
||||
uint16_t screenHeight = display->height();
|
||||
uint8_t effectiveLineHeight = FONT_HEIGHT_SMALL - 3;
|
||||
uint8_t visibleTotalLines = std::min<uint8_t>(totalLines, (screenHeight - vPadding * 2) / effectiveLineHeight);
|
||||
uint16_t contentHeight = visibleTotalLines * effectiveLineHeight;
|
||||
uint16_t boxHeight = contentHeight + vPadding * 2;
|
||||
if (visibleTotalLines == 1) {
|
||||
boxHeight += (currentResolution == ScreenResolution::High) ? 4 : 3;
|
||||
}
|
||||
int16_t boxLeft = (display->width() / 2) - (boxWidth / 2);
|
||||
if (totalLines > visibleTotalLines) {
|
||||
boxWidth += (currentResolution == ScreenResolution::High) ? 4 : 2;
|
||||
}
|
||||
int16_t boxTop = (display->height() / 2) - (boxHeight / 2);
|
||||
|
||||
const int selectedSlotClamped = std::max<int>(0, std::min<int>(selectedSlot, static_cast<int>(slotCount) - 1));
|
||||
const int selectedCharIndex = slotCharIndexBySlot[selectedSlotClamped];
|
||||
if (selectedCharIndex < 0 || selectedCharIndex >= static_cast<int>(formattedValue.size())) {
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t valueTextX = boxLeft + (boxWidth - lineWidths[valueLineIndex]) / 2;
|
||||
const uint16_t prefixWidth = display->getStringWidth(formattedValue.c_str(), selectedCharIndex, true);
|
||||
const uint16_t slotCharWidth = display->getStringWidth(formattedValue.c_str() + selectedCharIndex, 1, true);
|
||||
const int16_t slotCenterX = valueTextX + static_cast<int16_t>(prefixWidth + (slotCharWidth / 2));
|
||||
|
||||
int16_t topGuideY = boxTop + vPadding + (topGuideLineIndex * effectiveLineHeight);
|
||||
int16_t bottomGuideY = boxTop + vPadding + (bottomGuideLineIndex * effectiveLineHeight);
|
||||
const int16_t triHalfWidth = (currentResolution == ScreenResolution::High) ? 3 : 2;
|
||||
const int16_t guideInsetY = 1;
|
||||
const int16_t triHeight = std::max<int16_t>(2, static_cast<int16_t>((effectiveLineHeight - (guideInsetY * 2) - 1) / 2));
|
||||
const int16_t topBaseY = topGuideY + effectiveLineHeight - guideInsetY - 1;
|
||||
const int16_t topApexY = topBaseY - triHeight;
|
||||
const int16_t bottomBaseY = bottomGuideY + guideInsetY;
|
||||
const int16_t bottomApexY = bottomBaseY + triHeight;
|
||||
|
||||
display->setColor(WHITE);
|
||||
display->fillTriangle(slotCenterX, topApexY, slotCenterX - triHalfWidth, topBaseY, slotCenterX + triHalfWidth, topBaseY);
|
||||
display->fillTriangle(slotCenterX - triHalfWidth, bottomBaseY, slotCenterX + triHalfWidth, bottomBaseY, slotCenterX,
|
||||
bottomApexY);
|
||||
}
|
||||
|
||||
// Used on boot when a certificate is being created
|
||||
@@ -103,6 +380,10 @@ void NotificationRenderer::resetBanner()
|
||||
pauseBanner = false;
|
||||
numDigits = 0;
|
||||
currentNumber = 0;
|
||||
signedDecimalValueTenths = 0;
|
||||
signedDecimalMinTenths = -999;
|
||||
signedDecimalMaxTenths = 999;
|
||||
signedDecimalIsNegative = false;
|
||||
|
||||
nodeDB->pause_sort(false);
|
||||
|
||||
@@ -153,6 +434,9 @@ void NotificationRenderer::drawBannercallback(OLEDDisplay *display, OLEDDisplayU
|
||||
case notificationTypeEnum::number_picker:
|
||||
drawNumberPicker(display, state);
|
||||
break;
|
||||
case notificationTypeEnum::signed_decimal_picker:
|
||||
drawSignedDecimalPicker(display, state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,83 +444,132 @@ void NotificationRenderer::drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiS
|
||||
{
|
||||
const char *lineStarts[MAX_LINES + 1] = {0};
|
||||
uint16_t lineCount = 0;
|
||||
parseBannerLines(lineStarts, lineCount);
|
||||
|
||||
// Parse lines
|
||||
char *alertEnd = alertBannerMessage + strnlen(alertBannerMessage, sizeof(alertBannerMessage));
|
||||
lineStarts[lineCount] = alertBannerMessage;
|
||||
NumericSlotPickerState pickerState;
|
||||
pickerState.hasSign = false;
|
||||
pickerState.decimalDigits = 0;
|
||||
pickerState.minValue = 0;
|
||||
pickerState.maxValue = maxValueForDigits(static_cast<uint8_t>(numDigits));
|
||||
pickerState.digits.assign(numDigits, 0);
|
||||
splitPickerDigits(currentNumber, pickerState.digits);
|
||||
normalizePickerState(pickerState);
|
||||
|
||||
// Find lines
|
||||
while ((lineCount < MAX_LINES) && (lineStarts[lineCount] < alertEnd)) {
|
||||
lineStarts[lineCount + 1] = std::find((char *)lineStarts[lineCount], alertEnd, '\n');
|
||||
if (lineStarts[lineCount + 1][0] == '\n')
|
||||
lineStarts[lineCount + 1] += 1;
|
||||
lineCount++;
|
||||
const uint8_t slotCount = pickerSlotCount(pickerState);
|
||||
if (curSelected < 0) {
|
||||
curSelected = 0;
|
||||
} else if (curSelected > static_cast<int8_t>(slotCount)) {
|
||||
curSelected = static_cast<int8_t>(slotCount);
|
||||
}
|
||||
// modulo to extract
|
||||
uint8_t this_digit = (currentNumber % (pow_of_10(numDigits - curSelected))) / (pow_of_10(numDigits - curSelected - 1));
|
||||
// Handle input
|
||||
if (inEvent.inputEvent == INPUT_BROKER_UP || inEvent.inputEvent == INPUT_BROKER_ALT_PRESS ||
|
||||
inEvent.inputEvent == INPUT_BROKER_UP_LONG) {
|
||||
if (this_digit == 9) {
|
||||
currentNumber -= 9 * (pow_of_10(numDigits - curSelected - 1));
|
||||
} else {
|
||||
currentNumber += (pow_of_10(numDigits - curSelected - 1));
|
||||
}
|
||||
} else if (inEvent.inputEvent == INPUT_BROKER_DOWN || inEvent.inputEvent == INPUT_BROKER_USER_PRESS ||
|
||||
inEvent.inputEvent == INPUT_BROKER_DOWN_LONG) {
|
||||
if (this_digit == 0) {
|
||||
currentNumber += 9 * (pow_of_10(numDigits - curSelected - 1));
|
||||
} else {
|
||||
currentNumber -= (pow_of_10(numDigits - curSelected - 1));
|
||||
}
|
||||
|
||||
if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
|
||||
resetBanner();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPickerIncrementEvent(inEvent.inputEvent)) {
|
||||
applyPickerDelta(pickerState, curSelected, 1);
|
||||
} else if (isPickerDecrementEvent(inEvent.inputEvent)) {
|
||||
applyPickerDelta(pickerState, curSelected, -1);
|
||||
} else if (inEvent.inputEvent == INPUT_BROKER_ANYKEY) {
|
||||
if (inEvent.kbchar > 47 && inEvent.kbchar < 58) { // have a digit
|
||||
currentNumber -= this_digit * (pow_of_10(numDigits - curSelected - 1));
|
||||
currentNumber += (inEvent.kbchar - 48) * (pow_of_10(numDigits - curSelected - 1));
|
||||
if (applyPickerKeypress(pickerState, curSelected, inEvent.kbchar)) {
|
||||
curSelected++;
|
||||
}
|
||||
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_RIGHT) {
|
||||
curSelected++;
|
||||
} else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
|
||||
curSelected--;
|
||||
} else if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) &&
|
||||
alertBannerUntil != 0) {
|
||||
resetBanner();
|
||||
return;
|
||||
curSelected = std::max<int8_t>(0, curSelected - 1);
|
||||
}
|
||||
if (curSelected == static_cast<int8_t>(numDigits)) {
|
||||
|
||||
currentNumber = static_cast<uint32_t>(std::max<int32_t>(0, composePickerValue(pickerState)));
|
||||
if (curSelected == static_cast<int8_t>(slotCount)) {
|
||||
alertBannerCallback(currentNumber);
|
||||
resetBanner();
|
||||
return;
|
||||
}
|
||||
|
||||
inEvent.inputEvent = INPUT_BROKER_NONE;
|
||||
if (alertBannerMessage[0] == '\0')
|
||||
if (alertBannerMessage[0] == '\0') {
|
||||
return;
|
||||
|
||||
uint16_t totalLines = lineCount + 2;
|
||||
const char *linePointers[totalLines + 1] = {0}; // this is sort of a dynamic allocation
|
||||
|
||||
// copy the linestarts to display to the linePointers holder
|
||||
for (uint16_t i = 0; i < lineCount; i++) {
|
||||
linePointers[i] = lineStarts[i];
|
||||
}
|
||||
std::string digits = " ";
|
||||
std::string arrowPointer = " ";
|
||||
for (uint16_t i = 0; i < numDigits; i++) {
|
||||
// Modulo minus modulo to return just the current number
|
||||
digits += std::to_string((currentNumber % (pow_of_10(numDigits - i))) / (pow_of_10(numDigits - i - 1))) + " ";
|
||||
if (curSelected == i) {
|
||||
arrowPointer += "^ ";
|
||||
} else {
|
||||
arrowPointer += "_ ";
|
||||
|
||||
std::string formattedValue;
|
||||
std::vector<int16_t> slotCharIndexBySlot;
|
||||
buildNumericPickerDisplay(pickerState, formattedValue, slotCharIndexBySlot);
|
||||
|
||||
NumberPicker(display, state, lineStarts, lineCount, formattedValue, slotCharIndexBySlot.data(), slotCount, curSelected);
|
||||
}
|
||||
|
||||
void NotificationRenderer::drawSignedDecimalPicker(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||
{
|
||||
const char *lineStarts[MAX_LINES + 1] = {0};
|
||||
uint16_t lineCount = 0;
|
||||
parseBannerLines(lineStarts, lineCount);
|
||||
|
||||
NumericSlotPickerState pickerState;
|
||||
pickerState.hasSign = true;
|
||||
pickerState.decimalDigits = 1;
|
||||
pickerState.minValue = signedDecimalMinTenths;
|
||||
pickerState.maxValue = signedDecimalMaxTenths;
|
||||
pickerState.digits.assign(3, 0); // XX.X format
|
||||
|
||||
const int32_t currentValue = static_cast<int32_t>(signedDecimalValueTenths);
|
||||
if (currentValue < 0) {
|
||||
pickerState.isNegative = true;
|
||||
} else if (currentValue > 0) {
|
||||
pickerState.isNegative = false;
|
||||
} else {
|
||||
pickerState.isNegative = signedDecimalIsNegative;
|
||||
}
|
||||
const uint32_t absValue =
|
||||
(currentValue < 0) ? static_cast<uint32_t>(-static_cast<int64_t>(currentValue)) : static_cast<uint32_t>(currentValue);
|
||||
splitPickerDigits(absValue, pickerState.digits);
|
||||
normalizePickerState(pickerState);
|
||||
|
||||
const uint8_t slotCount = pickerSlotCount(pickerState);
|
||||
if (curSelected < 0) {
|
||||
curSelected = 0;
|
||||
} else if (curSelected > static_cast<int8_t>(slotCount)) {
|
||||
curSelected = static_cast<int8_t>(slotCount);
|
||||
}
|
||||
|
||||
if ((inEvent.inputEvent == INPUT_BROKER_CANCEL || inEvent.inputEvent == INPUT_BROKER_ALT_LONG) && alertBannerUntil != 0) {
|
||||
resetBanner();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isPickerIncrementEvent(inEvent.inputEvent)) {
|
||||
applyPickerDelta(pickerState, curSelected, 1);
|
||||
} else if (isPickerDecrementEvent(inEvent.inputEvent)) {
|
||||
applyPickerDelta(pickerState, curSelected, -1);
|
||||
} else if (inEvent.inputEvent == INPUT_BROKER_ANYKEY) {
|
||||
if (applyPickerKeypress(pickerState, curSelected, inEvent.kbchar)) {
|
||||
curSelected++;
|
||||
}
|
||||
} else if (inEvent.inputEvent == INPUT_BROKER_SELECT || inEvent.inputEvent == INPUT_BROKER_RIGHT) {
|
||||
curSelected++;
|
||||
} else if (inEvent.inputEvent == INPUT_BROKER_LEFT) {
|
||||
curSelected = std::max<int8_t>(0, curSelected - 1);
|
||||
}
|
||||
|
||||
linePointers[lineCount++] = digits.c_str();
|
||||
linePointers[lineCount++] = arrowPointer.c_str();
|
||||
signedDecimalValueTenths = static_cast<int16_t>(composePickerValue(pickerState));
|
||||
signedDecimalIsNegative = pickerState.isNegative;
|
||||
if (curSelected == static_cast<int8_t>(slotCount)) {
|
||||
alertBannerCallback(signedDecimalValueTenths);
|
||||
resetBanner();
|
||||
return;
|
||||
}
|
||||
|
||||
drawNotificationBox(display, state, linePointers, totalLines, 0);
|
||||
inEvent.inputEvent = INPUT_BROKER_NONE;
|
||||
if (alertBannerMessage[0] == '\0') {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string formattedValue;
|
||||
std::vector<int16_t> slotCharIndexBySlot;
|
||||
buildNumericPickerDisplay(pickerState, formattedValue, slotCharIndexBySlot);
|
||||
|
||||
NumberPicker(display, state, lineStarts, lineCount, formattedValue, slotCharIndexBySlot.data(), slotCount, curSelected);
|
||||
}
|
||||
|
||||
void NotificationRenderer::drawNodePicker(OLEDDisplay *display, OLEDDisplayUiState *state)
|
||||
@@ -622,7 +955,9 @@ void NotificationRenderer::drawNotificationBox(OLEDDisplay *display, OLEDDisplay
|
||||
strncpy(lineBuffer, lines[i], lineLengths[i]);
|
||||
lineBuffer[lineLengths[i]] = '\0';
|
||||
// Determine if this is a pop-up or a pick list
|
||||
if (alertBannerOptions > 0 && i == 0) {
|
||||
const bool highlightTitleRow =
|
||||
(i == 0) && (alertBannerOptions > 0 || current_notification_type == notificationTypeEnum::signed_decimal_picker);
|
||||
if (highlightTitleRow) {
|
||||
// Pick List
|
||||
display->setColor(WHITE);
|
||||
int background_yOffset = 1;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "graphics/Screen.h"
|
||||
#include "graphics/VirtualKeyboard.h"
|
||||
#include "modules/OnScreenKeyboardModule.h"
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#define MAX_LINES 5
|
||||
@@ -26,6 +27,10 @@ class NotificationRenderer
|
||||
static std::function<void(int)> alertBannerCallback;
|
||||
static uint32_t numDigits;
|
||||
static uint32_t currentNumber;
|
||||
static int16_t signedDecimalValueTenths;
|
||||
static int16_t signedDecimalMinTenths;
|
||||
static int16_t signedDecimalMaxTenths;
|
||||
static bool signedDecimalIsNegative;
|
||||
static VirtualKeyboard *virtualKeyboard;
|
||||
static std::function<void(const std::string &)> textInputCallback;
|
||||
|
||||
@@ -36,6 +41,7 @@ class NotificationRenderer
|
||||
static void drawBannercallback(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||
static void drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||
static void drawNumberPicker(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||
static void drawSignedDecimalPicker(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||
static void drawNodePicker(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||
static void drawTextInput(OLEDDisplay *display, OLEDDisplayUiState *state);
|
||||
static void drawNotificationBox(OLEDDisplay *display, OLEDDisplayUiState *state, const char *lines[MAX_LINES + 1],
|
||||
|
||||
@@ -81,6 +81,7 @@ class MeshModule
|
||||
static AdminMessageHandleResult handleAdminMessageForAllModules(const meshtastic_MeshPacket &mp,
|
||||
meshtastic_AdminMessage *request,
|
||||
meshtastic_AdminMessage *response);
|
||||
const char *getName() const { return name; }
|
||||
#if HAS_SCREEN
|
||||
virtual void drawFrame(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y) { return; }
|
||||
virtual bool isRequestingFocus(); // Checked by screen, when regenerating frameset
|
||||
|
||||
@@ -398,6 +398,8 @@ typedef struct _meshtastic_ModuleConfig_TelemetryConfig {
|
||||
bool device_telemetry_enabled;
|
||||
/* Enable/Disable the air quality telemetry measurement module on-device display */
|
||||
bool air_quality_screen_enabled;
|
||||
/* Temperature offset in Celsius applied to local environment telemetry before it is sent. */
|
||||
float environment_temperature_offset_c;
|
||||
} meshtastic_ModuleConfig_TelemetryConfig;
|
||||
|
||||
/* Canned Messages Module Config */
|
||||
@@ -593,7 +595,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_StoreForwardConfig_init_default {0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_RangeTestConfig_init_default {0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_init_default {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_init_default {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_default {0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_init_default {""}
|
||||
@@ -612,7 +614,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_ExternalNotificationConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_StoreForwardConfig_init_zero {0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_RangeTestConfig_init_zero {0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_init_zero {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_init_zero {0, 0, 0, 0, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, _meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_MIN, 0, 0, "", 0}
|
||||
#define meshtastic_ModuleConfig_AmbientLightingConfig_init_zero {0, 0, 0, 0, 0}
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_init_zero {""}
|
||||
@@ -718,6 +720,7 @@ extern "C" {
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_health_screen_enabled_tag 13
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_device_telemetry_enabled_tag 14
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_air_quality_screen_enabled_tag 15
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_environment_temperature_offset_c_tag 16
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_rotary1_enabled_tag 1
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_a_tag 2
|
||||
#define meshtastic_ModuleConfig_CannedMessageConfig_inputbroker_pin_b_tag 3
|
||||
@@ -948,7 +951,8 @@ X(a, STATIC, SINGULAR, BOOL, health_measurement_enabled, 11) \
|
||||
X(a, STATIC, SINGULAR, UINT32, health_update_interval, 12) \
|
||||
X(a, STATIC, SINGULAR, BOOL, health_screen_enabled, 13) \
|
||||
X(a, STATIC, SINGULAR, BOOL, device_telemetry_enabled, 14) \
|
||||
X(a, STATIC, SINGULAR, BOOL, air_quality_screen_enabled, 15)
|
||||
X(a, STATIC, SINGULAR, BOOL, air_quality_screen_enabled, 15) \
|
||||
X(a, STATIC, SINGULAR, FLOAT, environment_temperature_offset_c, 16)
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_CALLBACK NULL
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_DEFAULT NULL
|
||||
|
||||
@@ -1051,8 +1055,8 @@ extern const pb_msgdesc_t meshtastic_RemoteHardwarePin_msg;
|
||||
#define meshtastic_ModuleConfig_SerialConfig_size 28
|
||||
#define meshtastic_ModuleConfig_StatusMessageConfig_size 81
|
||||
#define meshtastic_ModuleConfig_StoreForwardConfig_size 24
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_size 55
|
||||
#define meshtastic_ModuleConfig_TAKConfig_size 4
|
||||
#define meshtastic_ModuleConfig_TelemetryConfig_size 50
|
||||
#define meshtastic_ModuleConfig_TrafficManagementConfig_size 52
|
||||
#define meshtastic_ModuleConfig_size 227
|
||||
#define meshtastic_RemoteHardwarePin_size 21
|
||||
|
||||
@@ -138,6 +138,32 @@ extern void drawCommonHeader(OLEDDisplay *display, int16_t x, int16_t y, const c
|
||||
#include "graphics/ScreenFonts.h"
|
||||
#include <Throttle.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr float TEMPERATURE_OFFSET_MIN_C = -20.0f;
|
||||
constexpr float TEMPERATURE_OFFSET_MAX_C = 20.0f;
|
||||
|
||||
float clampTemperatureOffsetC(float offsetC)
|
||||
{
|
||||
if (offsetC < TEMPERATURE_OFFSET_MIN_C)
|
||||
return TEMPERATURE_OFFSET_MIN_C;
|
||||
if (offsetC > TEMPERATURE_OFFSET_MAX_C)
|
||||
return TEMPERATURE_OFFSET_MAX_C;
|
||||
return offsetC;
|
||||
}
|
||||
|
||||
void applyTemperatureOffset(meshtastic_EnvironmentMetrics *metrics)
|
||||
{
|
||||
const float offsetC = clampTemperatureOffsetC(moduleConfig.telemetry.environment_temperature_offset_c);
|
||||
if (offsetC == 0.0f)
|
||||
return;
|
||||
|
||||
if (metrics->has_temperature)
|
||||
metrics->temperature += offsetC;
|
||||
if (metrics->has_soil_temperature)
|
||||
metrics->soil_temperature += offsetC;
|
||||
}
|
||||
} // namespace
|
||||
static constexpr uint16_t TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY = 0x8002;
|
||||
|
||||
void EnvironmentTelemetryModule::i2cScanFinished(ScanI2C *i2cScanner)
|
||||
@@ -261,6 +287,29 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
return disable();
|
||||
}
|
||||
|
||||
auto refreshLocalMeasurementPacket = [this]() {
|
||||
// Keep displaying remote telemetry once we have it.
|
||||
if (lastMeasurementPacket != nullptr && lastMeasurementPacket->from != nodeDB->getNodeNum()) {
|
||||
return;
|
||||
}
|
||||
|
||||
meshtastic_Telemetry local = meshtastic_Telemetry_init_zero;
|
||||
if (!getEnvironmentTelemetry(&local)) {
|
||||
return;
|
||||
}
|
||||
|
||||
meshtastic_MeshPacket *localPacket = allocDataProtobuf(local);
|
||||
if (localPacket == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (lastMeasurementPacket != nullptr) {
|
||||
packetPool.release(lastMeasurementPacket);
|
||||
}
|
||||
lastMeasurementPacket = packetPool.allocCopy(*localPacket);
|
||||
packetPool.release(localPacket);
|
||||
};
|
||||
|
||||
if (firstTime) {
|
||||
// This is the first time the OSThread library has called this function, so do some setup
|
||||
firstTime = 0;
|
||||
@@ -290,6 +339,7 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
result = rak9154Sensor.runOnce();
|
||||
#endif
|
||||
#endif
|
||||
refreshLocalMeasurementPacket();
|
||||
}
|
||||
// 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
|
||||
@@ -306,6 +356,7 @@ int32_t EnvironmentTelemetryModule::runOnce()
|
||||
result = delay;
|
||||
}
|
||||
}
|
||||
refreshLocalMeasurementPacket();
|
||||
|
||||
uint32_t lastTelemetry =
|
||||
transmitHistory ? transmitHistory->getLastSentToMeshMillis(TX_HISTORY_KEY_ENVIRONMENT_TELEMETRY) : 0;
|
||||
@@ -566,6 +617,9 @@ bool EnvironmentTelemetryModule::getEnvironmentTelemetry(meshtastic_Telemetry *m
|
||||
hasSensor = true;
|
||||
}
|
||||
#endif
|
||||
if (valid)
|
||||
applyTemperatureOffset(&m->variant.environment_metrics);
|
||||
|
||||
return valid && hasSensor;
|
||||
}
|
||||
|
||||
@@ -630,11 +684,15 @@ 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);
|
||||
const bool shouldReplaceDisplayPacket =
|
||||
(lastMeasurementPacket == nullptr || lastMeasurementPacket->from == nodeDB->getNodeNum());
|
||||
if (shouldReplaceDisplayPacket) {
|
||||
// release previous packet before occupying a new spot
|
||||
if (lastMeasurementPacket != nullptr)
|
||||
packetPool.release(lastMeasurementPacket);
|
||||
|
||||
lastMeasurementPacket = packetPool.allocCopy(*p);
|
||||
lastMeasurementPacket = packetPool.allocCopy(*p);
|
||||
}
|
||||
if (phoneOnly) {
|
||||
LOG_INFO("Send packet to phone");
|
||||
service->sendToPhone(p);
|
||||
|
||||
Reference in New Issue
Block a user