diff --git a/CHANGELOG.md b/CHANGELOG.md index cccf10d..2306603 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## [Unreleased] +## [1.6.0] – 2021-04-06 _Summary for the 1.6.0 release: support for Corsair Lighting Node Core, Hydro H150i Pro XT, and all Hydro Pro coolers; estimate input power and efficiency for Corsair HXi and RMi PSUS; enable support for ASUS Strix GTX 1070 and new @@ -11,6 +11,7 @@ loaded automatically because of `extra/linux/71-liquidctl.rules`; this substitutes the use of `extra/linux/modules-load.conf`, which has been removed._ +Changelog since 1.5.1: ### Added - Add experimental support for the Corsair Lighting Node Core - Add experimental support for the Corsair Hydro H150i Pro XT @@ -33,8 +34,16 @@ removed._ - Replace "ID" with "#" when listing all devices - Add `keyval.load_store` method, atomic at the filesystem level - Add "Hydro" to Platinum and Pro XT device descriptions +### Removed + - Remove modules-load configuration file for Linux (use the supplied udev rules instead) + - [extra] remove `krakencurve-poc`, use `yoda` instead ### Deprecated - Deprecate `-d`/`--device`; prefer `--match` or other selection options +### Checksums +``` +486dc366f10810a4efb301f3ceda10657a09937e9bc936cecec792ac26c2f186 dist/liquidctl-1.6.0.tar.gz +9b2e144c1fa63aaf41dc3d6a264b2e78e14a5f424b86e3a5f4b80396677000e6 dist/liquidctl-1.6.0-bin-windows-x86_64.zip +``` ## [1.5.1] – 2021-02-19 diff --git a/README.md b/README.md index 3a68dd9..6f7fe46 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,17 @@ Device #4: NZXT Kraken X (X42, X52, X62 or X72) # liquidctl status NZXT Smart Device (V1) -├── Fan 1 PWM -├── Fan 1 current 0.04 A -├── Fan 1 speed 1368 rpm +├── Fan 1 speed 1473 rpm ├── Fan 1 voltage 11.91 V -├── Fan 2 — -├── Fan 3 — -├── Firmware version 1.0.7 -├── LED accessories 2 -├── LED accessory type HUE+ Strip -├── LED count (total) 20 -└── Noise level 67 dB +├── Fan 1 current 0.01 A +├── Fan 1 control mode PWM +├── Fan 2 [...] +├── Fan 2 [...] +├── Firmware version 1.0.7 +├── LED accessories 2 +├── LED accessory type HUE+ Strip +├── LED count (total) 20 +└── Noise level 65 dB NZXT Kraken X (X42, X52, X62 or X72) ├── Liquid temperature 31.7 °C @@ -83,28 +83,26 @@ The following devices are supported by this version of liquidctl. See each guid | :-: | :-- | :-: | :-- | | AIO liquid cooler | [Corsair Hydro H80i GT, H100i GTX, H110i GTX](docs/asetek-690lc-guide.md) | USB | _ZE_ | | AIO liquid cooler | [Corsair Hydro H80i v2, H100i v2, H115i](docs/asetek-690lc-guide.md) | USB | _Z_ | -| AIO liquid cooler | [Corsair Hydro H100i Pro, H115i Pro, H150i Pro](docs/asetek-pro-guide.md) | USB | _EZN_ | +| AIO liquid cooler | [Corsair Hydro H100i Pro, H115i Pro, H150i Pro](docs/asetek-pro-guide.md) | USB | _EZ_ | | AIO liquid cooler | [Corsair Hydro H100i Platinum [SE], H115i Platinum](docs/corsair-platinum-pro-xt-guide.md) | USB HID | _E_ | -| AIO liquid cooler | [Corsair Hydro H100i Pro XT, H115i Pro XT](docs/corsair-platinum-pro-xt-guide.md) | USB HID | _E_ | -| AIO liquid cooler | [Corsair Hydro H150i Pro XT](docs/corsair-platinum-pro-xt-guide.md) | USB HID | _EN_ | +| AIO liquid cooler | [Corsair Hydro H100i Pro XT, H115i Pro XT, H150i Pro XT](docs/corsair-platinum-pro-xt-guide.md) | USB HID | _E_ | | AIO liquid cooler | [EVGA CLC 120 (CL12), 240, 280, 360](docs/asetek-690lc-guide.md) | USB | _Z_ | | AIO liquid cooler | [NZXT Kraken M22](docs/kraken-x2-m2-guide.md) | USB HID | | | AIO liquid cooler | [NZXT Kraken X40, X60](docs/asetek-690lc-guide.md) | USB | _LZE_ | | AIO liquid cooler | [NZXT Kraken X31, X41, X61](docs/asetek-690lc-guide.md) | USB | _LZE_ | | AIO liquid cooler | [NZXT Kraken X42, X52, X62, X72](docs/kraken-x2-m2-guide.md) | USB HID | | | AIO liquid cooler | [NZXT Kraken X53, X63, X73](docs/kraken-x3-z3-guide.md) | USB HID | | -| AIO liquid cooler | [NZXT Kraken Z63, Z73](docs/kraken-x3-z3-guide.md) | USB & USB HID | _E_ | +| AIO liquid cooler | [NZXT Kraken Z53, Z63, Z73](docs/kraken-x3-z3-guide.md) | USB & USB HID | _E_ | | DDR4 DRAM | [Corsair Vengeance RGB](docs/ddr4-guide.md) | SMBus | _EUX_ | | DDR4 DRAM | [DIMMs with a standard temperature sensor](docs/ddr4-guide.md) | SMBus | _EUX_ | | Fan/LED controller | [Corsair Commander Pro](docs/corsair-commander-guide.md) | USB HID | _E_ | -| Fan/LED controller | [Corsair Lighting Node Core](docs/corsair-commander-guide.md) | USB HID | _EN_ | -| Fan/LED controller | [Corsair Lighting Node Pro](docs/corsair-commander-guide.md) | USB HID | _E_ | +| Fan/LED controller | [Corsair Lighting Node Core, Pro](docs/corsair-commander-guide.md) | USB HID | _E_ | | Fan/LED controller | [NZXT Grid+ V3](docs/nzxt-smart-device-v1-guide.md) | USB HID | | | Fan/LED controller | [NZXT HUE 2, HUE 2 Ambient](docs/nzxt-hue2-guide.md) | USB HID | | | Fan/LED controller | [NZXT RGB & Fan Controller](docs/nzxt-hue2-guide.md) | USB HID | | | Fan/LED controller | [NZXT Smart Device](docs/nzxt-smart-device-v1-guide.md) | USB HID | | | Fan/LED controller | [NZXT Smart Device V2](docs/nzxt-hue2-guide.md) | USB HID | | -| Graphics card | [ASUS Strix GTX 1070](docs/nvidia-guide.md) | I²C | _UXN_ | +| Graphics card | [ASUS Strix GTX 1070](docs/nvidia-guide.md) | I²C | _UX_ | | Graphics card | [ASUS Strix RTX 2080 Ti OC](docs/nvidia-guide.md) | I²C | _UX_ | | Graphics card | [EVGA GTX 1080 FTW](docs/nvidia-guide.md) | I²C | _UX_ | | Motherboard | [Gigabyte RGB Fusion 2.0 motherboards](docs/gigabyte-rgb-fusion2-guide.md) | USB HID | | @@ -133,10 +131,11 @@ The following dependencies are required at runtime (common package names are lis - Python 3.6+ _(python3, python)_ - pkg_resources Python package _(python3-setuptools, python3-pkg-resources, python-setuptools)_ - docopt _(python3-docopt, python-docopt)_ +- colorlog _(python3-colorlog, python-colorlog)_ - cython-hidapi _(python3-hidapi, python3-hid, python-hidapi)_ - PyUSB _(python3-pyusb, python3-usb, python-pyusb)_ -- LibUSB 1.0 _(libusb-1.0, libusb-1.0-0, libusbx)_ - smbus Python package _(python3-i2c-tools, python3-smbus, i2c-tools)_ +- LibUSB 1.0 _(libusb-1.0, libusb-1.0-0, libusbx)_ To locally test and manually install, a few more dependencies are needed: @@ -204,7 +203,7 @@ The port is also available in DragonFly Ports. ## Installing on Windows -A pre-built executable for the last stable version is available in [liquidctl-1.5.1-bin-windows-x86_64.zip](https://github.com/liquidctl/liquidctl/releases/download/v1.5.1/liquidctl-1.5.1-bin-windows-x86_64.zip). +A pre-built executable for the last stable version is available in [liquidctl-1.6.0-bin-windows-x86_64.zip](https://github.com/liquidctl/liquidctl/releases/download/v1.6.0/liquidctl-1.6.0-bin-windows-x86_64.zip). Executables for previous releases can be found in the assets of the [Releases](https://github.com/liquidctl/liquidctl/releases) tab, and development builds can be found in the artifacts on the [AppVeyor runs](https://ci.appveyor.com/project/jonasmalacofilho/liquidctl/history). @@ -374,10 +373,11 @@ Description=AIO startup service [Service] Type=oneshot -ExecStart=liquidctl set pump speed 90 -ExecStart=liquidctl set fan speed 20 30 30 50 34 80 40 90 50 100 -ExecStart=liquidctl set ring color fading 350017 ff2608 -ExecStart=liquidctl set logo color spectrum-wave +ExecStart=liquidctl initialize all +ExecStart=liquidctl --match kraken set pump speed 90 +ExecStart=liquidctl --match kraken set fan speed 20 30 30 50 34 80 40 90 50 100 +ExecStart=liquidctl --match "smart device" set sync speed 55 +ExecStart=liquidctl --match kraken set sync color fading 350017 ff2608 [Install] WantedBy=default.target @@ -400,10 +400,11 @@ If necessary, it is also possible to have the service unit explicitly wait for t The configuration of devices can be automated by writing a batch file and setting up a new task for (every) login using Windows Task Scheduler. The batch file can be really simple and only needs to contain the invocations of liquidctl that would otherwise be done manually. ```batchfile -liquidctl set pump speed 90 -liquidctl set fan speed 20 30 30 50 34 80 40 90 50 100 -liquidctl set ring color fading 350017 ff2608 -liquidctl set logo color spectrum-wave +liquidctl initialize all +liquidctl --match kraken set pump speed 90 +liquidctl --match kraken set fan speed 20 30 30 50 34 80 40 90 50 100 +liquidctl --match "smart device" set sync speed 55 +liquidctl --match kraken set sync color fading 350017 ff2608 ``` Make sure that liquidctl is available in the context where the batch file will run: in short, `liquidctl --version` should work within a _normal_ Command Prompt window. @@ -470,7 +471,7 @@ Neumaier, Kristóf Jakab, Sean Nelson, Chris Griffith, notaz, realies and Thomas Pircher. Depending on how it is packaged, it might also bundle copies of python, hidapi, -libusb, cython-hidapi, pyusb and docopt. +libusb, cython-hidapi, pyusb, docopt, colorlog and colorama. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software diff --git a/docs/corsair-commander-guide.md b/docs/corsair-commander-guide.md index fc34f23..30213ee 100644 --- a/docs/corsair-commander-guide.md +++ b/docs/corsair-commander-guide.md @@ -13,25 +13,25 @@ sensors and fan types are currently connected. ``` # liquidctl initialize Corsair Commander Pro (experimental) -├── Firmware version 0.9.212 -├── Bootloader version 0.5 -├── Temp sensor 1 Connected -├── Temp sensor 2 Connected -├── Temp sensor 3 Connected -├── Temp sensor 4 Connected -├── Fan 1 Mode DC -├── Fan 2 Mode DC -├── Fan 3 Mode DC -├── Fan 4 Mode Auto/Disconnected -├── Fan 5 Mode Auto/Disconnected -└── Fan 6 Mode Auto/Disconnected +├── Firmware version 0.9.212 +├── Bootloader version 0.5 +├── Temperature probe 1 Yes +├── Temperature probe 2 Yes +├── Temperature probe 3 No +├── Temperature probe 4 No +├── Fan 1 control mode PWM +├── Fan 2 control mode PWM +├── Fan 3 control mode DC +├── Fan 4 control mode N/A +├── Fan 5 control mode N/A +└── Fan 6 control mode N/A ``` ``` # liquidctl initialize Corsair Lighting Node Pro (experimental) -├── Firmware version 0.10.4 -└── Bootloader version 3.0 +├── Firmware version 0.10.4 +└── Bootloader version 3.0 ``` @@ -49,19 +49,14 @@ If a fan or temperature probe is not connected then a value of 0 is shown. ``` # liquidctl status Corsair Commander Pro (experimental) -├── 12 volt rail 12.06 V -├── 5 volt rail 4.96 V -├── 3.3 volt rail 3.36 V -├── Temp sensor 1 26.4 °C -├── Temp sensor 2 27.5 °C -├── Temp sensor 3 21.7 °C -├── Temp sensor 4 25.3 °C +├── Temperature 1 26.4 °C +├── Temperature 2 27.5 °C ├── Fan 1 speed 927 rpm ├── Fan 2 speed 927 rpm ├── Fan 3 speed 1195 rpm -├── Fan 4 speed 0 rpm -├── Fan 5 speed 0 rpm -└── Fan 6 speed 0 rpm +├── +12V rail 12.06 V +├── +5V rail 4.96 V +└── +3.3V rail 3.36 V ``` diff --git a/docs/developer/release-checklist.md b/docs/developer/release-checklist.md index 20ba187..d41e330 100644 --- a/docs/developer/release-checklist.md +++ b/docs/developer/release-checklist.md @@ -2,52 +2,68 @@ ## Prepare system - - [ ] Install publishing dependencies: `pacman -S twine` + - [ ] Ensure publishing dependencies are installed: + `pacman -S twine` ## Prepare repository - [ ] Update liquidctl/version.py - - [ ] Update last update data in the man page + - [ ] Update last update date in the man page - [ ] Make sure the CHANGELOG is up to date + - [ ] Remove "N/New driver, ..." notes from the table of supported devices (and merge lines if appropriate) - [ ] Update the link in the README to the stable executable for Windows - - [ ] Remove "N/New driver, ..." notes from the table of supported devices - - [ ] Commit "release: prepare for v" + - [ ] Regenerate the udev rules: + `(cd extra/linux && python generate-uaccess-udev-rules.py > 71-liquidctl.rules)` + - [ ] Commit: + `git commit -m "release: prepare for v$VERSION"` ## Test -_with `"$(liquidctl --version) = "liquidclt v"`:_ + - [ ] Run unit and doc tests: + `pytest` - - [ ] Run unit and doc tests: `pytest` - - [ ] Run my setup scripts: `liquidcfg && liquiddyncfg` - - [ ] Run old HW tests: `extra/old-tests/asetek_*` and `extra/old-tests/kraken_two` - - [ ] Test krakenduty: `extra/krakenduty-poc train && extra/krakenduty-poc status` - - [ ] Test krakencurve: `extra/krakencurve-poc control --fan-sensor coretemp.package_id_0 --pump '(25,50),(35,100)' --fan '(25,35),(35,60),(60,100)' --verbose` - - [ ] Test yoda: `extra/yoda --match kraken control pump with '(20,50),(50,100)' on coretemp.package_id_0 and fan with '(20,25),(34,100)' on _internal.liquid --verbose` - - [ ] Test liquiddump: `extra/liquiddump | jq -c .` - - [ ] Test krakenx (git): `colctl --mode fading --color_count 2 --color0 192,32,64 --color1 246,11,21 --fan_speed "(30, 100), (40, 100)" --pump_speed "(30, 100), (40, 100)"` +Then install locally and: + + - [ ] Run my personal setup scripts: + `liquidcfg && liquiddyncfg` + - [ ] Test yoda: + `extra/yoda --match kraken control pump with '(20,50),(50,100)' on coretemp.package_id_0 and fan with '(20,25),(34,100)' on _internal.liquid --verbose` + - [ ] Test krakenduty: + `extra/krakenduty-poc train && extra/krakenduty-poc status` + - [ ] Test liquiddump: + `extra/liquiddump | jq -c .` + - [ ] Test krakenx (git): + `colctl --mode fading --color_count 2 --color0 192,32,64 --color1 246,11,21 --fan_speed "(30, 100), (40, 100)" --pump_speed "(30, 100), (40, 100)"` ## Source distribution - - [ ] Tag HEAD with `git tag -as v` and short summary annotation (signed) - - [ ] Push HEAD and `v` tag + - [ ] Generate the source distribution: + `python setup.py sdist` + - [ ] Check that all necessary files are in `dist/liquidctl-$VERSION.tar.gz` and that the generated `extraversion.py` makes sense + - [ ] Tag HEAD with changelog and PGP signature: + `git tag -as "v$VERSION"` + - [ ] Push HEAD and vVERSION tag: + `git push origin HEAD "v$VERSION"` - [ ] Check all CI statuses (pytest, flake8 linting, and `list --verbose`) - - [ ] Generate the source distribution: `python setup.py sdist` - - [ ] Check that all necessary files are in `dist/liquidctl-.tar.gz` and that generated `extraversion.py` makes sense - - [ ] Sign the source distribution: `gpg --detach-sign -a dist/liquidctl-.tar.gz` + - [ ] Sign the source distribution: + `gpg --detach-sign -a "dist/liquidctl-$VERSION.tar.gz"` ## Binary distribution for Windows - - [ ] Download and check artifact built by AppVeyor - - [ ] Sign the artifact: `gpg --detach-sign -a dist/liquidctl--bin-windows-x86_64.zip` + - [ ] Download and check the artifact built by AppVeyor (zip checksum, exe checksum, contents, and functionality) + - [ ] Sign the artifact: + `gpg --detach-sign -a "dist/liquidctl-$VERSION-bin-windows-x86_64.zip"` ## Release - - [ ] Upload: `twine upload dist/liquidctl-.tar.gz{,.asc}` - - [ ] Upgrade the `v` tag on GitHub to a release (with sdist, Windows artifact, and corresponding GPG signatures) - - [ ] Update the HEAD changelog with the release file SHA256 sums + - [ ] Upload: + `twine upload dist/liquidctl-$VERSION.tar.gz{,.asc}` + - [ ] Upgrade the vVERSION tag on GitHub to a release (with sdist, Windows artifact, and corresponding GPG signatures) + - [ ] Update the HEAD changelog with the release file SHA256 sums: + `sha256sum dist/liquidctl-$VERSION{.tar.gz,-bin-windows-x86_64.zip} | tee "dist/liquidctl-$VERSION.sha256sums"` ## Post release - - [ ] Merge the release branch into the main branch + - [ ] Merge the release branch into the main branch (if appropriate) - [ ] Update the HEAD release-checklist with this checklist - [ ] Update ArchLinux `liquidctl-git` diff --git a/docs/kraken-x3-z3-guide.md b/docs/kraken-x3-z3-guide.md index b096a50..777476f 100644 --- a/docs/kraken-x3-z3-guide.md +++ b/docs/kraken-x3-z3-guide.md @@ -1,7 +1,7 @@ # Fourth-generation NZXT liquid coolers _Driver API and source code available in [`liquidctl.driver.kraken3`](../liquidctl/driver/kraken3.py)._ -The fourth-generation of NZXT Kraken coolers is composed by X models—featuring the familiar infinity mirror—and Z models—replacing the infinity mirror with an OLED screen. +The fourth-generation of NZXT Kraken coolers is composed by X models—featuring the familiar infinity mirror—and Z models—replacing the infinity mirror with an LCD screen. Both X and Z models house seventh-generation Asetek pump designs, plus secondary PCBs from NZXT for enhanced control and visual customization. The coolers are powered directly from the power supply unit. @@ -15,13 +15,13 @@ The X models incorporate customizable pump speed control, a liquid temperature p All capabilities available at the hardware level are supported, but other features offered by CAM, like presets based on CPU or GPU temperatures, are not part of the scope of the liquidctl CLI. -## NZXT Kraken Z63, Z73 +## NZXT Kraken Z53, Z63, Z73 -The most notable difference between Kraken X and Kraken Z models is the replacement of the infinity mirror by a OLED screen. +The most notable difference between Kraken X and Kraken Z models is the replacement of the infinity mirror by a LCD screen. In addition to this, Kraken Z coolers restore the embedded fan controller that is missing from the current Kraken X models. -The OLED screen cannot yet be controlled with liquidctl, but all other hardware capabilities are supported. +The LCD screen cannot yet be controlled with liquidctl, but all other hardware capabilities are supported. ## Initialization @@ -157,6 +157,6 @@ they will be removed in a future version and are kept for now for backward compa | `backwards-super-rainbow` | None | ✓ | | `backwards-rainbow-pulse` | None | ✓ | -## The OLED screen (only Z models) +## The LCD screen (only Z models) To be implemented. diff --git a/docs/nzxt-smart-device-v1-guide.md b/docs/nzxt-smart-device-v1-guide.md index c20d0f4..248b9c8 100644 --- a/docs/nzxt-smart-device-v1-guide.md +++ b/docs/nzxt-smart-device-v1-guide.md @@ -32,23 +32,23 @@ The device can report fan information for each channel, the noise level at the o ``` # liquidctl status NZXT Smart Device (V1) -├── Fan 1 PWM -├── Fan 1 current 0.04 A -├── Fan 1 speed 1064 rpm +├── Fan 1 speed 1473 rpm ├── Fan 1 voltage 11.91 V -├── Fan 2 PWM -├── Fan 2 current 0.01 A -├── Fan 2 speed 1051 rpm -├── Fan 2 voltage 11.77 V -├── Fan 3 PWM -├── Fan 3 current 0.09 A -├── Fan 3 speed 1581 rpm -├── Fan 3 voltage 11.77 V -├── Firmware version 1.0.7 -├── LED accessories 2 -├── LED accessory type HUE+ Strip -├── LED count (total) 20 -└── Noise level 62 dB +├── Fan 1 current 0.01 A +├── Fan 1 control mode PWM +├── Fan 2 speed 1341 rpm +├── Fan 2 voltage 11.91 V +├── Fan 2 current 0.02 A +├── Fan 2 control mode DC +├── Fan 3 speed 1352 rpm +├── Fan 3 voltage 11.91 V +├── Fan 3 current 0.02 A +├── Fan 3 control mode N/A +├── Firmware version 1.0.7 +├── LED accessories 2 +├── LED accessory type HUE+ Strip +├── LED count (total) 20 +└── Noise level 65 dB ``` diff --git a/extra/krakencurve-poc b/extra/krakencurve-poc deleted file mode 100755 index d7b7a24..0000000 --- a/extra/krakencurve-poc +++ /dev/null @@ -1,200 +0,0 @@ -#!/usr/bin/env python3 - -"""Adjust Kraken X42/X52/X62/X72 speeds dynamically, with software. - -Deprecated proof of concept, use ./yoda instead. - -Usage: - krakencurve-poc [options] show-sensors - krakencurve-poc [options] control --pump --fan - krakencurve-poc --help - krakencurve-poc --version - -Options: - --pump Profile to use for the pump - --fan Profile to use for the fan - --pump-sensor Select alternate sensor for pump speed - --fan-sensor Select alternate sensor for fan speed - --interval Update interval in seconds [default: 2] - -v, --verbose Output additional information - -g, --debug Show debug information on stderr - --version Display the version number - --help Show this message - -Requirements: - all platforms liquidctl, including the Python APIs (pip install liquidctl) - Linux/FreeBSD psutil (pip install psutil) - macOS iStats (gem install iStats) - Windows none, system sensors not yet supported - -Changelog: - 0.0.3 Fix casing of log and error messages - 0.0.2 MacOS support for iStats; breaking refresh of the CLI and sensor names - 0.0.1 Initial proof-of-concept; system sensors only supported on Linux - -Copyright (C) 2018–2021 Jonas Malaco -SPDX-License-Identifier: GPL-3.0-or-later -""" - -import ast -import logging -import sys -import time - -from docopt import docopt -from liquidctl.driver.kraken_two import KrakenTwoDriver -from liquidctl.util import normalize_profile, interpolate_profile - -if sys.platform == 'darwin': - import re - import subprocess -elif sys.platform.startswith('linux') or sys.platform.startswith('freebsd'): - import psutil - -VERSION = '0.0.3' -LOGGER = logging.getLogger(__name__) - -LIQUID_SENSOR = 'kraken.coolant' - - -def read_sensors(cooler): - sensors = {} - if cooler: - data = {k: v for k, v, u in cooler.get_status()} - sensors[LIQUID_SENSOR] = data['Liquid temperature'] - if sys.platform == 'darwin': - istats_stdout = subprocess.check_output(['istats']).decode('utf-8') - for line in istats_stdout.split('\n'): - if line.startswith('CPU'): - cpu_temp = float(re.search(r'\d+\.\d+', line).group(0)) - sensors['istats.cpu'] = cpu_temp - break - elif sys.platform.startswith('linux') or sys.platform.startswith('freebsd'): - for m, li in psutil.sensors_temperatures().items(): - for label, current, _, _ in li: - sensors['{}.{}'.format(m, label.lower().replace(' ', '_'))] = current - return sensors - - -def show_sensors(cooler): - print('{:<60} {:>18}'.format('Sensor identifier', 'Temperature')) - print('-' * 80) - sensors = read_sensors(cooler) - for k, v in sensors.items(): - if k == LIQUID_SENSOR: - k = k + ' [default]' - print('{:<70} {:>6}{}'.format(k, v, '°C')) - - -def parse_profile(arg, mintemp, maxtemp, minduty=0, maxduty=100): - """Parse, validate and normalize a temperature–duty profile. - - >>> parse_profile('(20,30),(30,50),(34,80),(40,90)', 0, 60, 25, 100) - [(20, 30), (30, 50), (34, 80), (40, 90), (60, 100)] - >>> parse_profile('35', 0, 60, 25, 100) - [(0, 35), (59, 35), (60, 100)] - - The profile is validated in structure and acceptable ranges. Duty is - checked against `minduty` and `maxduty`. Temperature must be between - `mintemp` and `maxtemp`. - - >>> parse_profile('(20,30),(50,100', 0, 60, 25, 100) - Traceback (most recent call last): - ... - ValueError: Profile must be comma-separated (temperature, duty) tuples - >>> parse_profile('(20,30),(50,100,2)', 0, 60, 25, 100) - Traceback (most recent call last): - ... - ValueError: Profile must be comma-separated (temperature, duty) tuples - >>> parse_profile('(20,30),(50,97.6)', 0, 60, 25, 100) - Traceback (most recent call last): - ... - ValueError: Duty must be integer number between 25 and 100 - >>> parse_profile('(20,15),(50,100)', 0, 60, 25, 100) - Traceback (most recent call last): - ... - ValueError: Duty must be integer number between 25 and 100 - >>> parse_profile('(20,30),(70,100)', 0, 60, 25, 100) - Traceback (most recent call last): - ... - ValueError: Temperature must be integer number between 0 and 60 - - """ - try: - val = ast.literal_eval('[' + arg + ']') - if len(val) == 1 and isinstance(val[0], int): - # for arg == '' set fixed duty between mintemp and maxtemp - 1 - val = [(mintemp, val[0]), (maxtemp - 1, val[0])] - except: - raise ValueError('profile must be comma-separated (temperature, duty) tuples') - for step in val: - if not isinstance(step, tuple) or len(step) != 2: - raise ValueError('profile must be comma-separated (temperature, duty) tuples') - temp, duty = step - if not isinstance(temp, int) or temp < mintemp or temp > maxtemp: - raise ValueError('temperature must be integer between {} and {}'.format(mintemp, maxtemp)) - if not isinstance(duty, int) or duty < minduty or duty > maxduty: - raise ValueError('duty must be integer between {} and {}'.format(minduty, maxduty)) - return normalize_profile(val, critx=maxtemp) - - -def control(cooler, pump_profile, fan_profile, update_interval, - pump_sensor, fan_sensor): - LOGGER.info('pump following sensor %s and profile %s', pump_sensor, str(pump_profile)) - LOGGER.info('fan following sensor %s and profile %s', fan_sensor, str(fan_profile)) - while True: - sensors = read_sensors(cooler) - LOGGER.info('pump control (%s): %.1f°C, fan control (%s): %.1f°C', - pump_sensor, sensors[pump_sensor], fan_sensor, sensors[fan_sensor]) - pump_duty = interpolate_profile(pump_profile, sensors[pump_sensor]) - fan_duty = interpolate_profile(fan_profile, sensors[fan_sensor]) - cooler.set_instantaneous_speed('pump', pump_duty) - cooler.set_instantaneous_speed('fan', fan_duty) - time.sleep(update_interval) - - -if __name__ == '__main__': - if len(sys.argv) == 2 and sys.argv[1] == 'doctest': - import doctest - doctest.testmod(verbose=True) - sys.exit(0) - - args = docopt(__doc__, version='krakencurve-poc v{}'.format(VERSION)) - - if args['--debug']: - args['--verbose'] = True - logging.basicConfig(level=logging.DEBUG, format='[%(levelname)s] %(name)s: %(message)s') - LOGGER.debug('krakencurve-poc v%s', VERSION) - import liquidctl.version - LOGGER.debug('liquidctl v%s', liquidctl.version.__version__) - elif args['--verbose']: - logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') - else: - logging.basicConfig(level=logging.WARNING, format='%(levelname)s: %(message)s') - sys.tracebacklimit = 0 - - device = KrakenTwoDriver.find_supported_devices()[0] - device.connect() - - try: - if args['show-sensors']: - show_sensors(device) - elif args['control']: - pump_sensor = args['--pump-sensor'] or LIQUID_SENSOR - pump_max_temp = 100 if pump_sensor != LIQUID_SENSOR else 60 - fan_sensor = args['--fan-sensor'] or LIQUID_SENSOR - fan_max_temp = 100 if fan_sensor != LIQUID_SENSOR else 60 - - pump_profile = parse_profile(args['--pump'], 0, pump_max_temp, minduty=50) - fan_profile = parse_profile(args['--fan'], 0, fan_max_temp, minduty=25) - - control(device, pump_profile, fan_profile, - update_interval=int(args['--interval']), - pump_sensor=pump_sensor, - fan_sensor=fan_sensor) - else: - raise Exception('nothing to do') - except KeyboardInterrupt: - LOGGER.info('stopped by user') - finally: - device.disconnect() diff --git a/extra/linux/71-liquidctl.rules b/extra/linux/71-liquidctl.rules index 5b06786..4103223 100644 --- a/extra/linux/71-liquidctl.rules +++ b/extra/linux/71-liquidctl.rules @@ -174,7 +174,7 @@ SUBSYSTEMS=="usb", ATTRS{idVendor}=="1e71", ATTRS{idProduct}=="170e", TAG+="uacc # NZXT Kraken X (X53, X63 or X73) SUBSYSTEMS=="usb", ATTRS{idVendor}=="1e71", ATTRS{idProduct}=="2007", TAG+="uaccess" -# NZXT Kraken Z (Z63 or Z73) +# NZXT Kraken Z (Z53, Z63 or Z73) SUBSYSTEMS=="usb", ATTRS{idVendor}=="1e71", ATTRS{idProduct}=="3008", TAG+="uaccess" # NZXT RGB & Fan Controller diff --git a/extra/linux/generate-uaccess-udev-rules.py b/extra/linux/generate-uaccess-udev-rules.py index a075b4e..593d58d 100755 --- a/extra/linux/generate-uaccess-udev-rules.py +++ b/extra/linux/generate-uaccess-udev-rules.py @@ -1,114 +1,116 @@ import sys from inspect import cleandoc -# This script is meant to be executed from this directory or the project root. -# We use that assumption to make Python pick the local liquidctl modules, -# instead other versions that may be installed on the environment/system. -sys.path = ['../..', ''] + sys.path +if __name__ == '__main__': -from liquidctl.driver.base import find_all_subclasses -from liquidctl.driver.nvidia import _NvidiaI2CDriver -from liquidctl.driver.usb import BaseUsbDriver + # This script is meant to be executed from this directory or the project root. + # We use that assumption to make Python pick the local liquidctl modules, + # instead other versions that may be installed on the environment/system. + sys.path = ['../..', ''] + sys.path + + from liquidctl.driver.base import find_all_subclasses + from liquidctl.driver.nvidia import _NvidiaI2CDriver + from liquidctl.driver.usb import BaseUsbDriver -HEADER = ''' -# Rules that grant unprivileged access to devices supported by liquidctl -# -# Users and distros are encouraged to use these if they want liquidctl to work -# without requiring root privileges (e.g. with the use of `sudo`). -# -# In the case of I²C/SMBus devices, these rules also cause the loading of the -# `i2c-dev` kernel module. The module is required for access to I²C/SMBus -# devices from userspace, and loading kernel modules is in itself a privileged -# operation. -# -# Distros will likely want to place this file in `/usr/lib/udev/rules.d/`, -# while users installing this manually SHOULD use `/etc/udev/rules.d/` instead. -# -# The suggested name for this file is `71-liquidctl.rules`. This was chosen -# based on the numbering of other uaccess tagging rule files in my system (not -# very scientific, I know, but I could not find any documented policy for -# this), as well as the need to let users overrule these rules. -# -# These rules assume a system with modern versions of systemd/udev, that -# support the `uaccess` tag; on older systems the rules can be changed to set -# GROUP="plugdev" and MODE="0660" instead. The currently deprecated 'plugdev' -# group is not used by default to avoid generating warnings on systems that -# have already removed it. -# -# Finally, this file was automatically generated. To update it, from a Linux -# shell and the current directory, execute: -# -# $ python generate-uaccess-udev-rules.py > 71-liquidctl.rules -# -''' + HEADER = ''' + # Rules that grant unprivileged access to devices supported by liquidctl + # + # Users and distros are encouraged to use these if they want liquidctl to work + # without requiring root privileges (e.g. with the use of `sudo`). + # + # In the case of I²C/SMBus devices, these rules also cause the loading of the + # `i2c-dev` kernel module. The module is required for access to I²C/SMBus + # devices from userspace, and loading kernel modules is in itself a privileged + # operation. + # + # Distros will likely want to place this file in `/usr/lib/udev/rules.d/`, + # while users installing this manually SHOULD use `/etc/udev/rules.d/` instead. + # + # The suggested name for this file is `71-liquidctl.rules`. This was chosen + # based on the numbering of other uaccess tagging rule files in my system (not + # very scientific, I know, but I could not find any documented policy for + # this), as well as the need to let users overrule these rules. + # + # These rules assume a system with modern versions of systemd/udev, that + # support the `uaccess` tag; on older systems the rules can be changed to set + # GROUP="plugdev" and MODE="0660" instead. The currently deprecated 'plugdev' + # group is not used by default to avoid generating warnings on systems that + # have already removed it. + # + # Finally, this file was automatically generated. To update it, from a Linux + # shell and the current directory, execute: + # + # $ python generate-uaccess-udev-rules.py > 71-liquidctl.rules + # + ''' -MANUAL_RULES = r''' - # Section: special cases + MANUAL_RULES = r''' + # Section: special cases - # Host SMBus on Intel mainstream/HEDT platforms - KERNEL=="i2c-*", DRIVERS=="i801_smbus", TAG+="uaccess", \ - RUN{builtin}="kmod load i2c-dev" -''' + # Host SMBus on Intel mainstream/HEDT platforms + KERNEL=="i2c-*", DRIVERS=="i801_smbus", TAG+="uaccess", \ + RUN{builtin}="kmod load i2c-dev" + ''' -print(cleandoc(HEADER)) + print(cleandoc(HEADER)) -print() -print() -print(cleandoc(MANUAL_RULES)) - -print() -print() -print(f'# Section: NVIDIA graphics cards') - -nvidia_devs = {} - -for driver in find_all_subclasses(_NvidiaI2CDriver): - for did, sdid, description in driver._MATCHES: - ids = (driver._VENDOR, did, sdid) - if ids in nvidia_devs: - nvidia_devs[ids].append(description) - nvidia_devs[ids].sort() - else: - nvidia_devs[ids] = [description] - -nvidia_devs = [(svid, did, sdid, description) for (svid, did, sdid), description in nvidia_devs.items()] -nvidia_devs.sort(key=lambda x: x[3][0]) - -for svid, did, sdid, descriptions in nvidia_devs: print() - for desc in descriptions: - desc = desc.replace(' (experimental)', '') - print(f'# {desc}') - print(cleandoc(f''' - KERNEL=="i2c-*", ATTR{{name}}=="NVIDIA i2c adapter 1 *", ATTRS{{vendor}}=="0x10de", \\ - ATTRS{{device}}=="{did:#06x}", ATTRS{{subsystem_vendor}}=="{svid:#06x}", \\ - ATTRS{{subsystem_device}}=="{sdid:#06x}", DRIVERS=="nvidia", TAG+="uaccess", \\ - RUN{{builtin}}="kmod load i2c-dev" - ''')) - -print() -print() -print(f'# Section: USB devices and USB HIDs') - -usb_devs = {} - -for driver in find_all_subclasses(BaseUsbDriver): - for vid, pid, _, description, _ in driver.SUPPORTED_DEVICES: - ids = (vid, pid) - if ids in usb_devs: - usb_devs[ids].append(description) - usb_devs[ids].sort() - else: - usb_devs[ids] = [description] - -usb_devs = [(vid, pid, description) for (vid, pid), description in usb_devs.items()] -usb_devs.sort(key=lambda x: x[2][0]) - -for vid, pid, descriptions in usb_devs: print() - for desc in descriptions: - desc = desc.replace(' (experimental)', '') - print(f'# {desc}') - print(f'SUBSYSTEMS=="usb", ATTRS{{idVendor}}=="{vid:04x}", ATTRS{{idProduct}}=="{pid:04x}", TAG+="uaccess"') + print(cleandoc(MANUAL_RULES)) + + print() + print() + print(f'# Section: NVIDIA graphics cards') + + nvidia_devs = {} + + for driver in find_all_subclasses(_NvidiaI2CDriver): + for did, sdid, description in driver._MATCHES: + ids = (driver._VENDOR, did, sdid) + if ids in nvidia_devs: + nvidia_devs[ids].append(description) + nvidia_devs[ids].sort() + else: + nvidia_devs[ids] = [description] + + nvidia_devs = [(svid, did, sdid, description) for (svid, did, sdid), description in nvidia_devs.items()] + nvidia_devs.sort(key=lambda x: x[3][0]) + + for svid, did, sdid, descriptions in nvidia_devs: + print() + for desc in descriptions: + desc = desc.replace(' (experimental)', '') + print(f'# {desc}') + print(cleandoc(f''' + KERNEL=="i2c-*", ATTR{{name}}=="NVIDIA i2c adapter 1 *", ATTRS{{vendor}}=="0x10de", \\ + ATTRS{{device}}=="{did:#06x}", ATTRS{{subsystem_vendor}}=="{svid:#06x}", \\ + ATTRS{{subsystem_device}}=="{sdid:#06x}", DRIVERS=="nvidia", TAG+="uaccess", \\ + RUN{{builtin}}="kmod load i2c-dev" + ''')) + + print() + print() + print(f'# Section: USB devices and USB HIDs') + + usb_devs = {} + + for driver in find_all_subclasses(BaseUsbDriver): + for vid, pid, _, description, _ in driver.SUPPORTED_DEVICES: + ids = (vid, pid) + if ids in usb_devs: + usb_devs[ids].append(description) + usb_devs[ids].sort() + else: + usb_devs[ids] = [description] + + usb_devs = [(vid, pid, description) for (vid, pid), description in usb_devs.items()] + usb_devs.sort(key=lambda x: x[2][0]) + + for vid, pid, descriptions in usb_devs: + print() + for desc in descriptions: + desc = desc.replace(' (experimental)', '') + print(f'# {desc}') + print(f'SUBSYSTEMS=="usb", ATTRS{{idVendor}}=="{vid:04x}", ATTRS{{idProduct}}=="{pid:04x}", TAG+="uaccess"') diff --git a/extra/windows/redist-notices.txt b/extra/windows/redist-notices.txt index cce08a6..8df1ff0 100644 --- a/extra/windows/redist-notices.txt +++ b/extra/windows/redist-notices.txt @@ -4,6 +4,8 @@ liquidctl executables for Windows bundle the following projects: - pyinstaller (bootloader) - python - docopt + - colorlog + - colorama - pyusb - libusb-1.0 - cython-hidapi @@ -142,6 +144,61 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. =============================================================================== +colorlog – https://github.com/borntyping/python-colorlog + +Copyright (c) 2018 Sam Clements + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +=============================================================================== + +colorama – https://github.com/tartley/colorama + +Copyright (c) 2010 Jonathan Hartley +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the copyright holders, nor those of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +=============================================================================== + pyusb – https://github.com/pyusb/pyusb Copyright 2009–2017 Wander Lairson Costa diff --git a/liquidctl.8 b/liquidctl.8 index 12cfb6a..ff87153 100644 --- a/liquidctl.8 +++ b/liquidctl.8 @@ -1,6 +1,6 @@ '\" t .nr is_macos 0 -.TH LIQUIDCTL 8 2021\-01\-25 "liquidctl" "System Manager's Manual" +.TH LIQUIDCTL 8 2021\-04\-06 "liquidctl" "System Manager's Manual" . .SH NAME liquidctl \- monitor and control liquid coolers and other devices @@ -360,8 +360,8 @@ The animation direction can be set with where the allowed values are: \fIforward\fR or \fIbackward\fR. . .SS NZXT Kraken X53, X63, X73 -.SS NZXT Kraken Z63, Z73 -Cooling channels: \fIpump\fR; (only Z63, Z73:) \fIfan\fR. +.SS NZXT Kraken Z53, Z63, Z73 +Cooling channels: \fIpump\fR; (only Z53, Z63, Z73:) \fIfan\fR. .PP Lighting channels: \fIexternal\fR; (only X53, X63, X73:) \fIring\fR, \fIlogo\fR, \fIsync\fR. .TS diff --git a/liquidctl/cli.py b/liquidctl/cli.py index 02ce302..15d7b00 100644 --- a/liquidctl/cli.py +++ b/liquidctl/cli.py @@ -76,6 +76,7 @@ import os import sys from traceback import format_exception +import colorlog from docopt import docopt from liquidctl.driver import * @@ -206,7 +207,10 @@ def _print_dev_status(dev, status): for k, v, u in status: if isinstance(v, datetime.timedelta): v = str(v) - u = '' + elif isinstance(v, bool): + v = 'Yes' if v else 'No' + elif v is None: + v = 'N/A' else: valfmt = _VALUE_FORMATS.get(u, '') v = f'{v:{valfmt}}' @@ -271,14 +275,31 @@ def main(): if args['--debug']: args['--verbose'] = True - logging.basicConfig(level=logging.DEBUG, format='[%(levelname)s] %(name)s: %(message)s') - _LOGGER.debug('running %s', _gen_version()) + log_fmt = '%(log_color)s[%(levelname)s] %(module)s (%(funcName)s): %(message)s' + log_level = logging.DEBUG elif args['--verbose']: - logging.basicConfig(level=logging.INFO, format='%(levelname)s: %(message)s') + log_fmt = '%(log_color)s%(levelname)s: %(message)s' + log_level = logging.INFO else: - logging.basicConfig(level=logging.WARNING, format='%(levelname)s: %(message)s') + log_fmt = '%(log_color)s%(levelname)s: %(message)s' + log_level = logging.WARNING sys.tracebacklimit = 0 + log_colors = { + 'DEBUG': 'purple', + 'INFO': 'blue', + 'WARNING': 'yellow,bold', + 'ERROR': 'red,bold', + 'CRITICAL': 'red,bold,bg_white', + } + + log_fmtter = colorlog.TTYColoredFormatter(fmt=log_fmt, stream=sys.stderr, log_colors=log_colors) + log_handler = logging.StreamHandler() + log_handler.setFormatter(log_fmtter) + logging.basicConfig(level=log_level, handlers=[log_handler]) + + _LOGGER.debug('running %s', _gen_version()) + opts = _make_opts(args) filter_count = sum(1 for opt in opts if opt in _FILTER_OPTIONS) device_id = None diff --git a/liquidctl/driver/commander_pro.py b/liquidctl/driver/commander_pro.py index 3c5f782..696c1bb 100644 --- a/liquidctl/driver/commander_pro.py +++ b/liquidctl/driver/commander_pro.py @@ -99,18 +99,18 @@ def _quoted(*names): return ', '.join(map(repr, names)) -def _get_fan_mode_description(mode): +def _fan_mode_desc(mode): """This will convert the fan mode value to a descriptive name. """ - if mode == _FAN_MODE_DISCONNECTED: - return 'Auto/Disconnected' - elif mode == _FAN_MODE_DC: + if mode == _FAN_MODE_DC: return 'DC' elif mode == _FAN_MODE_PWM: return 'PWM' else: - return 'UNKNOWN' + if mode != _FAN_MODE_DISCONNECTED: + _LOGGER.warning('unknown fan mode: {mode:#04x}') + return None class CommanderPro(UsbHidDriver): @@ -178,10 +178,8 @@ class CommanderPro(UsbHidDriver): temp_connected = res[1:5] self._data.store('temp_sensors_connected', temp_connected) status += [ - ('Temp sensor 1', 'Connected' if temp_connected[0] else 'Not Connected', ''), - ('Temp sensor 2', 'Connected' if temp_connected[1] else 'Not Connected', ''), - ('Temp sensor 3', 'Connected' if temp_connected[2] else 'Not Connected', ''), - ('Temp sensor 4', 'Connected' if temp_connected[3] else 'Not Connected', ''), + (f'Temperature probe {i + 1}', bool(temp_connected[i]), '') + for i in range(4) ] if self._fan_count > 0: @@ -190,12 +188,8 @@ class CommanderPro(UsbHidDriver): fanModes = res[1:self._fan_count+1] self._data.store('fan_modes', fanModes) status += [ - ('Fan 1 Mode', _get_fan_mode_description(fanModes[0]), ''), - ('Fan 2 Mode', _get_fan_mode_description(fanModes[1]), ''), - ('Fan 3 Mode', _get_fan_mode_description(fanModes[2]), ''), - ('Fan 4 Mode', _get_fan_mode_description(fanModes[3]), ''), - ('Fan 5 Mode', _get_fan_mode_description(fanModes[4]), ''), - ('Fan 6 Mode', _get_fan_mode_description(fanModes[5]), ''), + (f'Fan {i + 1} control mode', _fan_mode_desc(fanModes[i]), '') + for i in range(6) ] return status @@ -210,42 +204,28 @@ class CommanderPro(UsbHidDriver): _LOGGER.debug('only the Commander Pro supports this') return [] - connected_temp_sensors = self._data.load('temp_sensors_connected', default=[0]*self._temp_probs) + temp_probes = self._data.load('temp_sensors_connected', default=[0]*self._temp_probs) fan_modes = self._data.load('fan_modes', default=[0]*self._fan_count) + status = [] + # get the temperature sensor values - temp = [0]*self._temp_probs - for num, enabled in enumerate(connected_temp_sensors): - if enabled: - temp[num] = self._get_temp(num) - - # get the real power supply voltages - res = self._send_command(_CMD_GET_VOLTS, [0]) - volt_12 = u16be_from(res, offset=1) / 1000 - - res = self._send_command(_CMD_GET_VOLTS, [1]) - volt_5 = u16be_from(res, offset=1) / 1000 - - res = self._send_command(_CMD_GET_VOLTS, [2]) - volt_3 = u16be_from(res, offset=1) / 1000 + for i, probe_enabled in enumerate(temp_probes): + if probe_enabled: + temp = self._get_temp(i) + status.append((f'Temperature {i + 1}', temp, '°C')) # get fan RPMs of connected fans - fanspeeds = [0]*self._fan_count - for fan_num, mode in enumerate(fan_modes): - if mode == _FAN_MODE_DC or mode == _FAN_MODE_PWM: - fanspeeds[fan_num] = self._get_fan_rpm(fan_num) + for i, fan_mode in enumerate(fan_modes): + if fan_mode == _FAN_MODE_DC or fan_mode == _FAN_MODE_PWM: + speed = self._get_fan_rpm(i) + status.append((f'Fan {i + 1} speed', speed, 'rpm')) - status = [ - ('12 volt rail', volt_12, 'V'), - ('5 volt rail', volt_5, 'V'), - ('3.3 volt rail', volt_3, 'V'), - ] - - for temp_num in range(self._temp_probs): - status += [(f'Temp sensor {temp_num + 1}', temp[temp_num], '°C')] - - for fan_num in range(self._fan_count): - status += [(f'Fan {fan_num + 1} speed', fanspeeds[fan_num], 'rpm')] + # get the real power supply voltages + for i, rail in enumerate(["+12V", "+5V", "+3.3V"]): + raw = self._send_command(_CMD_GET_VOLTS, [i]) + voltage = u16be_from(raw, offset=1) / 1000 + status.append((f'{rail} rail', voltage, 'V')) return status diff --git a/liquidctl/driver/ddr4.py b/liquidctl/driver/ddr4.py index 2beeaaa..f1caea9 100644 --- a/liquidctl/driver/ddr4.py +++ b/liquidctl/driver/ddr4.py @@ -210,7 +210,7 @@ class Ddr4Temperature(SmbusDriver): """ if not check_unsafe(*self._UNSAFE, **kwargs): - _LOGGER.warning("%s: nothing to return, requires unsafe features '%s'", + _LOGGER.warning("%s: nothing returned, requires unsafe features '%s'", self.description, ','.join(self._UNSAFE)) return [] diff --git a/liquidctl/driver/kraken3.py b/liquidctl/driver/kraken3.py index e627e14..d132429 100644 --- a/liquidctl/driver/kraken3.py +++ b/liquidctl/driver/kraken3.py @@ -3,7 +3,7 @@ Supported devices: - NZXT Kraken X (X53, X63 and Z73) -- NZXT Kraken Z (Z63 and Z73); no OLED screen control yet +- NZXT Kraken Z (Z53, Z63 and Z73); no LCD screen control yet Copyright (C) 2020–2021 Tom Frey, Jonas Malaco and contributors SPDX-License-Identifier: GPL-3.0-or-later @@ -349,7 +349,7 @@ class KrakenZ3(KrakenX3): """Fourth-generation Kraken Z liquid cooler.""" SUPPORTED_DEVICES = [ - (0x1e71, 0x3008, None, 'NZXT Kraken Z (Z63 or Z73) (experimental)', { + (0x1e71, 0x3008, None, 'NZXT Kraken Z (Z53, Z63 or Z73) (experimental)', { 'speed_channels': _SPEED_CHANNELS_KRAKENZ, 'color_channels': _COLOR_CHANNELS_KRAKENZ, }) diff --git a/liquidctl/driver/nvidia.py b/liquidctl/driver/nvidia.py index aaa228a..314d7aa 100644 --- a/liquidctl/driver/nvidia.py +++ b/liquidctl/driver/nvidia.py @@ -131,7 +131,7 @@ class EvgaPascal(SmbusDriver, _NvidiaI2CDriver): return [] if not check_unsafe('smbus', **kwargs): - _LOGGER.warning("%s: nothing to return, requires unsafe features 'smbus'", + _LOGGER.warning("%s: nothing returned, requires unsafe feature 'smbus'", self.description) return [] @@ -294,8 +294,8 @@ class RogTuring(SmbusDriver, _NvidiaI2CDriver): return [] if not check_unsafe('smbus', **kwargs): - _LOGGER.warning("%s: nothing to return, requires unsafe features " - "'smbus'", self.description) + _LOGGER.warning("%s: nothing returned, requires unsafe feature 'smbus'", + self.description) return [] assert self._address != self._SENTINEL_ADDRESS, \ diff --git a/liquidctl/driver/smart_device.py b/liquidctl/driver/smart_device.py index bca66af..f664735 100644 --- a/liquidctl/driver/smart_device.py +++ b/liquidctl/driver/smart_device.py @@ -261,22 +261,29 @@ class SmartDevice(_CommonSmartDeviceDriver): """ status = [] + fans = [None] * len(self._speed_channels) noise = [] + self.device.clear_enqueued_reports() for i, _ in enumerate(self._speed_channels): msg = self.device.read(self._READ_LENGTH) num = (msg[15] >> 4) + 1 state = msg[15] & 0x3 - status.append((f'Fan {num}', ['—', 'DC', 'PWM'][state], '')) + + fans[num - 1] = [ + (f'Fan {num} speed', msg[3] << 8 | msg[4], 'rpm'), + (f'Fan {num} voltage', msg[7] + msg[8]/100, 'V'), + (f'Fan {num} current', msg[9] + msg[10]/100, 'A'), + (f'Fan {num} control mode', [None, 'DC', 'PWM'][state], ''), + ] noise.append(msg[1]) - if state: - status.append((f'Fan {num} speed', msg[3] << 8 | msg[4], 'rpm')) - status.append((f'Fan {num} voltage', msg[7] + msg[8]/100, 'V')) - status.append((f'Fan {num} current', msg[10]/100, 'A')) + if i != 0: continue + fw = '{}.{}.{}'.format(msg[0xb], msg[0xc] << 8 | msg[0xd], msg[0xe]) status.append(('Firmware version', fw, '')) + if self._color_channels: lcount = msg[0x11] status.append(('LED accessories', lcount, '')) @@ -284,8 +291,11 @@ class SmartDevice(_CommonSmartDeviceDriver): ltype, lsize = [('HUE+ Strip', 10), ('Aer RGB', 8)][msg[0x10] >> 3] status.append(('LED accessory type', ltype, '')) status.append(('LED count (total)', lcount*lsize, '')) + status.append(('Noise level', round(sum(noise)/len(noise)), 'dB')) - return sorted(status) + + # flatten non None fan data and concat with status + return [x for fan_data in fans if fan_data for x in fan_data] + status def _write_colors(self, cid, mode, colors, sval, direction='forward'): mval, mod3, mod4, _, _ = self._COLOR_MODES[mode] diff --git a/liquidctl/version.py b/liquidctl/version.py index 51ed7c4..bcd8d54 100644 --- a/liquidctl/version.py +++ b/liquidctl/version.py @@ -1 +1 @@ -__version__ = '1.5.1' +__version__ = '1.6.0' diff --git a/setup.py b/setup.py index ade6663..15821ea 100644 --- a/setup.py +++ b/setup.py @@ -70,7 +70,7 @@ CHANGES_URL = '{}/blob/v{}/CHANGELOG.md'.format(HOME, VERSION) make_extraversion() -install_requires = ['docopt', 'pyusb', 'hidapi', 'tomlkit'] +install_requires = ['docopt', 'pyusb', 'hidapi', 'colorlog', 'tomlkit'] if sys.platform == 'linux': install_requires.append('smbus') diff --git a/tests/test_commander_pro.py b/tests/test_commander_pro.py index 49b960a..85e6087 100644 --- a/tests/test_commander_pro.py +++ b/tests/test_commander_pro.py @@ -1,7 +1,7 @@ import pytest from _testutils import MockHidapiDevice, Report, MockRuntimeStorage -from liquidctl.driver.commander_pro import _quoted, _prepare_profile, _get_fan_mode_description, CommanderPro +from liquidctl.driver.commander_pro import _quoted, _prepare_profile, _fan_mode_desc, CommanderPro from liquidctl.error import NotSupportedByDevice @@ -117,22 +117,22 @@ def test_quoted_not_string(): # fan modes def test_get_fan_mode_description_auto(): - assert _get_fan_mode_description(0x00) == 'Auto/Disconnected' + assert _fan_mode_desc(0x00) == None def test_get_fan_mode_description_unknown(): - assert _get_fan_mode_description(0x03) == 'UNKNOWN' - assert _get_fan_mode_description(0x04) == 'UNKNOWN' - assert _get_fan_mode_description(0x10) == 'UNKNOWN' - assert _get_fan_mode_description(0xff) == 'UNKNOWN' + assert _fan_mode_desc(0x03) == None + assert _fan_mode_desc(0x04) == None + assert _fan_mode_desc(0x10) == None + assert _fan_mode_desc(0xff) == None def test_get_fan_mode_description_dc(): - assert _get_fan_mode_description(0x01) == 'DC' + assert _fan_mode_desc(0x01) == 'DC' def test_get_fan_mode_description_pwm(): - assert _get_fan_mode_description(0x02) == 'PWM' + assert _fan_mode_desc(0x02) == 'PWM' # class methods @@ -177,20 +177,20 @@ def test_initialize_commander_pro(commanderProDevice): res = commanderProDevice.initialize() assert len(res) == 12 - assert res[0][1] == '0.9.212' - assert res[1][1] == '0.5' + assert res[0] == ('Firmware version', '0.9.212', '') + assert res[1] == ('Bootloader version', '0.5', '') - assert res[2][1] == 'Connected' - assert res[3][1] == 'Connected' - assert res[4][1] == 'Not Connected' - assert res[5][1] == 'Connected' + assert res[2] == ('Temperature probe 1', True, '') + assert res[3] == ('Temperature probe 2', True, '') + assert res[4] == ('Temperature probe 3', False, '') + assert res[5] == ('Temperature probe 4', True, '') - assert res[6][1] == 'DC' - assert res[7][1] == 'DC' - assert res[8][1] == 'PWM' - assert res[9][1] == 'Auto/Disconnected' - assert res[10][1] == 'Auto/Disconnected' - assert res[11][1] == 'Auto/Disconnected' + assert res[6] == ('Fan 1 control mode', 'DC', '') + assert res[7] == ('Fan 2 control mode', 'DC', '') + assert res[8] == ('Fan 3 control mode', 'PWM', '') + assert res[9] == ('Fan 4 control mode', None, '') + assert res[10] == ('Fan 5 control mode', None, '') + assert res[11] == ('Fan 6 control mode', None, '') data = commanderProDevice._data.load('fan_modes', None) assert data is not None @@ -238,12 +238,12 @@ def test_get_status_commander_pro(commanderProDevice): '000a8300000000000000000000000000', # temp sensor 1 '000b6a00000000000000000000000000', # temp sensor 2 '000a0e00000000000000000000000000', # temp sensor 4 + '0003ac00000000000000000000000000', # fan speed 1 + '0003ab00000000000000000000000000', # fan speed 2 + '0003db00000000000000000000000000', # fan speed 3 '002f2200000000000000000000000000', # get 12v '00136500000000000000000000000000', # get 5v '000d1f00000000000000000000000000', # get 3.3v - '0003ac00000000000000000000000000', # fan speed 1 - '0003ab00000000000000000000000000', # fan speed 2 - '0003db00000000000000000000000000' # fan speed 3 ] for d in responses: commanderProDevice.device.preload_read(Report(0, bytes.fromhex(d))) @@ -252,27 +252,24 @@ def test_get_status_commander_pro(commanderProDevice): commanderProDevice._data.store('temp_sensors_connected', [0x01, 0x01, 0x00, 0x01]) res = commanderProDevice.get_status() + print(res) - assert len(res) == 13 - - # voltages - assert res[0][1] == 12.066 # 12v - assert res[1][1] == 4.965 # 5v - assert res[2][1] == 3.359 # 3.3v + assert len(res) == 9 # temp probes - assert res[3][1] == 26.91 - assert res[4][1] == 29.22 - assert res[5][1] == 0.0 - assert res[6][1] == 25.74 + assert res[0] == ('Temperature 1', 26.91, '°C') + assert res[1] == ('Temperature 2', 29.22, '°C') + assert res[2] == ('Temperature 4', 25.74, '°C') # fans rpm - assert res[7][1] == 940 - assert res[8][1] == 939 - assert res[9][1] == 987 - assert res[10][1] == 0 - assert res[11][1] == 0 - assert res[12][1] == 0 + assert res[3] == ('Fan 1 speed', 940, 'rpm') + assert res[4] == ('Fan 2 speed', 939, 'rpm') + assert res[5] == ('Fan 3 speed', 987, 'rpm') + + # voltages + assert res[6] == ('+12V rail', 12.066, 'V') + assert res[7] == ('+5V rail', 4.965, 'V') + assert res[8] == ('+3.3V rail', 3.359, 'V') # check the commands sent sent = commanderProDevice.device.sent @@ -282,13 +279,13 @@ def test_get_status_commander_pro(commanderProDevice): assert sent[1].data[0] == 0x11 assert sent[2].data[0] == 0x11 - assert sent[3].data[0] == 0x12 - assert sent[4].data[0] == 0x12 - assert sent[5].data[0] == 0x12 + assert sent[3].data[0] == 0x21 + assert sent[4].data[0] == 0x21 + assert sent[5].data[0] == 0x21 - assert sent[6].data[0] == 0x21 - assert sent[7].data[0] == 0x21 - assert sent[8].data[0] == 0x21 + assert sent[6].data[0] == 0x12 + assert sent[7].data[0] == 0x12 + assert sent[8].data[0] == 0x12 def test_get_status_lighting_pro(lightingNodeProDevice): diff --git a/tests/test_smart_device.py b/tests/test_smart_device.py index db7e4bf..b2d8185 100644 --- a/tests/test_smart_device.py +++ b/tests/test_smart_device.py @@ -3,6 +3,12 @@ from _testutils import MockHidapiDevice, Report from liquidctl.driver.smart_device import SmartDevice +SAMPLE_RESPONSES = [ + '043e00056e00000b5b000301000007200002001e00', + '04400005b500000b5b000201000007020002001e00', + '044000053800000b5b000201000007120102001e00', +] + @pytest.fixture def mockSmartDevice(): @@ -35,3 +41,38 @@ def test_smart_device_not_totally_broken(mockSmartDevice): speed='fastest') dev.set_fixed_speed(channel='fan3', duty=50) + + +def test_smart_device_reads_status(mockSmartDevice): + dev = mockSmartDevice + + for _, capdata in enumerate(SAMPLE_RESPONSES): + capdata = bytes.fromhex(capdata) + dev.device.preload_read(Report(capdata[0], capdata[1:])) + + # skip initialize for now, we're not emulating the behavior precisely + # enough to require it here + + expected = [ + ('Fan 1 speed', 1461, 'rpm'), + ('Fan 1 voltage', 11.91, 'V'), + ('Fan 1 current', 0.02, 'A'), + ('Fan 1 control mode', 'PWM', ''), + ('Fan 2 speed', 1336, 'rpm'), + ('Fan 2 voltage', 11.91, 'V'), + ('Fan 2 current', 0.02, 'A'), + ('Fan 2 control mode', 'PWM', ''), + ('Fan 3 speed', 1390, 'rpm'), + ('Fan 3 voltage', 11.91, 'V'), + ('Fan 3 current', 0.03, 'A'), + ('Fan 3 control mode', None, ''), + ('Firmware version', '1.0.7', ''), + ('LED accessories', 2, ''), + ('LED accessory type', 'HUE+ Strip', ''), + ('LED count (total)', 20, ''), + ('Noise level', 63, 'dB') + ] + + got = dev.get_status() + + assert expected == got