Skip to content
WillXLukePublic

About

A qt6 based hardware temperature monitor.Supports local and remote collection.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

QtMonitor + mon-cli

image

A real-time hardware temperature monitor built with Qt 6 + C++ + lm-sensors, packaged as .deb for both amd64 and arm64.

This repository ships two binaries from the same source tree:

  • qtmonitor — the full GUI application (Qt Widgets + OpenGL).
  • mon-cli — a headless command-line companion built with Qt Core + Qt Network only. No X / Wayland needed, so it runs on servers, Jetsons, Raspberry Pi, or any GPU-less box.

Both binaries are produced by a single build_packages.sh run and are version-locked together.


mon-cli (headless CLI)

Run modes

mon-cli                          # interactive menu (1 → Monitor, 2 → help; Ctrl+C exits)
mon-cli monitor                  # open the Monitor live table directly
mon-cli sensors [--json]         # one-shot dump of detected temperatures (with timestamp)
mon-cli collect [opts]           # foreground: HTTP ingest server (receive pushed readings)
mon-cli sync     [opts]          # foreground: push local readings to a remote URL
mon-cli logger   [opts]          # background: record sensor data to CSV via systemd
mon-cli status                   # show collect / sync / logger service state + logger live info
mon-cli logs [collect|sync|logger] [-f] [-n N]   # journalctl for services
mon-cli service install          # write systemd unit files for collect/sync/logger
mon-cli service uninstall        # stop, disable and remove the unit files

Monitor (live table)

mon-cli (then pick 1) or mon-cli monitor opens a full-screen ANSI table that refreshes at 100 ms. It shows:

  • Live per-sensor temperatures
  • Logger status block when a mon-cli logger is running:
    • state (RUNNING / STOPPED)
    • elapsed time
    • recording start time
    • target stop time (when -t was used)
    • CSV output path
    • per-sensor min / avg / max accumulated so far

Ctrl+C exits cleanly.

Foreground collect and sync

Both support -t/--time for auto-exit:

# Run a 30-minute collection server on port 9000
mon-cli collect --port 9000 -t 30m

# Push this host's readings for 2 hours, then exit
mon-cli sync --url http://peer:8000/ingest -t 2h

# Mixed-case and combined durations are accepted
mon-cli sync --url http://peer:8000/ingest -t 1H30M

Duration format: 4h / 5M / 6s (case-insensitive, combinable). Maximum 48 h.

Background services (systemd)

mon-cli can install three systemd system services into /etc/systemd/system/:

Unit Role
mon-cli-collect.service HTTP ingest server (remote collection)
mon-cli-sync.service Push local readings to a peer
mon-cli-logger.service Record readings to CSV in the background
# One-time: install unit files (does not start anything)
sudo mon-cli service install

# Start / stop individual services. `start` rewrites the unit's ExecStart
# with the given arguments, then enables + restarts the service.
sudo mon-cli collect start --port 9000
sudo mon-cli sync    start --url http://peer:9000/ingest --interval 1000
sudo mon-cli logger  start --output-dir /var/log/mon-cli --stats -t 4h

sudo mon-cli collect stop
sudo mon-cli sync    stop
sudo mon-cli logger  stop

# Inspect
mon-cli status                  # all three services + logger live stats
mon-cli collect status
mon-cli sync    status
mon-cli logger  status          # systemctl + live stats from the status file

mon-cli logs                    # journalctl for all three
mon-cli logs logger -f          # follow logger output
mon-cli logs sync   -n 500      # last 500 lines

# Remove everything
sudo mon-cli service uninstall

Logger

mon-cli logger samples all detected sensors (or a --sensors subset) into a bounded in-memory cache and writes a CSV to the output directory when it stops — either because -t expired, mon-cli logger stop was issued, or the service was stopped by systemd.

# Record all sensors for 4 hours, then stop and flush to CSV
sudo mon-cli logger start -t 4h

# Record forever (until stopped), include avg/min/max columns
sudo mon-cli logger start --stats --output-dir /var/log/mon-cli

# Record only two sensors at 500 ms intervals with a 16 MB cache
sudo mon-cli logger start \
    --sensors k10temp-pci-00c3/Tctl,nvme-pci-0100/Composite \
    --interval 500 --cache-mb 16

Options:

Option Default Meaning
--output-dir DIR /var/log/mon-cli CSV destination directory
--interval MS 100 Sample interval (milliseconds)
--cache-mb MB 8 In-memory ring-buffer budget
--stats off Append (avg) (min) (max) columns
--sensors a,b all detected Restrict to a subset
-t, --time DUR none Auto-stop after 4h / 5M / 6s (max 48h)

CSV format matches the GUI's export:

timestamp(s),sensor1(°C),sensor2(°C),...
0.105,50.63,44.50,...
0.210,50.50,49.00,...

With --stats, each sensor additionally contributes three columns: sensor1(avg), sensor1(min), sensor1(max).

The logger publishes its live state to /run/mon-cli/logger.status (root / systemd) or /tmp/mon-cli-<uid>/logger.status (foreground non-root). mon-cli monitor and mon-cli status read this file to show the logger's state, elapsed time, start time, target stop time, and per-sensor min/avg/max.


qtmonitor (GUI)

OpenGL chart rendering

  • OpenGL-accelerated chart on supported hardware (QOpenGLWidget); automatically falls back to Qt's CPU raster engine when no GPU / GL context is available
  • Adaptive aggregation by data span, not just by window setting:
    • When the actual recorded span for a sensor is < 5 minutes, the chart always uses the raw 50 ms samples with per-pixel min/max decimation — the LOD aggregation does not kick in prematurely, so short recordings look crisp even if the user picks a 5-minute window
    • Once the span reaches 5 minutes (or older data has spilled to disk), the chart switches to the LOD path described below
  • Trend + peak preserving LOD aggregation for long windows:
    • The model maintains a per-second level-of-detail (avg, min, max) per sensor
    • Each output pixel column emits up to four points — (enterT, avg) → (minT, minV) → (maxT, maxV) → (exitT, avg) — so spikes and troughs stay visible and adjacent columns connect with a smooth trend line (no sawtooth)
    • Flat columns collapse to two points, eliminating noise on steady signals
  • Stable right edge: the still-open second bucket is popped out of the closed-bucket aggregation and re-appended every tick as a "partial column" (enter anchor / min / max / exit at the current wall-clock time). The historical curve is served from cache while the live edge tracks 50 ms samples, so the rightmost pixel no longer vibrates
  • Per-sensor downsampled-data cache keyed by window + pixel width + the integer second of the latest sample, so the closed-bucket aggregation is recomputed at most once per second instead of once per 50 ms tick
  • Cached visible-window bounds avoid re-scanning every point on every frame
  • Y-axis auto-scale uses the raw min/max of the visible window plus the live open bucket's extremes, so sudden spikes correctly push the axis range up even when the displayed line is aggregated

Statistics table

  • Refreshes on the same timer as the chart but reuses QTableWidgetItems instead of allocating new ones every tick
  • Column widths are only recomputed when the sensor list changes, not on every refresh
  • trimToTimeWindow is throttled to ~1 Hz (was: every 50 ms tick), removing a recurring O(N) scan of every sensor's timestamp vector

Data export

  • CSV (timestamp + all sensors)
  • Chart snapshot as PNG / JPG / BMP
  • Combined report (CSV + image) via Export dialog
  • Filenames include yyyyMMdd_HHmmss timestamp

Remote sync

  • POST JSON (hostname, timestamp, interval_ms, sensor data) to a user-configurable URL
  • Per-sensor checkbox to choose which curves to send
  • Configurable send interval (ms)
  • Start / Stop buttons in the control bar; status shown in bottom-right status bar
  • CLI demo receiver (scripts/cli_demo_remote_sync.py) prints received data

Build

Dependencies

sudo apt update
sudo apt install -y build-essential qt6-base-dev qt6-base-dev:arm64 \
                    qt6-base-dev-tools \
                    libsensors-dev libsensors-dev:arm64 \
                    debhelper fakeroot dpkg-cross \
                    gcc-aarch64-linux-gnu

If you have not configured sensors yet:

sudo sensors-detect   # answer YES to scan, save
sensors               # verify sensors are detected

Compile from source

cd ~/Desktop/qtmonitor
mkdir -p build
cd build
qmake6 ../qtmonitor.pro       # GUI
make -j$(nproc)
./qtmonitor

# CLI companion
mkdir -p ../build-cli && cd ../build-cli
qmake6 ../mon-cli.pro
make -j$(nproc)
./mon-cli

Both builds are out-of-tree: build/ and build-cli/ hold all generated objects, so the project root stays clean.

Build all .deb packages

Run the provided script from the project root:

cd ~/Desktop/qtmonitor
./build_packages.sh

This will:

  1. Create output/ if it does not exist
  2. Clean previously built .deb / .ddeb / .changes / .buildinfo artifacts
  3. Build the amd64 packages (qtmonitor and mon-cli) with dpkg-buildpackage
  4. Cross-compile both binaries for arm64 and stage matching .debs
  5. Move all resulting packages into output/
  6. Remove transient build/, build-cli/, build-arm64/, build-cli-arm64/ directories

Outputs in output/:

  • qtmonitor_0.0.3-1_amd64.deb / qtmonitor_0.0.3-1_arm64.deb
  • mon-cli_0.0.3-1_amd64.deb / mon-cli_0.0.3-1_arm64.deb
  • qtmonitor_0.0.3-1_amd64.changes / .buildinfo
  • *-dbgsym_0.0.3-1_amd64.ddeb (when produced)

Python scripts (.py) anywhere in the project are never deleted by the cleanup step.

Manual package build (advanced)

# amd64 (both qtmonitor and mon-cli debs)
dpkg-buildpackage -us -uc -b

# arm64 cross-compile (GUI)
mkdir -p build-arm64
( cd build-arm64 && \
  aarch64-linux-gnu-qmake6 \
      -spec linux-aarch64-gnu-g++ CONFIG+=release ../qtmonitor.pro && \
  make -j$(nproc) )

# arm64 cross-compile (CLI)
mkdir -p build-cli-arm64
( cd build-cli-arm64 && \
  aarch64-linux-gnu-qmake6 \
      -spec linux-aarch64-gnu-g++ CONFIG+=release ../mon-cli.pro && \
  make -j$(nproc) )

Install

# GUI
sudo dpkg -i output/qtmonitor_0.0.3-1_amd64.deb

# CLI (optional — can be installed independently on headless hosts)
sudo dpkg -i output/mon-cli_0.0.3-1_amd64.deb

sudo apt -f install   # resolve any missing deps

The mon-cli package depends only on libqt6core6*, libqt6network6, libsensors5 — no Qt Gui / Widgets / OpenGL — so it's safe to install on servers without a display stack.


Run

GUI

qtmonitor
# or: /usr/bin/qtmonitor

The first launch shows a warning if lm-sensors is not initialized; the program falls back to a UI-only mode. Run sensors-detect once to enable real readings.

CLI

mon-cli                    # interactive menu
mon-cli monitor            # live table, 100 ms refresh
mon-cli sensors            # one-shot dump
mon-cli sensors --json     # machine-readable output

CLI demo for remote sync

# Terminal 1: receiver
python3 -u scripts/cli_demo_remote_sync.py
# Listening on http://0.0.0.0:8000/ingest

# Terminal 2 (GUI): Tools → Remote Sync → URL http://127.0.0.1:8000/ingest

# OR Terminal 2 (CLI):
mon-cli sync --url http://127.0.0.1:8000/ingest

The CLI receiver prints formatted JSON:

[2026-07-21T11:40:00] hostname=workstation interval_ms=1000
  coretemp-isa-0000/Core 0                       49.50 C
  coretemp-isa-0000/Core 1                       51.25 C

You can also chain two mon-cli instances:

# Host A
mon-cli collect --port 8000
# Host B
mon-cli sync --url http://host-a:8000/ingest

Translations

translationmanager.cpp is a single-source dictionary covering both languages. The QT_TR(key) macro resolves the current language's text at runtime. (GUI only — mon-cli is English-only.)

Notes

  • The program runs completely offline; remote sync is opt-in to a user-configured URL.
  • All CPU work stays in the GUI thread; the chart and table refresh on a configurable timer (40 ms ~ 5 s).
  • Memory is bounded by the data window: temperaturemodel.cpp trims samples older than the configured retention window roughly once per second, and spills older history to temporary files on disk when the in-memory budget is exceeded.
  • The arm64 .deb is cross-compiled; install it on Jetson / Raspberry Pi / Apple Silicon (Asahi) systems.
  • On systems without hardware OpenGL, Qt automatically uses a software raster path through QOpenGLWidget's internal fallback, so the same GUI binary works on headless or GPU-less machines. mon-cli has no GL dependency at all.
  • mon-cli logger writes its live status to /run/mon-cli/logger.status (when running as a system service) or /tmp/mon-cli-<uid>/logger.status (foreground, non-root). mon-cli monitor and mon-cli status pick up whichever exists.

About

A qt6 based hardware temperature monitor.Supports local and remote collection.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages