2026-06-17 04:55:15 +03:00
2026-06-17 04:55:15 +03:00
2026-06-17 04:55:15 +03:00
2026-06-17 04:55:15 +03:00
2026-06-17 04:55:15 +03:00
2026-06-17 04:55:15 +03:00
2026-06-17 04:55:15 +03:00
2026-06-17 04:55:15 +03:00
2026-06-17 04:55:15 +03:00
2026-06-17 04:55:15 +03:00
2026-06-17 04:55:15 +03:00

ButtonTask v2

Configurable touch-screen button panel for embedded Linux (Qt5 + QML), with a companion Flask web configurator. Both share a single JSON config file.

Features

  • Dynamic list of buttons (1..N), each fires HTTP GET/POST on tap
  • Layout: grid (configurable column count) or list
  • Configurable background: animated gradient, solid color or image
  • Configurable feedback ring: color and width of the OK / error glow around buttons
  • In-app settings dialog (tabbed pages): CRUD buttons, drag-and-drop reorder, layout/background/feedback/password, Ethernet configuration and update info
  • Web configurator (Flask blueprints + tabbed UI + SortableJS): same operations remotely, plus icon upload, Ethernet configuration and OTA updates
  • Ethernet configuration via NetworkManager (nmcli) from the web UI
  • OTA software update with automatic rollback on a failed launch (health-check)
  • Hot reload: app picks up config changes via QFileSystemWatcher

Layout

ButtonTask/
  CMakeLists.txt        Qt5 build
  main.cpp
  src/                  C++ sources (ConfigManager, ButtonsModel, ButtonController,
                        SettingsContainer, SystemInfo)
  qml/                  Main.qml, BackgroundLayer.qml, ButtonDelegate.qml, SettingsDialog.qml
    components/         BackHeader.qml, LabeledSlider.qml
    settings/          MainPage / ButtonsPage / LayoutPage / BackgroundPage /
                       FeedbackPage / NetworkInfoPage / UpdatePage / ButtonEditorDialog
  resources/            qml.qrc, icons.qrc
  config/config.json    default config (shipped next to the binary)
  webconfig/            Flask service (modular):
    app.py             application factory + /healthz
    config_store.py    load/save/defaults (in sync with C++ ensureDefaults)
    auth.py            login/logout/session
    api.py             buttons/layout/background/feedback/settings/icons
    network.py         Ethernet via nmcli
    updater.py         OTA upload + status
    paths.py           install layout helpers
    templates/ static/ *.service, requirements.txt
    scripts/           bt-netconfig, bt-update, bt-service, buttontask.sudoers,
                       make-package.sh

The Qt app reads the config from (in order of priority):

  1. --config <path> command-line argument
  2. BUTTONTASK_CONFIG environment variable
  3. <applicationDir>/config/config.json (default)

The Flask service reads the same paths.

Building (embedded Linux, Qt5)

sudo apt install qtbase5-dev qtdeclarative5-dev \
                 qml-module-qtquick-controls2 qml-module-qtquick-layouts \
                 qml-module-qtgraphicaleffects qml-module-qtqml-models2 \
                 cmake build-essential

mkdir build && cd build
cmake -DCMAKE_BUILD_TYPE=Release ..
make -j$(nproc)
sudo make install

Binary lands in /usr/local/bin/ButtonTask. To run on a framebuffer/EGL device:

QT_QPA_PLATFORM=eglfs ButtonTask --config /opt/buttontask/config/config.json

Web configurator

cd webconfig
pip3 install -r requirements.txt
BUTTONTASK_CONFIG=../config/config.json python3 app.py

Open http://<device-ip>:8080. Login: admin + the password from settings.password.

Deployment with systemd (versioned layout)

The device uses an A/B-style versioned layout so OTA updates can roll back:

/opt/buttontask/
  versions/<ver>/   ButtonTask + webconfig/   (one dir per deployed version)
  current -> versions/<ver>                    (active version symlink)
  config/  icons/   shared, never replaced by an update
  run/              qt.alive heartbeat, update-status.json
  scripts/          bt-netconfig, bt-update, bt-service (root-owned)

First install:

sudo useradd -r -s /bin/false buttontask
sudo mkdir -p /opt/buttontask/{versions/2.0,config,icons,run,scripts}
sudo cp build/ButtonTask /opt/buttontask/versions/2.0/
sudo cp -r webconfig /opt/buttontask/versions/2.0/
sudo ln -sfn /opt/buttontask/versions/2.0 /opt/buttontask/current
sudo cp config/config.json /opt/buttontask/config/

# privileged helpers (root-owned) + sudoers whitelist
sudo install -m 0755 webconfig/scripts/bt-netconfig webconfig/scripts/bt-update \
                     webconfig/scripts/bt-service /opt/buttontask/scripts/
sudo install -m 0440 webconfig/scripts/buttontask.sudoers /etc/sudoers.d/buttontask
sudo visudo -cf /etc/sudoers.d/buttontask

sudo chown -R buttontask:buttontask /opt/buttontask
sudo chown root:root /opt/buttontask/scripts/bt-*   # scripts must be root-owned

sudo cp webconfig/buttontask.service /etc/systemd/system/
sudo cp webconfig/buttontask-web.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now buttontask buttontask-web

Network configuration (Ethernet)

Open the web UI → tab Сеть. It reads the live state via nmcli and can apply DHCP or a static IP/prefix/gateway/DNS. The change is performed by the root helper bt-netconfig (whitelisted in sudoers); the unprivileged web service never touches the network directly. The same settings can also be applied from the device itself via the in-app Сеть page (it calls the same bt-netconfig helper through sudo, so the Qt UI must run as the buttontask user covered by the sudoers rule).

OTA software update

Build a package and upload it from the web UI → tab Обновление:

./webconfig/scripts/make-package.sh 2.1 build dist
# -> dist/buttontask-2.1.tar.gz  (+ sha256 to paste for integrity)

On upload, bt-update (detached, root) extracts the package into versions/<ver>, flips the current symlink, restarts both services, then runs a health-check: the web /healthz must answer, the Qt UI heartbeat (run/qt.alive) must be fresh, and the buttontask unit must be active within BUTTONTASK_HEALTH_TIMEOUT (default 30s). If any check fails it automatically switches current back to the previous version and restarts. Progress is shown live and is also readable in the app's Обновление page.

JSON config schema

{
  "version": 2,
  "layout": {
    "mode": "grid",          // "grid" | "list"
    "columns": 2,            // grid only
    "spacing": 10,
    "showLabels": true
  },
  "background": {
    "type": "gradient",      // "gradient" | "solid" | "image"
    "color1": "#00007b",
    "color2": "#7b007b",
    "animated": true,
    "imagePath": ""          // filename inside iconsDir
  },
  "feedback": {
    "okColor": "#00cc44",    // ring/glow color on success
    "errorColor": "#cc2200", // ring/glow color on error
    "okWidth": 6,            // ring width (px) on success
    "errorWidth": 6,         // ring width (px) on error
    "glowRadius": 20         // glow radius (0 disables the glow)
  },
  "settings": {
    "darkMode": true,
    "password": "admin",     // shared with web auth
    "kioskMode": false,
    "iconsDir": "./icons",   // dir for user icons & background images
    "webPort": 8080
  },
  "buttons": [
    {
      "id": "uuid",
      "label": "Кнопка",
      "iconPath": "cleaner1.png",  // relative to iconsDir, or absolute
      "action": {
        "type": "http_get",         // "http_get" | "http_post"
        "url": "http://server/btn",
        "headers": {},              // e.g. {"Authorization": "Bearer <token>"}
        "body": "",
        "timeoutMs": 7000,          // optional request timeout (default 7000)
        "acceptAnyResponse": false  // optional: true = any HTTP reply is success
      },
      "feedback": {
        "successText": "Отправлено",
        "errorText": "Ошибка",
        "fadeMs": 5000
      }
    }
  ]
}

Notes

  • Both Qt and Flask write the config atomically (QSaveFile / os.replace).
  • Qt watches the file via QFileSystemWatcher and reloads automatically when the web configurator saves changes.
  • Button success criteria: a request is green on a 1xx/2xx/3xx HTTP reply (redirects are followed) and red on 4xx/5xx or a transport failure (timeout / connection refused / DNS). Set action.acceptAnyResponse: true to treat any received HTTP reply as success (only timeouts/refused fail), and action.timeoutMs to tune the timeout. Add an API key/token via action.headers if the target endpoint requires one.
  • Web configurator endpoints are all session-protected (@login_required after password login); only /healthz is public (it exposes just ok + version, used by the OTA rollback health-check).
  • Default credentials are admin / admin — change on first run.
  • v1 (Android/RS485/voice) was removed; only the icon set was kept.
S
Description
No description provided
Readme 1 GiB
Languages
TeX 37.7%
HTML 25.2%
Python 10.9%
C++ 5.9%
Shell 5.5%
Other 14.7%