Skip to content

Activity Monitoring

activity_monitoring (main/display/activity_monitoring.cpp/.h) is the display-layer module that implements screen inactivity timeout and wake/sleep behavior for the pendant display. It bridges the platform-level esp3d_activity_manager component with the rest of the UI system: it reads the configured timeout from NVS settings, wires up the backlight fade animation, subscribes to connection-status changes as an activity source, and drives periodic timeout checking through an LVGL timer.

When ESP3D_BRIGHTNESS_CONTROL_FEATURE is enabled, the module gradually dims the screen to black after a configurable inactivity period and restores full brightness on the next user touch or system event. When the feature is disabled the module still initialises its LVGL timer (no-op body) so callers never need to guard against the build flag.

The entire module runs on Core 1 (the LVGL task). No cross-core calls are made.


Platform Services

Platform Component

UI Layer - main/display/

ActivityMonitoring::setup()

ActivityMonitoring::setup()

ActivitySubscriber

activity_process_event

lv_timer_create (1000 ms)

readUint32 / readByte

subscribe server_status

backlight_set()

BSP touch/button callbacks

activity_process_event()

activity_monitoring

(this module)

ui_core

UIManager / LVGL task

common_screens

Splash screen → setup() caller

cnc_shared

Main screen → setup() caller

esp3d_activity_manager

C broadcaster + ActivitySubscriber

ESP3DSettings

(NVS)

ESP3DValues

server_status

disp_backlight

backlight_set()

BSP board_init

(all boards)

For the full architecture of esp3d_activity_manager (broadcaster, ActivitySubscriber state machine, BSP integration pattern) see esp3d_activity_manager.md.


The module has two concrete artefacts: a global subscriber object and a private LVGL timer, joined by the single public function setup().

activity_monitoring.cpp

setTimeout(ms)

lv_timer_create

esp3dXValues.subscribe

enable()

activity_timer_callback()

activity_process_event()

g_screenSubscriber

ActivitySubscriber

(linkedToUI=true)

setup()

initialize()

(called once)

LVGL Timer

1000 ms

onServerStatusActivity()

server_status watcher

g_screenSubscriber — global screen subscriber

Section titled “g_screenSubscriber — global screen subscriber”

Constructed at static-init time, before setup() is called. Its timeout starts at 0 (disabled) and is configured to the NVS value inside initialize().

Wake-up lambda (fires when activity_process_event() wakes the sleeping subscriber):

1. ActivitySubscriber::interruptFade() — clears _fading to stop any running fade loop
2. ESP3D_WAKEUP_SOUND macro — plays audible wake tone (board-dependent)
3. backlight_set( readByte(esp3d_brightness_level) ) — restores saved brightness

Sleep lambda (fires when handle() detects elapsed ≥ timeout):

1. _fading = true
2. Read current brightness from NVS
3. Loop: backlight_set(brightness); vTaskDelay(50 ms); brightness -= 10
Exit condition: brightness <= 0 OR _fading == false (interrupted by wake-up)
4. backlight_set(0) if loop ended naturally
5. _fading = false

LVGL note: vTaskDelay() is called inside the sleep lambda which executes within an LVGL timer callback. The delay is bounded (~1.25 s max for a full fade) and is immediately interruptible via interruptFade(). Do not add heavier work to this callback.


No

Yes

No

Yes

Yes

No

ActivityMonitoring::setup()

system_initialized?

read esp3d_screen_timeout

(NVS, uint32_t seconds → ms)

g_screenSubscriber.setTimeout(timeout_ms)

esp3dXValues.subscribe(

server_status, onServerStatusActivity)

lv_timer_create(

activity_timer_callback, 1000 ms, nullptr)

system_initialized = true

ESP3D_BRIGHTNESS

_CONTROL_FEATURE?

return

g_screenSubscriber

.isEnabled()?

g_screenSubscriber.enable()

registers slot with activity_manager

_isActive = true

setup() is idempotent — it is safe to call from multiple screens:

CallEffect
First call (e.g., splash screen)Runs full initialisation: reads NVS, starts LVGL timer, subscribes to server_status, enables subscriber
Subsequent calls (e.g., main screen)No-op if subscriber is already enabled; re-enables it if it was explicitly disabled

Periodic timeout check (LVGL timer → sleep)

Section titled “Periodic timeout check (LVGL timer → sleep)”
backlight driverg_screenSubscriber.handle()activity_timer_callbackLVGL Task (Core 1)backlight driverg_screenSubscriber.handle()activity_timer_callbackLVGL Task (Core 1)alt[elapsed >= timeout AND _isActive]loop[Every 1000 ms]fire timerhandle()elapsed = now - _lastActivityMs_isActive = falsesleep lambda → fade to 0 (50 ms steps)no-op - still within timeout window BLg_screenSubscriberactivity_manageronServerStatusActivityESP3DValuesNetwork/transportBLg_screenSubscriberactivity_manageronServerStatusActivityESP3DValuesNetwork/transportalt[Screen was sleeping]setValue(server_status, new_value)onServerStatusActivity(idx, value, action)activity_process_event()onNotifyStatic → _lastActivityMs = now_isActive = truewake lambda → restore brightness

This ensures the screen wakes whenever a CNC connection is established or lost, even with no physical user input.

This path is owned by the BSP layer, not by this module. See esp3d_activity_manager.md — BSP Integration Pattern and Data Flow: Input Event to Screen Wake-Up for the full sequence, including how activity_process_event() returns false to suppress the first touch on wake-up.


Yes

No

Yes

No

_onSleep() called

_isActive → false

_fading = true

brightness = readByte(esp3d_brightness_level)

brightness > 0

AND _fading?

backlight_set(brightness)

vTaskDelay(50 ms)

brightness -= 10

_fading still true?

backlight_set(0)

_fading = false

(completed)

Interrupted by wake-up

no further write

The interruptFade() call inside the wake-up lambda atomically clears _fading, causing the fade loop to exit on its next iteration without writing backlight_set(0). The wake-up lambda then writes the target brightness immediately after.


Setting indexTypeUsed for
esp3d_screen_timeoutuint32_t (seconds)Inactivity threshold; 0 disables the timeout
esp3d_brightness_leveluint8_tBrightness restored on wake-up; starting point of the fade

Both are read via esp3dXsettings. See Core_Platform_&_Infrastructure.md for the ESP3DSettings NVS API.

The screen-timeout settings screen (screen_timeout_screen.cpp) updates the timeout at runtime without restarting the system:

// From screen_timeout_screen.cpp (after user confirms a new value)
ActivityMonitoring::g_screenSubscriber.setTimeout(new_timeout_ms);

setTimeout() resets the inactivity clock and marks _isActive = true, preventing a spurious immediate sleep if the timeout was previously expired.


# CMakeLists.txt
option(ESP3D_BRIGHTNESS_CONTROL_FEATURE "Enable display brightness / screen timeout" OFF)
SymbolWhen OFF
g_screenSubscriberNot compiled
Wake/sleep lambdasNot compiled
onServerStatusActivityNot compiled
LVGL timerCreated, callback body is empty no-op
setup()Safe to call; records system_initialized, creates timer

Callers do not need a compile-time guard around ActivityMonitoring::setup().


// main/display/activity_monitoring.h
namespace ActivityMonitoring {
void setup();
}
PropertyDetail
ThreadMust be called from the LVGL task (Core 1)
IdempotentYes — safe to call from any screen, any number of times
First-call side effectsReads NVS, starts LVGL timer, subscribes to server_status, enables subscriber
Subsequent-call side effectsEnables subscriber if disabled; no-op if already enabled
Feature guard neededNo — always safe regardless of ESP3D_BRIGHTNESS_CONTROL_FEATURE

ActivityMonitoring::g_screenSubscriber (feature-gated)

Section titled “ActivityMonitoring::g_screenSubscriber (feature-gated)”
#if ESP3D_BRIGHTNESS_CONTROL_FEATURE
extern ActivitySubscriber g_screenSubscriber;
#endif

Exposed for two use cases:

  1. Runtime timeout update — g_screenSubscriber.setTimeout(ms) from the settings screen.
  2. State query — g_screenSubscriber.isActive(), g_screenSubscriber.getElapsedTime() for diagnostic screens.

For the standard “user did something” notification path, call activity_process_event() from the activity manager (BSP callbacks already do this).


DecisionRationale
LVGL timer (not a FreeRTOS task)Timeout checking is a once-per-second lightweight check. Running it inside the LVGL timer avoids a dedicated task, stack allocation, and any synchronisation overhead.
_fading as static volatile boolThe sleep lambda runs inside the LVGL timer callback on Core 1. The wake-up path also runs on Core 1 (via activity_process_event() from the LVGL input callback). A volatile flag is sufficient; no mutex is needed because both paths share the same core.
linkedToUI = true for screen subscriberThe first touch must wake the screen, not trigger a UI element. This is enforced by activity_process_event() returning false to the BSP input callback, which then reports LV_INDEV_STATE_RELEASED instead of passing coordinates to LVGL.
server_status subscriptionConnection events are meaningful UI transitions. The screen should be awake during connect/disconnect sequences regardless of physical activity.
Idempotent setup()Screens are created and destroyed. The setup() caller (e.g., main screen) does not know whether the splash screen already ran initialisation. A single idempotent entry point eliminates the need for coordination.

DocumentTopic
esp3d_activity_manager.mdFull ActivitySubscriber state machine, C broadcaster API, BSP integration pattern, input-consumption sequence
screens_architecture.mdScreen lifecycle; where setup() is called in the splash and main screen create() functions
screen_transitions_flow.mdSafe LVGL timer patterns; object lifetime rules that apply to the activity timer
display_drivers.mddisp_backlight API (backlight_set, backlight_config_t) used by the wake/sleep lambdas
Core_Platform_&_Infrastructure.mdESP3DSettings NVS reads and ESP3DValues subscription API
Input_system.mdHow activity_process_event() return value suppresses the wake touch in the BSP input pipeline
esp32_memory_constraints.mdHeap budget context explaining the static subscriber table design