Skip to content

UIButton has no server or client wiring — clicking it does nothing #1060

Description

@humppafreak

Summary

sensesp::UIButton (src/sensesp/ui/ui_button.h, ui_button.cpp) is
documented as:

Each UIButton object creates a button in the "Control" tab of the
web UI. When the button is clicked, the object's observers are
notified.

This does not happen. UIButton::add() registers the button into a
static std::map<String, std::shared_ptr<UIButton>>
(ui_buttons_/get_ui_buttons()), but nothing else in the codebase —
neither the HTTP backend nor the bundled web UI frontend — reads that
map, serves it to the browser, or handles a click. An app that
constructs a UIButton and attach()es a callback to it will compile
and run fine, but the button never appears anywhere in the web UI, and
the callback never fires.

Environment

  • SensESP version: 3.5.0 (resolved via SignalK/SensESP @ ^3.2.0 in
    platformio.ini)
  • Also checked against the current main branch on GitHub as of
    2026-08 — same result.
  • Platform: ESP32-C3, PlatformIO/Arduino framework.

How to reproduce

#include "sensesp/ui/ui_button.h"

auto* btn = sensesp::UIButton::add("my_action", "Do The Thing");
btn->attach([]() { Serial.println("clicked!"); });

Flash this alongside a normal SensESPAppBuilder setup, open the
device's web UI, and look for a "Control" tab or any rendering of "Do
The Thing." Nothing appears.

Investigation

1. Backend: no HTTP handler serves or consumes the button registry.

Grepped every .cpp/.h under src/sensesp/net/ (the HTTP
server/handler layer) for UIButton, ui_button, ui_buttons_, and
get_ui_buttons:

$ grep -rln "UIButton\|ui_buttons\|get_ui_buttons" src/sensesp/net/
(no results)

The only files referencing UIButton at all are ui_button.h and
ui_button.cpp themselves. config_handler.cpp only implements
/api/config* (get/put persisted config items via
ConfigItem/FileSystemSaveable); app_command_handler.cpp and
base_command_handler.cpp implement fixed, hardcoded endpoints
(/api/device/reset, /api/device/restart, /api/info, /api/log,
/api/routes, WiFi scan, SignalK TOFU reset) — nothing generic or
pluggable for app-registered commands/buttons.

2. Frontend: the actual served bundle has no button-rendering
mechanism either.

The web UI HTML/CSS/JS actually served at / (and /status,
/configuration, /system, /log, /wifi, /signalk — see
add_routes_handlers() in base_command_handler.cpp) comes from
src/sensesp/net/web/autogen/frontend_files.h, a generated
kFrontendFiles[] array of gzip-compressed static assets (a
Preact-based SPA, judging by the bundle's
__federation_shared_preact-*.js chunk name).

I extracted and decompressed the main JS bundle
(/assets/index-*.js, ~34KB gzipped / ~100KB decompressed) out of the
compiled firmware and searched it directly:

$ grep -o '/api/[a-zA-Z_/-]*' index_bundle.js | sort -u
/api/config
/api/device/reset
/api/device/restart
/api/info
/api/log
/api/routes
/api/signalk
/api/signalk/reset-tofu
/api/wifi/scan
/api/wifi/scan-results

No /api/command, /api/buttons, or anything resembling a generic
custom-action endpoint. The only two buttons that exist anywhere in
the actual UI are hardcoded, fixed "Restart the device" and "Reset the
device to factory defaults" (wired to the two fixed /api/device/*
endpoints above) — there is no loop or component in the bundle that
reads a dynamic list of registered commands/buttons and renders one
per entry.

3. A separate, older, apparently-unused frontend file exists in the
source tree
(src/sensesp/net/web/js_sensesp.h, index.h,
css_bootstrap.h, static_file_data.h, served via
add_static_file_handlers()). Decompressing that JS shows a
different, older single-page-app implementation that does have a
showControl() function referencing deviceInfo.Commands and calling
GET /command?id=<name> — a shape that lines up conceptually with
UIButton's title_/name_/must_confirm_ fields. But:

  • Nothing populates Commands in any /info-family JSON response on
    the backend (checked add_http_info_handler() in
    base_command_handler.cpp; it emits StatusPageItemBase entries
    only, no Commands key).
  • Nothing handles GET /command on the backend either.
  • This older bundle isn't what's served at the root/default routes
    that add_routes_handlers() registers — the newer
    autogen/frontend_files.h bundle is.

So even this older, seemingly-more-complete-looking client-side
mechanism is dead on both ends in the current codebase — it may be
the vestige of a previous web UI generation that UIButton was
originally built against, before the web UI was replaced with the
current Preact SPA (which never got equivalent support added).

Impact

Any application code that follows UIButton's documented usage (the
class's own doc comment, and presumably any examples/docs pointing at
it) silently does nothing — no compile error, no runtime error, no log
message, just a button that never appears. This is a confusing trap:
it looks like a supported, working feature.

Suggested fix (one of)

  1. Wire it up properly: add a backend endpoint that serves
    UIButton::get_ui_buttons() as JSON and a POST/GET endpoint to
    invoke notify() on the matching button by name, then add the
    corresponding rendering + click-handling to the current Preact
    bundle's "Control"-equivalent page (there doesn't currently appear
    to be one at all in the new frontend — showControl()'s
    functionality may need to be reintroduced from scratch, not just
    reconnected).
  2. Mark it clearly non-functional: update the class doc comment to
    say it currently has no UI backing in this version, so people don't
    build against it expecting it to work.
  3. Remove it, if there's no near-term plan to wire it up, rather
    than leave working-looking dead code in the public API.

Happy to test a fix against real hardware if one gets proposed — this
came up while building a compass-interface firmware on top of SensESP
3.5.0/ESP32-C3 and needing a one-click "start calibration" action;
ended up keeping the existing config-checkbox-based trigger mechanism
instead, since that's the only mechanism that's actually confirmed to
work end-to-end.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions