Skip to content

Releases: TekuSP/SubZeroFramework

v0.1.5 - NPU/GPU work for Windows and Linux

Choose a tag to compare

@TekuSP TekuSP released this 26 Jul 23:12
45406bf

Added

  • Windows: live GPU and NPU utilization. Each graphics adapter's detail now carries its own live
    utilization — the current percentage over a rolling 30-second graph, the same card the CPU uses per core —
    and a new Neural processor category presents the NPU the way the CPU category presents packages: a
    count, a processor list, and the picked device's detail. On a Framework 16 that is the integrated Radeon,
    the discrete GPU module and the Ryzen AI NPU, each with its own reading, never blended into one number.
    The service reads the Windows GPU Engine performance counters through one persistent PDH query (~1.5 ms per
    second-tick, measured) and derives busy time per adapter the same way Task Manager does; devices are named
    via SetupAPI and matched by adapter LUID. Software adapters with no physical device behind them (WARP) are
    excluded. The Windows readers and their interop are compiled only into Windows builds — Linux ships none of
    it, needs nothing new at install time, and shows an honest empty state until its per-vendor readers land.

  • Linux: Device Capabilities finally shows graphics and displays. The page was empty on Linux because
    Hardware.Info enumerates both lists by shelling out to xrandr, which needs a display server the background
    service does not have. They now come from the kernel instead: adapters from /sys/class/drm with their
    names resolved through the system pci.ids database, and displays from each DRM connector's EDID — model,
    manufacturer, product code, serial, manufacture date, physical size and density. Works headless, and
    identically under X11, Wayland or no session at all. The current resolution and refresh rate come from the
    kernel's mode-setting state, so a 165 Hz panel reads 165 Hz rather than the 60 Hz its EDID advertises as
    "preferred". Because a connector belongs to its card by kernel topology, each display is attributed to the
    right GPU exactly, instead of by matching names as on Windows.

  • Linux: GPU utilization for AMD, NVIDIA and Intel. Same cards as on Windows, one per GPU. AMD reads the
    amdgpu driver's own gpu_busy_percent. NVIDIA goes through NVML, loaded at runtime only if the proprietary
    driver is already installed — there is no new package dependency, and on an AMD-only machine the attempt is
    silently skipped. Intel reads the i915 or xe performance counters, discovering the available engines from
    what the driver advertises rather than assuming a layout.

    Sleeping GPUs are not woken to be measured. Reading AMD's busy attribute reaches the SMU, and an NVML query
    takes a power-management reference on the card; either can hold a discrete GPU awake, which is how monitoring
    tools have historically ruined laptop battery life. A GPU whose power state already reads "suspended" is
    reported as 0% — which is what it is — without being touched.

    Each source is independent and optional: one vendor's driver misbehaving cannot blank out the others. Where
    a reading genuinely cannot be taken the device simply reports nothing. In particular Intel's counters need
    kernel 6.15 or newer on the newest GPUs (the xe driver), and the alternatives — inferring load from power
    residency, or summing per-process figures — were rejected as misleading rather than shipped as an
    approximation.

  • Linux: NPU utilization. Intel NPUs (Core Ultra) report through the driver's busy-time counter, which
    costs nothing to read and leaves an idle NPU asleep. AMD Ryzen AI NPUs report per-column utilization, on
    kernels new enough to expose it and on the Ryzen AI 300 parts where it is wired up; where it is not
    available the device is still listed, just without a reading. As with the GPUs, a sleeping NPU is reported
    as 0% rather than being woken to be asked — for AMD that gate is the difference between a monitoring
    feature and a battery-life regression.

  • The Neural processor page now describes the hardware, not just its load. Vendor, driver and driver
    version, firmware version, location and description sit alongside the utilization graph, the way the CPU
    and graphics pages have always shown their devices. Windows reads them from the device properties; Linux
    from the kernel's accelerator class, including the NPU firmware version where the driver publishes one.

  • Device Capabilities categories with nothing in them are disabled. A category whose body could only show
    an empty state no longer opens: the rail entry dims and says why on hover ("No graphics detected"). Onboard
    devices and System profile always stay open. This is what a Linux machine sees for Graphics today, and what
    any machine sees for a category its hardware does not have.

  • Settings → Service logs. Shows what the background service has logged since it started, with a level
    filter and a one-click "Copy all" for pasting into a bug report — no more hunting through Event Viewer or
    journalctl. The service mirrors its log into a bounded in-memory buffer (the last 2,000 entries) that sits
    alongside the platform sinks rather than replacing them; when older entries have been dropped the page says
    so instead of implying a complete history.

Changed

  • Settings ▸ Service logs is now Settings ▸ Logs, and shows the app's logs too. The page only ever showed
    the background service. The app's own records had nowhere to go on Windows at all: the desktop head is a
    GUI-subsystem binary, so its console sink writes to a console that does not exist and its debug sink only
    exists under a debugger. A released build could warn about a broken service connection every second and none
    of it would reach you. The app now keeps the same bounded buffer the service does, and the page interleaves
    both by timestamp with a source column and a Service / App / Both filter — so a client reconnect warning
    reads directly next to the service restart that caused it. Copy-all tags each line with its source, because
    "which process said this" is the first question asked of a pasted log. If the service cannot be reached, its
    half fails alone and the app's own entries still appear — those are exactly the ones explaining why it looks
    dead.

  • Telemetry no longer reaches the UI faster than the UI can use it. Thirty-five subscriptions across the
    view models and telemetry clients inherited the service's poll cadence directly, so lowering the poll
    interval multiplied UI work with it. Each now states its own ceiling — 250 ms for live readouts, 500 ms for
    chart history, 1 s for inventory — applied before marshalling to the UI thread, so coalescing happens off
    it. These are ceilings, not floors: the operators only emit when something actually arrived, so a
    one-second poll still updates once a second.

Fixed

  • Two code-analysis rules had been silently inert. SZF0004 (require ObserveOn) and SZF0005 (require
    DisposeWith) skipped every subscription whose receiver was typed as IObservable<T> — which is nearly all
    of them, since every Watch*/Connect* method and every Rx operator returns the interface. The type check
    compared a display string (System.IObservable<T>) against a metadata name (System.IObservable`1),
    which never matches for a generic, and the interface check looked at implemented interfaces, which do not
    include the type itself. Both rules now apply where they always should have.

    Fixing that exposed ~53 violations, and working through them showed the rules had been over-broad as well as
    under-applied. They now recognise the legitimate patterns they used to flag: Observable.Create factories
    (whose disposal is the returned disposable, and whose scheduler is the consumer's choice), ownership by
    storage in a member, SerialDisposable slots and keyed registries, using var, returning the subscription
    to a caller, and marshalling inside the handler via the dispatcher rather than upstream. ObserveOn is also
    no longer required in the service and Core, which have no UI thread for it to marshal to.

  • New rule: SZF0013, telemetry subscriptions must rate-limit. Flags a subscription that starts at a
    per-poll telemetry source without bounding its rate. The operator it asks for depends on the stream: change
    sets must use Batch, which coalesces, because Sample and Throttle drop items and a dropped change set
    loses an add or a remove permanently — DynamicData ships no Sample/Throttle for change sets for exactly
    this reason. Snapshot streams carry the whole value each time and may use either.

  • Uninstalling from inside the app now uninstalls the app. The Uninstall button used to delete the
    background service's registration — the same registration the Windows installer owns — so removing SubZero
    afterwards through Add/Remove Programs left Windows Installer deleting a service that was already gone. On
    an installed build the button now hands over to the real uninstaller and closes the app so its files can be
    removed; the installer stops and removes the service itself. It asks first, and says plainly that saved fan
    profiles are kept in case you reinstall. On a development build, where there is no installer, it still
    removes just the service and the button says so.

Fixed

  • Error status text was unreadable. "Unavailable", "Stalled", "Not Present", a critical battery and a
    ≥85 °C reading were drawn in the palette's dark red fill tone (#442726) as foreground — near-invisible on
    the dark card. They now use the error text tone, like the rest of the app. Same swap for the storage/memory
    usage bar past 90%, whose fill was the odd one out next to its bright green and amber siblings.
  • A newer service could kill the telemetry stream on an older client. The app's copy of the wire enum
    mapper parsed an unknown value int...
Read more

v0.1.4 - Fix sensor availability no longer rewrites fan profiles or the UI

Choose a tag to compare

@TekuSP TekuSP released this 25 Jul 18:08
854a061

Added

  • "Treat missing readings as 0°" per curve profile. A selected driving sensor that loses power counts as
    cold instead of dropping out — for sensors that go quiet because whatever they measure is switched off, like
    a sleeping GPU. With Maximum aggregation a dark sensor then simply stops mattering; with the other modes it
    pulls the driving temperature down, so the editor warns about that combination.
  • The curve chart shows where fan control actually is. A red ✕ rides the curve at the live driving
    temperature and the duty the service last applied. Because that duty already includes the CPU boost, the
    marker can sit above the drawn curve — the gap is the boost. It disappears whenever no driving temperature
    can be read, rather than freezing at a position the fan is no longer holding.
  • Windows installer offers a clean install. A new wizard page (before the install location) asks whether
    to keep existing fan settings; ticking it deletes the service's saved configuration instead of upgrading
    onto it. Off by default, and available unattended as msiexec /i … CLEANINSTALL=1. Per-user preferences
    (display units, startup, alerts) are not touched — the installer only owns machine-wide service data.

Fixed

  • A sleeping GPU rewrote the fan's saved profile. When a driving sensor became unavailable the editor
    deselected it and adopted whichever sensor happened to be first (Mainboard), and the next Apply persisted
    that — the curve silently stopped following the GPU for good. Availability is runtime status, not user data:
    a selection now survives its sensors going dark, comes back when they return, and is only auto-seeded for a
    slot that has no selection at all.
  • A powered-down sensor could under-cool the machine. The service read every selected sensor's raw value
    without checking whether it was reporting, so a dark sensor's 0 °C was folded into the aggregate — halving
    an Average against a hot CPU. Sensor state is now honored, and the client and service share one
    implementation of the reduction (FanDrivingTemperature), so the predicted duty matches what the fan does.
  • "Falls back to its firmware-safe curve" is now true. When no driving sensor of a curve-driven fan could
    be read, the service simply skipped the fan — leaving the embedded controller holding the last duty the
    curve asked for, with nothing observing the heat. It now hands that fan back to firmware fan control (once
    per episode, retried if the handover fails) and resumes the curve automatically when a sensor reports again.
    The stored profile is never altered by the fallback.
  • Opening Fan Control showed the Auto panel for a fan that was not on Auto. The mode selector correctly
    read "Custom curve" while the body below it described automatic control. The mode body is a navigation
    sub-region whose navigator does not exist yet on first entry; the sync gave up in that case and nothing ever
    retried, because a fan already running a curve never raises a mode change to retrigger it.
  • The sensor list changed length when the GPU powered down. Its four sensors do not report the same way —
    three go "not powered" while the die sensor goes "not present" — and a not-present sensor was deleted from
    the selector outright, so eight chips became seven and a selected sensor disappeared from view. A sensor
    that has never reported is still omitted, but one already on screen now stays put and dims.
  • An unpowered sensor could not be picked as a driving sensor. Its chip was disabled, so a GPU sensor was
    unselectable exactly when you wanted to choose it (with the GPU idle) — and a selected sensor that went dark
    could not be deselected either. Chips now stay interactive and simply dim while they have no reading.
  • Windows: the taskbar showed a generic window icon. An unpackaged WinUI 3 window does not inherit the
    icon embedded in the executable, so the app icon appeared in Explorer but not on the taskbar or in Alt+Tab.

Full Changelog: v0.1.3...v0.1.4

v0.1.3 - Fixed out of bounds custom curves issues and added reset to factory settings

Choose a tag to compare

@TekuSP TekuSP released this 25 Jul 16:17
6bca003

What's Changed

  • Bump Uno.Sdk from 6.7.0-dev.137 to 6.7.0-dev.139 by @dependabot[bot] in #53
  • Bump Grpc.Tools from 2.82.0 to 2.83.0 by @dependabot[bot] in #52

Added

  • Settings → Service: "Reset fan settings to factory defaults". Returns every fan to the controller's
    automatic mode and deletes all saved fan settings in one confirmed action — curve profiles in every slot,
    the active profile per fan, "Applies to" links, CPU boost, and manual/max overrides — including entries for
    fan indices the hardware no longer reports, which no per-fan action could reach. Display units, startup and
    alert settings are untouched.

Fixed

  • Curve points could be dragged off the chart and stranded there. The editor let a point be parked
    outside the plotted temperature window (dragging past the plot edge keeps producing coordinates out in the
    axis margin), where it was invisible and could never be grabbed again — the curve became uneditable. Points
    now snap into a band inside the visible window, and curves already holding stranded points are pulled back
    when they load. Pressing empty chart space also picks the new point straight up, so press-drag places a
    point in one motion.
  • A curve no longer flatlines above its last point. The hidden anchor closing every curve at the top of
    the temperature range inherited the last point's duty, so a curve ending at 0 % (or any low duty) held that
    speed all the way to 130 °C instead of climbing. It is now pinned to full speed, matching the idle anchor at
    the cold end: every curve ramps from idle to maximum across the range, however early the user's own points
    stop. The client's drawn curve, its predicted-duty readout, and the service's actuation now share ONE
    implementation of that rule (FanCurveDomain), so a preview cannot promise a speed the fan does not deliver.

Full Changelog: v0.1.2-alpha...v0.1.3

v0.1.2-Alpha Fixed CPU usage spikes from lshw

Choose a tag to compare

@TekuSP TekuSP released this 23 Jul 13:57
6b619f2

v0.1.1-Alpha Fixed empty bay detection on Framework 16

Choose a tag to compare

@TekuSP TekuSP released this 23 Jul 08:44
44d6eb8

What's Changed

  • Bump Uno.Sdk from 6.7.0-dev.127 to 6.7.0-dev.137 by @dependabot[bot] in #49
  • Fixed If expansion bay is empty, application incorrectly assumes error state
  • Added INSTALL.MD with guide how to install the app

Full Changelog: v0.1.0-alpha...v0.1.1-alpha

Installation

  • Debian
    • sudo apt install ./subzeroframework-service_0.1.1_amd64.deb ./subzeroframework_0.1.1_amd64.deb
  • Windows
    • Run SubZeroFramework-Setup-0.1.1-x64.msi
  • Fedora
    • sudo dnf install ./subzeroframework-service-0.1.1-1.x86_64.rpm ./subzeroframework-0.1.1-1.x86_64.rpm
  • Arch
sudo pacman -U subzeroframework-bin-0.1.1-1-x86_64.pkg.tar.zst
sudo systemctl enable --now subzeroframework.service
  • Other Linux distros:
tar -xzf subzeroframework-0.1.0-linux-x64.tar.gz
cd subzeroframework-0.1.0
sudo mkdir -p /usr/lib/subzeroframework
sudo cp -r ui service /usr/lib/subzeroframework/
sudo ln -sf /usr/lib/subzeroframework/ui/SubZeroFramework /usr/bin/subzeroframework
sudo cp subzeroframework.service /usr/lib/systemd/system/
sudo cp subzeroframework.desktop /usr/share/applications/
sudo systemctl daemon-reload
sudo systemctl enable --now subzeroframework.service

v0.1.0-alpha Initial Release of SubZero Framework Edition

Choose a tag to compare

@TekuSP TekuSP released this 22 Jul 19:22
7bbd65e

This is it, first release of SubZero Framework Edition fan control.
There can be bugs, please report them via Issues.

Whole project is Open-Source and I am glad Framework gave permission for their logo and brand.

Download via Assets below for your platform. You need both service + app for most Linux distros.
Arch and Windows have it combined.

Full Changelog: https://github.com/TekuSP/SubZeroFramework/commits/v0.1.0-alpha