Skip to content

Fix crash when all page switches are disabled#191

Open
librick wants to merge 1 commit into
MallocArray:mainfrom
librick:fix-rotation-infinite-recursion
Open

Fix crash when all page switches are disabled#191
librick wants to merge 1 commit into
MallocArray:mainfrom
librick:fix-rotation-infinite-recursion

Conversation

@librick
Copy link
Copy Markdown

@librick librick commented May 14, 2026

If you turn off all display page switches in Home Assistant, the AirGradient ONE crashes (reproduced on v5.3.3, likely affects v5.3.4). I traced this to the rotation logic in display_sh1106_multi_page.yaml and display_sh1106_single_page.yaml.

When every page switch is off, including display_blank_page, the on_page_change handlers each call display.page.show_next on their own page. This triggers the next page's on_page_change handler, which calls show_next again, and so on. Because there's no base case or enabled page to stop the loop, the call stack grows without bound, triggering a stack overflow. This happens within microseconds and crashes the device.

The configuration.md doc says:

disabling all pages will result in the device rebooting

but does not explain why.

This proposed fix gates show_next calls behind a template binary_sensor (any_non_blank_page_enabled), which is true if and only if any non-blank page switch is on.

If the blank page switch is the only switch that's on, the blank page is shown. If the blank page switch is off and every other page switch is also off, the blank page is shown.

Applied to display_sh1106_multi_page.yaml and display_sh1106_single_page.yaml. display_ssd1306.yaml is not affected because its on_page_change only has a handler for the boot page, which bounds the chain of possible page skips.

configuration.md is updated to reflect that disabling all pages no longer crashes the device, and that setting the blank page switch on its own is sufficient to display a blank page.

Stack Trace

Here's the stack trace from a boot after hitting the crash condition:

[13:50:58.996][E][esp32.crash:329]: *** CRASH DETECTED ON PREVIOUS BOOT ***
[13:50:59.000][E][esp32.crash:334]:   Reason: Fault
[13:50:59.000][E][esp32.crash:336]:   Crashed core: 0
[13:50:59.000][E][esp32.crash:337]:   PC:  0x42006342  (fault location)
WARNING Decoded 0x42006342: esphome::display::Display::show_page(esphome::display::DisplayPage*) at /data/build/my-device/src/esphome/components/display/display.cpp:684
[13:50:59.028][E][esp32.crash:302]:   BT0: 0x42006342  (backtrace)
WARNING Decoded 0x42006342: esphome::display::Display::show_page(esphome::display::DisplayPage*) at /data/build/my-device/src/esphome/components/display/display.cpp:684
[13:50:59.051][E][esp32.crash:302]:   BT1: 0x42022A68  (backtrace)
WARNING Decoded 0x42022a68: esphome::http_request::HttpRequestSendAction<>::encode_json_(ArduinoJson::V742HB22::JsonObject) at /data/build/my-device/src/esphome/components/http_request/http_request.h:561
[13:50:59.139][E][esp32.crash:302]:   BT2: 0x42006310  (stack scan)
WARNING Decoded 0x42006310: esphome::display::DisplayOnPageChangeTrigger::process(esphome::display::DisplayPage*, esphome::display::DisplayPage*) at /data/build/my-device/src/esphome/components/display/display.cpp:708
[13:50:59.166][E][esp32.crash:302]:   BT3: 0x4200634E  (stack scan)
WARNING Decoded 0x4200634e: __gnu_cxx::__normal_iterator<esphome::display::DisplayOnPageChangeTrigger**, std::vector<esphome::display::DisplayOnPageChangeTrigger*, std::allocator<esphome::display::DisplayOnPageChangeTrigger*> > >::operator++() at /data/cache/platformio/packages/toolchain-riscv32-esp/riscv32-esp-elf/include/c++/14.2.0/bits/stl_iterator.h:1103
 (inlined by) esphome::display::Display::show_page(esphome::display::DisplayPage*) at /data/build/my-device/src/esphome/components/display/display.cpp:683
[13:50:59.191][E][esp32.crash:302]:   BT4: 0x42006310  (stack scan)
WARNING Decoded 0x42006310: esphome::display::DisplayOnPageChangeTrigger::process(esphome::display::DisplayPage*, esphome::display::DisplayPage*) at /data/build/my-device/src/esphome/components/display/display.cpp:708
[13:50:59.219][E][esp32.crash:302]:   BT5: 0x4200634E  (stack scan)
WARNING Decoded 0x4200634e: __gnu_cxx::__normal_iterator<esphome::display::DisplayOnPageChangeTrigger**, std::vector<esphome::display::DisplayOnPageChangeTrigger*, std::allocator<esphome::display::DisplayOnPageChangeTrigger*> > >::operator++() at /data/cache/platformio/packages/toolchain-riscv32-esp/riscv32-esp-elf/include/c++/14.2.0/bits/stl_iterator.h:1103
 (inlined by) esphome::display::Display::show_page(esphome::display::DisplayPage*) at /data/build/my-device/src/esphome/components/display/display.cpp:683
[13:50:59.243][E][esp32.crash:302]:   BT6: 0x42006310  (stack scan)
WARNING Decoded 0x42006310: esphome::display::DisplayOnPageChangeTrigger::process(esphome::display::DisplayPage*, esphome::display::DisplayPage*) at /data/build/my-device/src/esphome/components/display/display.cpp:708
[13:50:59.266][E][esp32.crash:302]:   BT7: 0x4200634E  (stack scan)
WARNING Decoded 0x4200634e: __gnu_cxx::__normal_iterator<esphome::display::DisplayOnPageChangeTrigger**, std::vector<esphome::display::DisplayOnPageChangeTrigger*, std::allocator<esphome::display::DisplayOnPageChangeTrigger*> > >::operator++() at /data/cache/platformio/packages/toolchain-riscv32-esp/riscv32-esp-elf/include/c++/14.2.0/bits/stl_iterator.h:1103
 (inlined by) esphome::display::Display::show_page(esphome::display::DisplayPage*) at /data/build/my-device/src/esphome/components/display/display.cpp:683
[13:50:59.289][E][esp32.crash:302]:   BT8: 0x42006310  (stack scan)
WARNING Decoded 0x42006310: esphome::display::DisplayOnPageChangeTrigger::process(esphome::display::DisplayPage*, esphome::display::DisplayPage*) at /data/build/my-device/src/esphome/components/display/display.cpp:708
[13:50:59.317][E][esp32.crash:302]:   BT9: 0x4200634E  (stack scan)
WARNING Decoded 0x4200634e: __gnu_cxx::__normal_iterator<esphome::display::DisplayOnPageChangeTrigger**, std::vector<esphome::display::DisplayOnPageChangeTrigger*, std::allocator<esphome::display::DisplayOnPageChangeTrigger*> > >::operator++() at /data/cache/platformio/packages/toolchain-riscv32-esp/riscv32-esp-elf/include/c++/14.2.0/bits/stl_iterator.h:1103
 (inlined by) esphome::display::Display::show_page(esphome::display::DisplayPage*) at /data/build/my-device/src/esphome/components/display/display.cpp:683
[13:50:59.341][E][esp32.crash:358]: Use: addr2line -pfiaC -e firmware.elf 0x42006342 0x42006342 0x42022A68 0x42006310 0x4200634E 0x42006310 0x4200634E 0x42006310 0x4200634E 0x42006310 0x4200634E

@librick
Copy link
Copy Markdown
Author

librick commented May 14, 2026

Still seeing crashes after further tests. Closing this for now so I can debug further

@librick librick closed this May 14, 2026
If you turn off all display page switches in Home Assistant,
the AirGradient ONE crashes (reproduced on v5.3.3, likely affects
v5.3.4). I traced this to the rotation logic in
display_sh1106_multi_page.yaml and display_sh1106_single_page.yaml.

When every page switch is off, including display_blank_page, the
on_page_change handlers each call display.page.show_next on their own
page. This triggers the next page's on_page_change handler, which calls
show_next again, and so on. Because there's no base case or enabled page
to stop the loop, the call stack grows without bound, triggering a stack
overflow. This happens within microseconds and crashes the device.

The configuration.md doc says:

>disabling all pages will result in the device rebooting

but does not explain why.

This proposed fix gates show_next calls behind a template binary_sensor
(any_non_blank_page_enabled), which is true if and only if any non-blank
page switch is on.

If the blank page switch is the only switch that's on, the blank page is
shown. If the blank page switch is off and every other page switch is
also off, the blank page is shown.

Applied to display_sh1106_multi_page.yaml and
display_sh1106_single_page.yaml. display_ssd1306.yaml is not affected
because its on_page_change only has a handler for the boot page, which
bounds the chain of possible page skips.

configuration.md is updated to reflect that disabling all pages no
longer crashes the device, and that setting the blank page switch on its
own is sufficient to display a blank page.
@librick librick reopened this May 15, 2026
@librick librick force-pushed the fix-rotation-infinite-recursion branch from 709f291 to bb30742 Compare May 15, 2026 03:46
@librick
Copy link
Copy Markdown
Author

librick commented May 15, 2026

Reopening. My first attempt had some bugs, turns out this is tricky to get right. Reproduced the existing behavior on v5.3.3, deployed my update, and verified it works as intended.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant