Skip to content

petervflocke/lcdmonitor

Repository files navigation

LCD Monitor

Two-part monitoring stack that shows Linux host metrics on an Arduino Nano with a 20x4 HD44780 LCD. The Arduino handles the UI (telemetry vs. command list) while a Python daemon polls system sensors, formats the frames, and exchanges commands over serial.

Repository layout

  • arduino/ – PlatformIO project for the Nano sketch (LCD driver, rotary encoder, serial protocol).
  • server/ – Python 3.12 daemon with config loader, sensor collectors, command protocol, and systemd entrypoint.
  • infra/systemd/ – Example unit files for user and system service deployments.
  • docs/ – Wiring, ADRs, and deployment guides (docs/deployment-systemd.md expands on the service setup).
  • Makefile – Shared tasks for formatting, linting, testing, builds, and service helper commands.

Prerequisites

  • Python 3.12 with uv (or python3 -m venv fallback).
  • PlatformIO CLI (pio) and serial access to the Arduino Nano. The Makefile auto-detects the VS Code extension’s bundled binary at ~/.platformio/penv/bin/pio; override with make PIO=/path/to/pio … if installed elsewhere.
  • Optional GPU telemetry: pynvml or nvidia-smi available on the host.

Setup

make setup        # sync Python deps with uv
make fmt          # format Python code (ruff + black)
make lint         # ruff linting
make type         # mypy (strict)
make test         # pytest
make arduino-build

Hardware upload/monitor commands are available via make arduino-upload and make arduino-monitor. Day-to-day development typically uses the PlatformIO VS Code extension’s GUI shortcuts; the Make targets remain available for scripted runs. If the CLI is not on PATH, either rely on the auto-detected ~/.platformio/penv/bin/pio or provide PIO=/path/to/pio when invoking make.

Running the Python daemon

  • Dry run on a development box (no Arduino required):

    make server-dry-run

    This prints the assembled LCD lines to stdout once and exits.

  • Full daemon using the sample config and the local serial device:

    make server-run

    The config is driven by server/config.example.yaml; copy and edit it for your host. Enable command execution explicitly with --allow-exec and pick an execution driver (shell is the default, systemd-user and systemd-system remain available). For logging, --verbose elevates output to INFO, while --log-level=<LEVEL> (CRITICAL/ERROR/WARNING/INFO/DEBUG) provides explicit control. Each telemetry frame starts with a metadata line (META interval=<seconds>) so the Arduino can scale its watchdog before rendering the remaining lines; the sketch hides the metadata from the LCD.

    Pass extra CLI flags through the Make targets with SERVER_ARGS—handy for turning on debug logging or experimenting with other options:

    make server-run SERVER_ARGS="--log-level=DEBUG"
    make server-dry-run SERVER_ARGS="--log-level=INFO --once"
    make server-run SERVER_ARGS="--log-level=INFO --allow-exec --exec-driver systemd-user"

    The same variable works with make server-run-pip and make server-dry-run-pip.

Systemd deployment

Systemd units live in infra/systemd/ and are documented in detail in docs/deployment-systemd.md. In short:

  • User service (development)infra/systemd/lcdmonitor.service runs under the logged-in user. Copy it to ~/.config/systemd/user/, adjust WorkingDirectory/ExecStart, then run:

    systemctl --user daemon-reload
    systemctl --user enable --now lcdmonitor
    journalctl --user -u lcdmonitor -f

    For headless operation, enable lingering via loginctl enable-linger $USER. make service-user-install only prints these instructions—you still copy/edit the unit manually.

  • System Service (production)infra/systemd/lcdmonitor.system.service assumes a dedicated service account (e.g. lcdmon) that belongs to the serial group (dialout, uucp, etc.). Create /etc/lcdmonitor/config.yaml, then create /etc/default/lcdmonitor with:

    LCDMONITOR_VENV=/opt/lcdmonitor/.venv
    LCDMONITOR_CONFIG=/etc/lcdmonitor/config.yaml

    Prerequisites before installation:

    1. Create the service user and add it to the serial group (e.g., sudo useradd -r -s /usr/sbin/nologin -G dialout lcdmon).
    2. Decide on an install root (INSTALL_ROOT, default /opt/lcdmonitor).
    3. Prepare the Python virtualenv at ${INSTALL_ROOT}/.venv with runtime deps (pip install /opt/lcdmonitor/server) or run make setup locally and copy the environment. If the virtualenv isn't ready yet, run the installer with ENABLE_SERVICE=0 so it won't start the daemon until you finish provisioning.

    Automated install (overrides optional):

    sudo make service-system-install SERVICE_USER=lcdmon SERVICE_GROUP=dialout INSTALL_ROOT=/opt/lcdmonitor \
      CONFIG_PATH=/etc/lcdmonitor/config.yaml ENV_FILE=/etc/default/lcdmonitor

    Run this from a clean checkout on the server. The target rsyncs the current repo into ${INSTALL_ROOT} (set COPY_REPO=0 to skip; falls back to tar if rsync is unavailable), seeds /etc/default/lcdmonitor, copies the hardened unit into /etc/systemd/system/lcdmonitor.service, adjusts ownership of /etc/lcdmonitor/config.yaml, reloads systemd, and enables the service (unless ENABLE_SERVICE=0). Command execution stays disabled by default; add --allow-exec --exec-driver shell to ExecStart once you have a whitelist in the config (or switch to another driver if you have systemd privileges configured).

    Once the virtualenv is provisioned, future updates only require pulling latest code and running:

    sudo make service-system-update SERVICE_USER=lcdmon SERVICE_GROUP=dialout INSTALL_ROOT=/opt/lcdmonitor \
      CONFIG_PATH=/etc/lcdmonitor/config.yaml ENV_FILE=/etc/default/lcdmonitor

    This reruns the installer with ENABLE_SERVICE=0, upgrades the installed Python package in the venv, then restarts the service and prints its status.

    Root-level commands should use sudo -n and you must grant the service user explicit passwordless sudo rules (e.g., lcdmon ALL=(root) NOPASSWD:/sbin/shutdown,/sbin/reboot) to avoid prompts. The bundled system unit sets NoNewPrivileges=no so these sudo calls can elevate; review and tighten other hardening knobs as needed for your environment. The installer also prepares /home/lcdmon/.cache/pip and all pip invocations use sudo -H -u lcdmon … so virtualenv upgrades do not warn about unwritable caches.

Make helpers print the same instructions for quick reference:

make service-user-install
make service-system-notes
sudo make service-system-install [SERVICE_USER=… SERVICE_GROUP=… INSTALL_ROOT=…]
sudo make service-system-update [SERVICE_USER=… SERVICE_GROUP=… INSTALL_ROOT=…]

service-system-install expects the prerequisites above (existing service account, deployed repo, ready virtualenv). It will warn if the user or group are missing.

Testing and CI

  • make ci runs formatting checks, lint, mypy, pytest, and an Arduino build.
  • make e2e PORT=/dev/ttyACM0 builds the sketch and runs the mock sender against connected hardware.
  • uvx pip-audit (via make audit) surfaces Python dependency issues.

Sensor sources

  • CPU summary: psutil for CPU%/RAM%, CPU package temp from the coretemp chip label Package id 0 when available, otherwise the first exposed temperature sensor.
  • GPU summary: NVML (pynvml) first, falling back to nvidia-smi (util%, memory%, GPU temp).
  • Generic temps (provider: temp in config): psutil sensors_temperatures() with optional chip/label filters from the YAML config.

Additional docs

  • Wiring diagram and bill of materials: docs/wiring.md.
  • Systemd deployment deep dive: docs/deployment-systemd.md.
  • Architecture and decisions: docs/adr/.
  • Codex collaboration: AGENTS.md, config.toml, docs/codex-playbook.md, docs/codex-howto.md.

Hardware validation requires the attached Arduino; if Codex cannot run those checks, pending tests are called out in task notes and handoff summaries for manual follow-up.

Keep docs, tests, and configs in sync with behavior changes before opening a PR.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors