mirror of
https://github.com/meshtastic/firmware.git
synced 2025-12-23 11:10:52 +00:00
InkHUD refactoring (#6216)
* chore: todo.txt * chore: comments * fix: no fast refresh on VME290 Reverts a line of code which was accidentally committed * refactor: god class Divide the behavior from the old WindowManager class into several subclasses which each have a clear role. * refactor: cppcheck medium warnings Enough to pass github CI for now * refactor: updateType selection * refactor: don't use a setter for the shared AppletFonts * fix: update prioritization forceUpdate calls weren't being prioritized * refactor: remove unhelpful logging getTimeString is used for parsing our own time, but also the timestamps of messages. The "one time only" log printing will likely fire in unhelpful situations. * fix: " " * refactor: get rid of types.h file for enums * Keep that sneaky todo file out of commits
This commit is contained in:
176
src/graphics/niche/InkHUD/DisplayHealth.cpp
Normal file
176
src/graphics/niche/InkHUD/DisplayHealth.cpp
Normal file
@@ -0,0 +1,176 @@
|
||||
#ifdef MESHTASTIC_INCLUDE_INKHUD
|
||||
|
||||
#include "./DisplayHealth.h"
|
||||
#include "DisplayHealth.h"
|
||||
|
||||
using namespace NicheGraphics;
|
||||
|
||||
// Timing for "maintenance"
|
||||
// Paying off full-refresh debt with unprovoked updates, if the display is not very active
|
||||
static constexpr uint32_t MAINTENANCE_MS_INITIAL = 60 * 1000UL;
|
||||
static constexpr uint32_t MAINTENANCE_MS = 60 * 60 * 1000UL;
|
||||
|
||||
InkHUD::DisplayHealth::DisplayHealth() : concurrency::OSThread("Mediator")
|
||||
{
|
||||
// Timer disabled by default
|
||||
OSThread::disable();
|
||||
}
|
||||
|
||||
// Request which update type we would prefer, when the display image next changes
|
||||
// DisplayHealth class will consider our suggestion, and weigh it against other requests
|
||||
void InkHUD::DisplayHealth::requestUpdateType(Drivers::EInk::UpdateTypes type)
|
||||
{
|
||||
// Update our "working decision", to decide if this request is important enough to change our plan
|
||||
if (!forced)
|
||||
workingDecision = prioritize(workingDecision, type);
|
||||
}
|
||||
|
||||
// Demand that a specific update type be used, when the display image next changes
|
||||
// Note: multiple DisplayHealth::force calls should not be made,
|
||||
// but if they are, the importance of the type will be weighed the same as if both calls were to DisplayHealth::request
|
||||
void InkHUD::DisplayHealth::forceUpdateType(Drivers::EInk::UpdateTypes type)
|
||||
{
|
||||
if (!forced)
|
||||
workingDecision = type;
|
||||
else
|
||||
workingDecision = prioritize(workingDecision, type);
|
||||
|
||||
forced = true;
|
||||
}
|
||||
|
||||
// Find out which update type the DisplayHealth has chosen for us
|
||||
// Calling this method consumes the result, and resets for the next update
|
||||
Drivers::EInk::UpdateTypes InkHUD::DisplayHealth::decideUpdateType()
|
||||
{
|
||||
LOG_DEBUG("FULL-update debt:%f", debt);
|
||||
|
||||
// For convenience
|
||||
typedef Drivers::EInk::UpdateTypes UpdateTypes;
|
||||
|
||||
// Grab our final decision for the update type, so we can reset now, for the next update
|
||||
// We do this at top of the method, so we can return early
|
||||
UpdateTypes finalDecision = workingDecision;
|
||||
workingDecision = UpdateTypes::UNSPECIFIED;
|
||||
forced = false;
|
||||
|
||||
// Check whether we've paid off enough debt to stop unprovoked refreshing (if in progress)
|
||||
// This maintenance behavior will also have opportunity to halt itself when the timer next fires,
|
||||
// but that could be an hour away, so we can stop it early here and free up resources
|
||||
if (OSThread::enabled && debt == 0.0)
|
||||
endMaintenance();
|
||||
|
||||
// Explicitly requested FULL
|
||||
if (finalDecision == UpdateTypes::FULL) {
|
||||
LOG_DEBUG("Explicit FULL");
|
||||
debt = max(debt - 1.0, 0.0); // Record that we have paid back (some of) the FULL refresh debt
|
||||
return UpdateTypes::FULL;
|
||||
}
|
||||
|
||||
// Explicitly requested FAST
|
||||
if (finalDecision == UpdateTypes::FAST) {
|
||||
LOG_DEBUG("Explicit FAST");
|
||||
// Add to the FULL refresh debt
|
||||
if (debt < 1.0)
|
||||
debt += 1.0 / fastPerFull;
|
||||
else
|
||||
debt += stressMultiplier * (1.0 / fastPerFull); // More debt if too many consecutive FAST refreshes
|
||||
|
||||
// If *significant debt*, begin occasionally refreshing *unprovoked*
|
||||
// This maintenance behavior is only triggered here, by periods of user interaction
|
||||
// Debt would otherwise not be able to climb above 1.0
|
||||
if (debt >= 2.0)
|
||||
beginMaintenance();
|
||||
|
||||
return UpdateTypes::FAST; // Give them what the asked for
|
||||
}
|
||||
|
||||
// Handling UpdateTypes::UNSPECIFIED
|
||||
// -----------------------------------
|
||||
// In this case, the UI doesn't care which refresh we use
|
||||
|
||||
// Not much debt: suggest FAST
|
||||
if (debt < 1.0) {
|
||||
LOG_DEBUG("UNSPECIFIED: using FAST");
|
||||
debt += 1.0 / fastPerFull;
|
||||
return UpdateTypes::FAST;
|
||||
}
|
||||
|
||||
// In debt: suggest FULL
|
||||
else {
|
||||
LOG_DEBUG("UNSPECIFIED: using FULL");
|
||||
debt = max(debt - 1.0, 0.0); // Record that we have paid back (some of) the FULL refresh debt
|
||||
|
||||
// When maintenance begins, the first refresh happens shortly after user interaction ceases (a minute or so)
|
||||
// If we *are* given an opportunity to refresh before that, we'll skip that initial maintenance refresh
|
||||
// We were intending to use that initial refresh to redraw the screen as FULL, but we're doing that now, organically
|
||||
if (OSThread::enabled && OSThread::interval == MAINTENANCE_MS_INITIAL)
|
||||
OSThread::setInterval(MAINTENANCE_MS); // Note: not intervalFromNow
|
||||
|
||||
return UpdateTypes::FULL;
|
||||
}
|
||||
}
|
||||
|
||||
// Determine which of two update types is more important to honor
|
||||
// Explicit FAST is more important than UNSPECIFIED - prioritize responsiveness
|
||||
// Explicit FULL is more important than explicit FAST - prioritize image quality: explicit FULL is rare
|
||||
// Used when multiple applets have all requested update simultaneously, each with their own preferred UpdateType
|
||||
Drivers::EInk::UpdateTypes InkHUD::DisplayHealth::prioritize(Drivers::EInk::UpdateTypes type1, Drivers::EInk::UpdateTypes type2)
|
||||
{
|
||||
switch (type1) {
|
||||
case Drivers::EInk::UpdateTypes::UNSPECIFIED:
|
||||
return type2;
|
||||
|
||||
case Drivers::EInk::UpdateTypes::FAST:
|
||||
return (type2 == Drivers::EInk::UpdateTypes::FULL) ? Drivers::EInk::UpdateTypes::FULL : Drivers::EInk::UpdateTypes::FAST;
|
||||
|
||||
case Drivers::EInk::UpdateTypes::FULL:
|
||||
return type1;
|
||||
}
|
||||
|
||||
return Drivers::EInk::UpdateTypes::UNSPECIFIED; // Suppress compiler warning only
|
||||
}
|
||||
|
||||
// We're using the timer to perform "maintenance"
|
||||
// If significant FULL-refresh debt has accumulated, we will occasionally run FULL refreshes unprovoked.
|
||||
// This prevents gradual build-up of debt,
|
||||
// in case we aren't doing enough UNSPECIFIED refreshes to pay the debt back organically.
|
||||
// The first refresh takes place shortly after user finishes interacting with the device; this does the bulk of the restoration
|
||||
// Subsequent refreshes take place *much* less frequently.
|
||||
// Hopefully an applet will want to render before this, meaning we can cancel the maintenance.
|
||||
int32_t InkHUD::DisplayHealth::runOnce()
|
||||
{
|
||||
if (debt > 0.0) {
|
||||
LOG_DEBUG("debt=%f: performing maintenance", debt);
|
||||
|
||||
// Ask WindowManager to redraw everything, purely for the refresh
|
||||
// Todo: optimize? Could update without re-rendering
|
||||
InkHUD::getInstance()->forceUpdate(Drivers::EInk::UpdateTypes::FULL);
|
||||
|
||||
// Record that we have paid back (some of) the FULL refresh debt
|
||||
debt = max(debt - 1.0, 0.0);
|
||||
|
||||
// Next maintenance refresh scheduled - long wait (an hour?)
|
||||
return MAINTENANCE_MS;
|
||||
}
|
||||
|
||||
else
|
||||
return endMaintenance();
|
||||
}
|
||||
|
||||
// Begin periodically refreshing the display, to repay FULL-refresh debt
|
||||
// We do this in case user doesn't have enough activity to repay it organically, with UpdateTypes::UNSPECIFIED
|
||||
// After an initial refresh, to redraw as FULL, we only perform these maintenance refreshes very infrequently
|
||||
// This gives the display a chance to heal by evaluating UNSPECIFIED as FULL, which is preferable
|
||||
void InkHUD::DisplayHealth::beginMaintenance()
|
||||
{
|
||||
OSThread::setIntervalFromNow(MAINTENANCE_MS_INITIAL);
|
||||
OSThread::enabled = true;
|
||||
}
|
||||
|
||||
// FULL-refresh debt is low enough that we no longer need to pay it back with periodic updates
|
||||
int32_t InkHUD::DisplayHealth::endMaintenance()
|
||||
{
|
||||
return OSThread::disable();
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user