Linux hwmon driver and daemon for the Thermal Grizzly WireView Pro II power monitor. Exposes voltage, current, power, and temperature sensor data through the standard Linux hwmon subsystem.
Both editions of the WireView Pro II are supported: the original and the WireView Pro II Noctua Edition, which speaks the same protocol.
Works standalone or alongside the wireview-linux GUI application. When both are used together, the app reads sensor data from hwmon and sends commands through the daemon's Unix socket - giving you full app functionality plus system-wide sensor integration.
WireView Pro II (USB) → wireviewd (serial) → kernel module → /sys/class/hwmon/ → monitoring tools
└─────────→ HTTP /sensors, /metrics (opt-in) → LAN: other hosts, the GUI app, wireviewctl, Prometheus
- wireview_hwmon.ko - Kernel module that creates a virtual hwmon device
- wireviewd - Userspace daemon that reads the device over serial, feeds the kernel module, and (opt-in) publishes readings + accepts authenticated commands over the LAN. It tells the two editions apart by the product id the device reports (
EF05WireView Pro II,EF06Noctua Edition) and passes it on to clients - wireviewctl - CLI tool for querying sensors, sending commands, firmware flashing (
flash), and atoplive monitor
sudo add-apt-repository ppa:sparvoli/wireview-hwmon
sudo apt update
sudo apt install wireview-hwmon wireview-hwmon-dkmsThis installs the daemon, CLI tool, kernel module (via DKMS), systemd service, and udev rules. The module auto-rebuilds on kernel updates and the daemon starts automatically.
Pre-built .deb packages are available on the Releases page. Download both and install from the download directory:
sudo apt install ./wireview-hwmon_*_amd64.deb ./wireview-hwmon-dkms_*_all.debsudo dnf copr enable emaspa/wireview-linux
sudo dnf install wireview-hwmon wireview-hwmon-dkms
sudo systemctl enable --now wireviewdThe same COPR repo also provides the WireView GUI (wireview-linux). The DKMS module pulls kernel-devel for your running kernel and rebuilds automatically on kernel updates.
Pre-built .rpm packages are on the Releases page (one set works on Fedora 43-44):
sudo dnf install ./wireview-hwmon-*.x86_64.rpm ./wireview-hwmon-dkms-*.noarch.rpm
sudo systemctl enable --now wireviewdparu -S wireview-hwmon wireview-hwmon-dkms # or: yay -S
sudo modprobe wireview_hwmon
sudo systemctl enable --now wireviewdVerify with sensors (or wireviewctl info); a wireview-named hwmon device should appear. From the next boot onward everything comes up automatically: the dkms package registers the module in modules-load.d and the service also modprobes it on start.
DKMS needs the matching kernel headers (linux-headers, linux-cachyos-headers, …) installed; the module then rebuilds automatically on kernel updates.
The repository is a flake with a NixOS module. It builds the kernel module for
the kernel in boot.kernelPackages, loads it at boot, installs the udev rules
and runs wireviewd:
{
inputs.wireview-hwmon.url = "github:emaspa/wireview-hwmon";
outputs = { nixpkgs, wireview-hwmon, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
system = "x86_64-linux";
modules = [
./configuration.nix
wireview-hwmon.nixosModules.default
{
services.wireview-hwmon.enable = true;
# Privileged daemon commands: config writes, flashing, the GUI's log reads
users.users.alice.extraGroups = [ "wireview" ];
}
];
};
};
}wireviewctl flash finds the bundled firmware in the Nix store and has
dfu-util on its path. The optional daemon config goes in
/etc/wireview/config, which the module does not generate because it may hold
the LAN secret. nix build github:emaspa/wireview-hwmon builds the daemon and
CLI alone. The package is marked unfree because it bundles Thermal Grizzly's
firmware image. The flake allows it on its own, so no allowUnfree setting is
needed.
Immutable / atomic distros (Bazzite, Silverblue, Kinoite) are not supported for the kernel module - DKMS doesn't fit rpm-ostree. On those, run the WireView GUI Flatpak in direct-serial mode, which doesn't need this module.
wireviewctl flash with no file argument flashes Thermal Grizzly's official
firmware image from /usr/share/wireview/TG-WV-PRO2-FW.hex, or from the
package in the Nix store on NixOS: v05, build
TG-WV-PRO2-FW_20260902_0741, from the upstream WireView2 1.0.8 Windows
release. The one image serves both the WireView Pro II and the Noctua Edition.
It comes with the wireview-hwmon package and with make install. The image
is proprietary and not covered by the GPL, see
firmware/README.md.
- Linux 6.8 or newer with kernel headers (
linux-headers-$(uname -r)); older kernels are not supported - A Thermal Grizzly WireView Pro II device connected via USB
gccandmake
git clone https://github.com/emaspa/wireview-hwmon.git
cd wireview-hwmon
makeThis builds the kernel module (wireview_hwmon.ko), the daemon (wireviewd), and the CLI tool (wireviewctl).
# Install udev rules (serial port and DFU bootloader access)
sudo cp 99-wireview-hwmon.rules /etc/udev/rules.d/
# Arch-based distros have no dialout group, their serial group is uucp
getent group dialout >/dev/null || sudo sed -i 's/GROUP="dialout"/GROUP="uucp"/g' /etc/udev/rules.d/99-wireview-hwmon.rules
sudo udevadm control --reload-rules
sudo udevadm trigger # or replug the device: new permissions apply on the next device event
# Load the kernel module
sudo insmod wireview_hwmon.ko
# Run the daemon
sudo ./wireviewd
# In another terminal, check sensor data
sensors wireview-isa-0000sudo make install # installs module, daemon, udev rules, and systemd service
# Auto-load module on boot
echo wireview_hwmon | sudo tee /etc/modules-load.d/wireview-hwmon.conf
# Enable and start the daemon service
sudo systemctl enable --now wireviewd
# Load the module now (or reboot)
sudo modprobe wireview_hwmon
# Verify
sensors wireview-isa-0000After rebooting, both the module and daemon will start automatically.
make install puts the udev rules and the systemd unit in /etc/udev/rules.d/
and /etc/systemd/system/, and those copies override the packaged ones under
/usr/lib. After pulling a new version, re-run sudo make install,
then sudo systemctl restart wireviewd and replug the device (or run
sudo udevadm trigger). If you have switched to a distro package, delete the
old copies instead:
sudo rm /etc/udev/rules.d/99-wireview-hwmon.rules /etc/systemd/system/wireviewd.service
sudo udevadm control --reload-rules && sudo systemctl daemon-reloadIf Secure Boot is enabled, the kernel will refuse to load unsigned modules (Key was rejected by service). You have two options:
Option 1: Disable Secure Boot (easiest)
Reboot, enter BIOS/UEFI settings, disable Secure Boot, then boot back in and load the module normally.
Option 2: Sign the module (keeps Secure Boot enabled)
# Generate a signing key (one-time)
sudo mkdir -p /var/lib/shim-signed/mok
sudo openssl req -new -x509 -newkey rsa:2048 \
-keyout /var/lib/shim-signed/mok/MOK.priv \
-outform DER -out /var/lib/shim-signed/mok/MOK.der \
-nodes -days 36500 -subj "/CN=Local Module Signing/"
# Enroll the key (requires reboot to confirm in MokManager)
sudo mokutil --import /var/lib/shim-signed/mok/MOK.der
# Reboot - MokManager will prompt you to enroll the key
# After reboot, sign the module
sudo /usr/src/linux-headers-$(uname -r)/scripts/sign-file sha256 \
/var/lib/shim-signed/mok/MOK.priv \
/var/lib/shim-signed/mok/MOK.der \
/lib/modules/$(uname -r)/updates/wireview_hwmon.ko
# Now it loads
sudo modprobe wireview_hwmonYou only need to generate and enroll the key once. After a kernel update, re-sign the module or rebuild with sudo make install and sign again.
sudo make uninstallwireviewd [-i interval_ms] [-d /dev/ttyACMx] [-V]
-i Poll interval in milliseconds (default: 1000)
-d Serial device path (default: auto-detect)
-V Print the version and exit
The systemd unit runs wireviewd as root but sandboxed and with no
capabilities. In practice:
- It can write only to
/run(the command socket) and/var/log/wireview; audit logs are created mode600./etc/wireviewis read-only to it. - A
port=below 1024 in/etc/wireview/configfails to bind, since that needsCAP_NET_BIND_SERVICEand the unit drops all capabilities. - The device policy allows only CDC-ACM serial ports and
/dev/wireview-hwmon, so-daccepts only/dev/ttyACM*devices.
The unit deliberately does not use a private user namespace: the daemon
identifies command-socket clients by their real user id to check wireview
group membership.
Running ./wireviewd by hand, as in the Quick start, is not sandboxed.
| Sensor | hwmon attribute | Unit |
|---|---|---|
| Pin 1-6 Voltage | in0_input - in5_input |
millivolts |
| Average Voltage | in6_input |
millivolts |
| Supply Voltage (Vdd) | in7_input |
millivolts |
| Pin 1-6 Current | curr1_input - curr6_input |
milliamps |
| Total Current | curr7_input |
milliamps |
| Total Power | power1_input |
microwatts |
| Pin 1-6 Power | power2_input - power7_input |
microwatts |
| Onboard Temp In | temp1_input |
millidegrees C |
| Onboard Temp Out | temp2_input |
millidegrees C |
| External Temp 1 | temp3_input |
millidegrees C |
| External Temp 2 | temp4_input |
millidegrees C |
| Total Energy | energy1_input |
microjoules since wireviewd started |
| PSU Capability | power1_cap |
microwatts (600/450/300/150 W; no data if unknown) |
| Fan Duty | pwm1 |
0-255 |
| Per-channel alarms | temp1_alarm - temp4_alarm, curr1_alarm - curr7_alarm, power1_alarm |
0/1 |
| Fault Status | intrusion0_alarm (intrusion0_label) |
0/1 |
| Fault Log | intrusion1_alarm (intrusion1_label) |
0/1 |
| Fault Status (raw) | fault_status_raw |
bitmask |
| Fault Log (raw) | fault_log_raw |
bitmask |
All voltage, current, power, temperature, and energy channels also expose _label attributes for tool-friendly names.
The alarms follow the active fault bits (fault_status, debounced by the
daemon), not the fault log:
| Bit | Fault | Alarms |
|---|---|---|
| 0 | Chip over-temperature | temp1_alarm, temp2_alarm |
| 1 | Sensor over-temperature | temp3_alarm, temp4_alarm |
| 2 | Over-current (total) | curr7_alarm |
| 3 | Wire over-current | curr1_alarm - curr6_alarm |
| 4 | Over-power | power1_alarm |
| 5 | Current imbalance | curr1_alarm - curr7_alarm |
The device does not say which wire tripped bit 3, so all six per-pin alarms report it.
The device reports power, not energy, so wireviewd integrates it: each
accepted frame adds the total power times the time since the previous frame.
The counter survives device reconnects and resets when the daemon restarts. A
gap longer than 5 s (or three poll intervals, if -i makes that longer), such
as an unplug, a serial handover or a system suspend, is not counted. It is
published as energy1_input, as "energyJ" in GET /sensors and as
wireview_energy_joules_total on GET /metrics.
Module and daemon must both be this release or newer. An older module has no
energy1_input (the daemon notices and keeps feeding it the old record), and
with an older daemon the new module reports no data (ENODATA) for it.
wireview-isa-0000
Adapter: ISA adapter
Pin 1: 12.12 V
Pin 2: 12.13 V
Pin 3: 12.11 V
Pin 4: 12.12 V
Pin 5: 12.11 V
Pin 6: 12.12 V
Average: 12.12 V
Vdd: 3.30 V
Onboard In: +45.3°C
Onboard Out: +42.1°C
External 1: +38.7°C
External 2: N/A
Total: 389.36 W (cap = 600.00 W)
Pin 1: 63.39 W
Pin 2: 66.11 W
Pin 3: 62.00 W
Pin 4: 64.72 W
Pin 5: 67.33 W
Pin 6: 65.81 W
Total: 1.37 MJ
Pin 1: 5.23 A
Pin 2: 5.45 A
Pin 3: 5.12 A
Pin 4: 5.34 A
Pin 5: 5.56 A
Pin 6: 5.43 A
Total: 32.13 A
pwm1: 75%
Fault Status: OK
Fault Log: ALARM
sensors prints pwm1 as a percentage (191 of 255 shows as 75%). A channel
whose alarm is set gets ALARM at the end of its line.
wireviewctl lets you query sensor data and send device commands from the terminal or scripts.
Usage: wireviewctl [--host H[:port] [--secret-file FILE]] <command> [args]
Global options:
--host H[:port] Talk to wireviewd on H over HTTP (default port 9876)
instead of the local socket: info, build, sensors and
read-config read; screen, nvm, clear-faults and
write-config are signed with the shared secret.
bootloader and flash are local only.
--secret-file FILE Shared secret for signed writes: the passphrase, or a
file with a secret= line (like /etc/wireview/config).
Without it, $WIREVIEW_SECRET is used.
Commands (require wireviewd running):
info Show device firmware, UID, build, product and edition
clear-faults [STATUS_MASK [LOG_MASK]]
Clear faults. Masks are hex bits to clear (default FFFF,
i.e. all active faults and the whole fault log)
read-config Read device config (hex to stdout)
write-config FILE Write device config (hex from file)
screen CMD Change display (main|simple|current|temp|status|same|pause|resume)
nvm CMD NVM operation (load|store|reset|load-cal|store-cal|load-cal-factory|store-cal-factory)
build Show firmware build string
bootloader Enter DFU bootloader mode
flash [FILE] [-y] [--force]
Flash firmware (.hex or .bin) via DFU (needs dfu-util;
works without the daemon if the bootloader is already up).
Without FILE, flashes the bundled image at
/usr/share/wireview/TG-WV-PRO2-FW.hex
Refuses an image for another product, the build the
device runs, an older build, and on a Noctua Edition
a build before 2026-09-02; --force overrides that.
-y only skips the confirmation prompt.
Commands (require wireview_hwmon module):
sensors [--json] Show all sensor readings from hwmon sysfs; --json prints
the same schema as the daemon's GET /sensors
Monitor:
top [--host H[:port][,H2...]]... [--interval MS]
Live dashboard: the local device plus remote hosts.
--host repeats and/or takes a comma/space list; hosts are
also read from /etc/wireview/hosts, and the global
--host (before "top") adds one more. Press q to quit.
Other:
-V, --version Print the wireviewctl version
info ends with the device's product and edition:
product: EF06
edition: WireView Pro II Noctua Edition
A daemon that does not report the product yet (wireview-hwmon 1.6.0 and
earlier) only runs Pro II devices, so info prints product: EF05 (assumed: this wireviewd does not report it) and edition: WireView Pro II for it.
top and sensors --json show the edition name the same way.
clear-faults with no arguments clears every active fault and the whole fault
log (masks FFFF FFFF). STATUS_MASK and LOG_MASK are 16-bit hex bitmasks,
with or without 0x: a set bit clears that fault, and a missing mask defaults
to FFFF. So wireviewctl clear-faults 0 FFFF clears only the log, and
wireviewctl clear-faults 0x0004 clears status bit 2 plus the whole log.
(On the wire the firmware takes the inverse, a keep-mask; wireviewctl does
the inversion, the raw socket and HTTP clearFaults do not.)
flash checks the whole image before it runs dfu-util or asks the daemon
for the bootloader. In an Intel HEX file every record must hold only hex
digits, exactly the bytes its count declares and a correct checksum; the file
must end with an end-of-file record (type 01); data must fall in the 4 MiB
flash window from 0x08000000, and no address may be given twice. The error
names the file and line (wireviewctl: fw.hex: line 812: bad checksum) and
nothing reaches the device. A .bin has no checksums to check: it is loaded
at 0x08000000 as is.
Then flash compares the image with the device, before it asks anything of
the device or the bootloader. It reads the image's BuildStruct (vendor and
product id, firmware version, product name and build string, at image offset
192) and asks the daemon for the device's, and prints both with a verdict:
device: WireView Pro II (EF05, assumed), firmware v05, build TG-WV-PRO2-FW_20260706_1047
image: Thermal Grizzly WireView Pro II (EF05), firmware v05, build TG-WV-PRO2-FW_20260902_0741
verdict: newer: the image is newer than the device firmware
- Product: the image vendor must be the device's, and the image product
the device's product after the upstream alias table, which maps the Noctua
Edition (
EF06) to the Pro II image (EF05). An image for another product, or one too short to carry the ids, is refused. - Noctua Edition: images built before 2026-09-02 do not know that edition, so a Noctua Edition device refuses them, and any image whose build string has no date.
- Build: the firmware version byte decides first; with the same version,
the build time in the build string (
..._yyyyMMdd_HHmm, as upstream reads it). An older image is refused, and so is the build the device already runs. A newer image goes on to the usual confirmation. When either build string has no date, the verdict isunknown:flashsays which one and asks as usual, with a warning.
--force flashes despite a refusal. -y only skips the confirmation prompt
and never overrides a gate, so wireviewctl flash -y stays a safe headless
update: it flashes a newer build and refuses the rest. When the daemon is not
running or has no device (for example when the bootloader is already up),
flash says that it cannot verify the image against the device and relies on
the confirmation or -y, as before. A daemon that does not report the product
(wireview-hwmon 1.6.0 and earlier) only runs Pro II devices, so the device
counts as EF05 there.
Commands that can change or brick the device are accepted only from root or
members of the wireview system group: bootloader, flash, nvm and
write-config (and, for other clients such as the GUI app, the serial
handover used for log reads, theme uploads and in-app flashing). Everything
else (info, sensors, top, read-config, screen, clear-faults,
build) works for any user.
The deb, rpm and AUR packages and make install create the group. Add
yourself; it takes effect on the next connection, no re-login needed:
sudo usermod -aG wireview $USERflash needs the group only to ask the daemon to enter the bootloader. The
flashing itself runs dfu-util as you, which needs the seat or dialout
access described below; with the device already in DFU mode, that is all it
needs.
The udev rules give the serial port (0483:5740) and the STM32 DFU bootloader
(0483:df11, the device after wireviewctl bootloader) mode 660 in the
serial group, plus an ACL for the user logged in at the local seat (uaccess). In
a local desktop session the GUI app's direct-serial mode, wireviewctl flash
and dfu-util therefore work without sudo. SSH and other remote sessions get
no seat ACL; join the serial group and log in again:
# Debian, Ubuntu, Fedora and most other distros
sudo usermod -aG dialout $USER
# Arch-based distros
sudo usermod -aG uucp $USERThe serial group is dialout, or uucp on Arch-based distros. The serial port
gets it from the distro's own udev rules, so the rules file works unchanged
everywhere. The DFU bootloader line names dialout, and the AUR package and
make install change it to uucp on Arch.
/dev/wireview-hwmon, the node wireviewd feeds readings into, is root-only
(600) on purpose: anything that can write to it can inject fake readings into
hwmon, and wireviewd is its only writer. Monitoring tools read
/sys/class/hwmon/, which stays world-readable.
The rules also tell ModemManager to ignore the device. Without that it probes
every new CDC-ACM port as a possible modem for about half a minute, which
keeps the port busy and delays wireviewd after a plug-in or a restart.
# Show device info: firmware, UID, build, product and edition
wireviewctl info
# Read all sensors (scriptable key: value format)
wireviewctl sensors
# Live dashboard of this host and remotes (q to quit) - see "LAN monitoring" below
wireviewctl top --host 192.168.1.50
# Switch to simple display
wireviewctl screen simple
# Update the device firmware to the bundled image (no download needed);
# add -y to skip the confirmation prompt for headless updates. The same or
# an older build, or an image for another product, is refused (--force
# overrides).
# Unofficial tool, not affiliated with Thermal Grizzly: flash at your own
# risk. A power loss mid-flash can leave the device unbootable until
# reflashed manually.
wireviewctl flash
# Clear only the fault log, keep active faults
wireviewctl clear-faults 0 FFFF
# Back up and restore config (write-config needs the wireview group)
wireviewctl read-config > config.hex
wireviewctl write-config config.hex
# Store config to NVM
wireviewctl nvm store
# Use in scripts
POWER=$(wireviewctl sensors | grep total_power_uw | cut -d' ' -f2)
echo "Total power: $((POWER / 1000000)) W"
# Same readings as JSON, for jq and friends
wireviewctl sensors --json | jq '.devices[0].sumPowerW'
# Switch the display of a WireView on another machine
wireviewctl --host 192.168.1.50 --secret-file ~/.config/wireview-secret screen simplewireviewctl sensors prints one key: value line per reading, in sysfs units
(_mv, _ma, _uw, _mc), followed by:
fan_duty: 75
fault_status: 0
fault_log: 4
psu_cap: 600W
energy_uj: 1372480512000
alarm_temp_onboard_in: 0
...
alarm_total_power: 0
fan_dutyis in % (0-100), frompwm1, or fromfan1_inputon an older module.psu_capis frompower1_cap, or thepsu_capenum on an older module;unknownwhen the device reports no PSU capability.energy_ujappears when the module hasenergy1_input.- The
alarm_<name>lines, one per alarm attribute, are named after the channel:alarm_temp_onboard_in,alarm_temp_onboard_out,alarm_temp_external1,alarm_temp_external2,alarm_pin1_current-alarm_pin6_current,alarm_total_currentandalarm_total_power.
wireviewctl sensors --json prints the document GET /sensors serves
(pretty-printed here):
{"host": "gpu-box", "appVersion": "wireviewctl", "devices": [{
"id": "0032001F3133510B37363235", "name": "WireView Pro II", "connected": true,
"hwRev": "EF05", "fwVer": "5", "buildString": "TG-WV-PRO2-FW_20260902_0741",
"timestamp": "2026-09-29T14:22:07Z",
"pinVoltage": [12.120, 12.130, 12.110, 12.120, 12.110, 12.120],
"pinCurrent": [5.230, 5.450, 5.120, 5.340, 5.560, 5.430],
"tempInC": 45.3, "tempOutC": 42.1, "ext1C": 38.7, "ext2C": -100.0,
"psuCapW": 600, "fan": 75, "faultStatus": 0, "faultLog": 4,
"sumCurrentA": 32.130, "sumPowerW": 389.363, "energyJ": 1372480.512}]}id, fwVer and buildString come from the daemon and are "" when it is
not running. So do name and hwRev ("EF06", "WireView Pro II Noctua Edition") when the daemon reports the device's product; otherwise name is
"WireView Pro II" and hwRev is "", as an older daemon writes them. A
disconnected temperature sensor reads -100.0, the value the device reports
for it. psuCapW is 0 when unknown, and energyJ is left out when the module has no energy1_input. connected is false when the module's readings are stale.
Without the module the command prints {"devices":[]} (with host and
appVersion) and exits 1.
WIREVIEW_HWMON_PATH=DIR makes sensors and top read DIR (a directory with
a name file and the attribute files) instead of searching
/sys/class/hwmon, for testing without the module.
With --host H[:port] before the command, wireviewctl talks to wireviewd
on H over HTTP instead of the local socket (port 9876 by default; write IPv6
addresses as [addr]:port). That daemon needs its
LAN listener enabled.
info,build,sensors [--json]andread-configreadGET /sensorsandGET /config, which need no secret.sensors --jsonpasses the daemon's document through unchanged.GET /sensorshas fewer fields than the local sysfs. It has no average or Vdd voltage and no per-channel alarms, so remotesensorsprints noavg_voltage_mv,vdd_mvoralarm_*lines, and remoteinfohas noconfig_versionline. Remoteinfotakes the product and edition fromhwRevandname. Any other field the document lacks is left out too (topshows it as--), never printed as0:psu_cap: unknownmeans the device reportedpsuCapW: 0. When the document says"connected": false,sensorsprints a note on stderr that the readings may be stale.screen,nvm,clear-faultsandwrite-configgo out as a signedPOST /command. The secret comes from--secret-file FILE(the passphrase alone, or a file with asecret=line, such as/etc/wireview/config) or from$WIREVIEW_SECRET. There is no--secretoption on purpose: a secret on the command line shows up inps. Over HTTP the signature is the permission check; thewireviewgroup does not apply.bootloaderandflashrefuse: the daemon never exposes the bootloader over the network.wireviewctl --host H topadds H to thetopview, liketop --host H.
A non-2xx reply prints the HTTP status and the daemon's error, and the command exits 1.
The daemon listens on a Unix socket at /run/wireviewd.sock that any local user can connect to, allowing external programs (including the wireview-linux app) to send commands to the device without direct serial access. Commands marked * are privileged: the daemon checks the peer's credentials and accepts them only from root or members of the wireview group; anyone else gets status 3 (denied). Supported commands:
| Command | Description |
|---|---|
| GET_DEVICE_INFO | Query firmware version, config version, UID, build string, vendor and product id |
| CLEAR_FAULTS | Clear fault status and/or fault log (payload: status keep-mask, log keep-mask; u16 LE each, fault &= mask, so 0 clears all and a set bit keeps that fault) |
| READ_CONFIG | Read the device configuration |
| WRITE_CONFIG * | Write a new device configuration |
| SCREEN_CMD | Send a screen command (change display page) |
| NVM_CMD * | Send an NVM command (store/recall configuration) |
| READ_BUILD | Read the firmware build string |
| ENTER_BOOTLOADER * | Restart the device into DFU bootloader mode |
| SUSPEND_SERIAL * | Pause daemon polling and release the serial port for a client (1-300 s, re-armable) |
| RESUME_SERIAL * | End a serial handover early and resume polling |
The socket uses a binary protocol: request [type:u8][len:u16 LE][payload], response [status:u8][len:u16 LE][payload]. Status is 0 (ok), 1 (error), 2 (device not connected) or 3 (denied: privileged command from a peer that is not root or in the wireview group).
The GET_DEVICE_INFO payload is:
| Bytes | Field |
|---|---|
| 1 | firmware version |
| 1 | config version |
| 12 | UID |
| n + 1 | build string, NUL-terminated (at most 32 characters) |
| 1 | vendor id (0xEF, Thermal Grizzly) |
| 1 | product id (0x05 WireView Pro II, 0x06 WireView Pro II Noctua Edition) |
The vendor and product id came in with wireview-hwmon 1.7.0. They follow the
build string's NUL, so a client that reads the build string up to the first
NUL, as the GUI up to 1.2.5.0 and wireviewctl up to 1.6.0 do, sees no
change. A reply that ends at the NUL comes from an older daemon, which only
ever attached a WireView Pro II: read it as vendor 0xEF, product 0x05.
The socket exists for the daemon's whole life, device or not. Clients stay connected across an unplug and replug; while no device is present every command gets status 2 (a privileged command from an unprivileged peer still gets 3), and the same connection works again once the device is back.
Up to 8 clients can be connected at once. A client that sends no request for 60 seconds (counted from the connection, then from its last complete request) is disconnected, and when all 8 slots are taken a new connection replaces the client that has been idle longest. A client should therefore expect the daemon to close a connection it left idle, and reconnect and retry when that happens: the GUI does this from wireview-linux 1.2.5.0. A request in progress is not idle, but it must arrive in full within 2 seconds of its first byte or the client is disconnected. The client that requested a SUSPEND_SERIAL handover is neither closed as idle nor replaced until the handover ends, since the GUI renews it only once a minute. A new connection is refused (status 1, then closed) only while every connected client is mid-request or holds the handover. Idle disconnects and replacements are logged in the audit log with the client's uid.
- When used with the wireview-linux app, the daemon handles the serial port and the app communicates through hwmon (sensors) and the daemon socket (commands). Both can run simultaneously.
- The daemon locks the serial port while it runs: other programs that do not run as root get "Device or resource busy" instead of sharing the device's replies. The app's log reads and theme uploads borrow the port through SUSPEND_SERIAL, which lifts the lock until the handover ends.
- If the device is disconnected, the daemon will wait and reconnect automatically. The command socket and the LAN listener keep serving meanwhile.
- Sensor readings become stale (report N/A) if no data is received for 5 seconds.
The daemon can publish its readings over the LAN and accept authenticated
commands, so you can monitor and control WireViews on other machines - from the
GUI app, from wireviewctl top and
wireviewctl --host, from Prometheus, or from any HTTP client. It is off by
default: no port is opened unless you enable it.
GET /sensors- JSON snapshot of the device (per-pin V/I, power, temps, faults, fan duty, PSU cap, energy, firmware build; see the example underwireviewctl sensorsoutput). Open, read-only; the same endpoint the GUI app andwireviewctl topconsume. With no device,devicesis empty.nameis the edition ("WireView Pro II"or"WireView Pro II Noctua Edition") andhwRevits vendor and product id in uppercase hex, as Thermal Grizzly's own client writes them ("EF05","EF06"); daemons before 1.7.0 sent"WireView Pro II"and"".GET /metrics- the same readings for Prometheus (see below). Open, read-only.GET /config- the device's current configuration, so a remote editor can load it.POST /command- write commands (screen / NVM / clear-faults / writeConfig). Authenticated (see below); the firmware bootloader is never reachable over the network.
Edit /etc/wireview/config (a documented reference with the defaults is installed by
the deb, rpm and AUR packages and by make install; upgrades keep your edits):
# open the listener (default 0 = off)
remote_enabled=1
# listener port (default)
port=9876
# listen address (default: all, IPv4 and IPv6)
#bind=127.0.0.1
# shared secret for authenticated remote writes (empty = reads only)
secret=your-passphrase
# audit-log retention in days
log_days=14Comments go on their own line: text after a value is part of the value.
Then sudo systemctl restart wireviewd. Reads stay open; writes require the secret.
bind= takes a numeric IPv4 or IPv6 address (127.0.0.1 for a local
Prometheus only, ::1, 192.168.1.10). Unset, the listener takes every
address with one dual-stack socket, or IPv4 only on hosts without IPv6. A value
that is not a numeric address (a typo, a host name) disables the listener
rather than opening it on every address; the journal says why.
GET /metrics serves the Prometheus text format (0.0.4) from the listener's
port. It is read-only and, like /sensors, not logged. Every sample carries
device="<UID>", the uppercase hex UID that /sensors reports as id.
| Metric | Type | Other labels | Value |
|---|---|---|---|
wireview_up |
gauge | 1 while the device is connected and reporting | |
wireview_pin_voltage_volts |
gauge | pin="1" - "6" |
Voltage per pin |
wireview_pin_current_amps |
gauge | pin |
Current per pin |
wireview_pin_power_watts |
gauge | pin |
Power per pin |
wireview_power_watts |
gauge | Total power, sum of the pins | |
wireview_current_amps |
gauge | Total current | |
wireview_voltage_average_volts |
gauge | Average pin voltage | |
wireview_vdd_volts |
gauge | Device supply voltage | |
wireview_temperature_celsius |
gauge | sensor="onboard_in", "onboard_out", "external_1", "external_2" |
Temperature; disconnected sensors are left out |
wireview_fan_duty_ratio |
gauge | Fan duty, 0 to 1 | |
wireview_psu_cap_watts |
gauge | PSU capability, 0 = unknown | |
wireview_fault_status |
gauge | Active fault bitmask (debounced) | |
wireview_fault_log |
gauge | Latched fault bitmask (debounced) | |
wireview_fault_active |
gauge | fault="chip_over_temp", "sensor_over_temp", "over_current", "wire_over_current", "over_power", "current_imbalance" |
1 while that fault (bits 0-5) is active |
wireview_energy_joules_total |
counter | Energy since wireviewd started |
|
wireview_firmware_info |
gauge | version, build, product ("EF05", "EF06"), edition |
Always 1 |
Without a device only wireview_up 0 is served, labelled with the last device
seen (unlabelled before the first one), so an up == 0 alert stays on the same
series across a disconnect. A minimal scrape config:
scrape_configs:
- job_name: wireview
static_configs:
- targets: ['192.168.1.50:9876']make install also drops avahi-wireview.service into /etc/avahi/services/,
so avahi-daemon advertises the listener as _wireview._tcp and the GUI app
finds the host without typing an address. The packages don't ship it (the
listener is off by default). If you installed a package and enabled the
listener, copy it from the source tree:
sudo install -Dm644 avahi-wireview.service /etc/avahi/services/wireview.serviceThe file advertises port 9876; edit its <port> if you changed port=.
- Opt-in - nothing is exposed unless
remote_enabled=1;bind=limits the listener to one address. - Writes are signed with HMAC-SHA256. Each
POST /commandcarries a timestamp, nonce, and signature over the body using the sharedsecret, so the secret never travels on the wire and replays are rejected (±30 s window). An empty secret disables writes (the daemon answers403). - Flood protection - request-size cap and bounded handling.
- Audit log (below). No TLS: this is built for a trusted LAN - the HMAC keeps
the secret off the wire, but anyone on the LAN can read
/sensorsand/metricswhile the listener is on.
A btop-style terminal dashboard of every WireView, local and remote:
wireviewctl top # local device only
wireviewctl top --host 192.168.1.50 # add a remote host
wireviewctl top --host 192.168.1.50:9876,nas # several (comma/space list, host[:port])
wireviewctl top --interval 500 # refresh every 500 ms
wireviewctl --host 192.168.1.50 top # the global --host works tooEach device gets a panel with colored power/current bar gauges (green/yellow/red by load), the energy counter in Wh (when the module or remote daemon publishes it), a per-pin Volts/Amps/Watts breakdown, temperatures, fan duty, and fault state. A two-device view (one local, one remote) looks like:
WireView top 2 devices 14:22:07 refresh 1.0s q to quit
╭─ local ─ WireView Pro II ─ fw3 ─ cap 600W
│ Power ████████████████░░░░░░░░░░░░ 372.4 W 381.245 Wh
│ Current █████████████████░░░░░░░░░░░ 31.2 A
│ Pin 1 2 3 4 5 6
│ Volts 12.11 12.13 12.10 12.12 12.11 12.12
│ Amps 5.21 5.40 5.08 5.33 5.55 4.63
│ Watts 63.1 65.5 61.4 64.6 67.2 56.1
│ Temp In 45.3° Out 42.1° E1 38.7° E2 -- Fan 75% Faults none
╰─
╭─ 192.168.1.50 ─ WireView Pro II ─ fw3 ─ cap 450W
│ Power ██████░░░░░░░░░░░░░░░░░░░░░░ 118.6 W 52.907 Wh
│ Current ███████░░░░░░░░░░░░░░░░░░░░░ 9.8 A
│ Pin 1 2 3 4 5 6
│ Volts 12.09 12.10 12.08 12.11 12.09 12.10
│ Amps 1.62 1.70 1.55 1.68 1.61 1.64
│ Watts 19.6 20.6 18.7 20.3 19.5 19.8
│ Temp In 39.8° Out 36.2° E1 -- E2 -- Fan 40% Faults none
╰─
(An unreachable host, or a local device whose readings are stale, shows a single
red offline line instead of stalling the view.)
Remote hosts can also be listed (one host[:port] per line) in
/etc/wireview/hosts, which you create yourself (nothing installs it). The
local device is read straight from hwmon sysfs; remotes via GET /sensors.
Press q to quit.
When the listener is enabled, command activity is recorded to a daily-rotating
file in /var/log/wireview/ (retention from log_days):
2026-06-10T09:41:08 [INFO] wireviewd 1.5.1 started; listener enabled on [::]:9876, remote writes enabled, log retention 14 days
2026-06-10T09:41:12 [INFO] command from 192.168.1.20: op=screen -> executed
2026-06-10T09:41:15 [WARN] command from 192.168.1.33 rejected: bad signature
High-frequency /sensors and /metrics polls are not logged.
| File | Purpose |
|---|---|
/etc/wireview/config |
Daemon settings: remote_enabled, port, bind, secret, log_days. Mode 600. Read at (re)start. A commented reference is installed by the packages and make install. |
/etc/wireview/hosts |
Optional remote-host list for wireviewctl top (one host[:port] per line). Not installed; create it yourself if you want one. |
/var/log/wireview/ |
Daily-rotating audit logs, mode 600 (created by the daemon on its first log write). |
/etc/avahi/services/wireview.service |
Optional mDNS advertisement of the listener. Installed by make install only; see mDNS discovery. |
make testruns the unit tests for the daemon and CLI parsers, built with ASan/UBSan when
the compiler supports them, and an end-to-end test: tests/e2e.py runs a test
build of wireviewd against tests/fake_device.py, a simulated WireView Pro II
on a pseudo-terminal, with the hwmon node, socket, log directory and config
moved into a temporary directory, so it never touches an installed daemon.
WIREVIEW_SKIP_E2E=1 skips the end-to-end test (so does a missing python3),
and SANITIZE=0 builds without the sanitizers.
CI runs the same tests on every push and pull request, plus a -Werror build
of the daemon and CLI, a W=1 module build that fails on any warning, and lint
(shellcheck, checkpatch, the version check).
- Multi-device support (deferred). One hwmon device per WireView. The
/sensorsschema already has adevices[]array, but the daemon handles one device: this needs a misc node per device or a device id in the record, and a decision on how the hwmon devices are named. - Remote readings on a par with local ones: carry average voltage, Vdd and
the alarms in
GET /sensors, sowireviewctl --host h sensorsprints what the local command does. - Man pages for
wireviewdandwireviewctl.
wireviewdcan log "device info query failed" once right after a restart and succeeds on the retry two seconds later. It happens when the desktop app is running: while the daemon is stopped, the app opens the port itself, and the daemon's first query collides with the app's traffic. From then on the daemon holds the port locked, so the retry gets a clean line.- A raw
.binfirmware must be under 4 MiB, while a.hexmay fill exactly 4 MiB. - The deb postinst runs a bare
udevadm trigger, which re-triggers every device; it should match only the WireView. - A system that once had a 1.6.0 deb installed keeps an empty
/lib.usr-is-mergeddirectory after purge. - In the GUI (wireview-linux): the deb and rpm ship the single-file binary and the unused loose libraries side by side, doubling the download; the deb declares no
Depends; and a second launch while the app sits in the tray exits without raising the window.
Thanks to Thermal Grizzly for providing a WireView Pro II Noctua Edition unit for development.
GPL-2.0