Merge branch 'master' into 195-config-file

This commit is contained in:
Marshall Asch 2021-04-17 11:25:21 -04:00 committed by GitHub
commit d81058314a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 441 additions and 512 deletions

View File

@ -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

View File

@ -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 | <sup>_ZE_</sup> |
| AIO liquid cooler | [Corsair Hydro H80i v2, H100i v2, H115i](docs/asetek-690lc-guide.md) | USB | <sup>_Z_</sup> |
| AIO liquid cooler | [Corsair Hydro H100i Pro, H115i Pro, H150i Pro](docs/asetek-pro-guide.md) | USB | <sup>_EZN_</sup> |
| AIO liquid cooler | [Corsair Hydro H100i Pro, H115i Pro, H150i Pro](docs/asetek-pro-guide.md) | USB | <sup>_EZ_</sup> |
| AIO liquid cooler | [Corsair Hydro H100i Platinum [SE], H115i Platinum](docs/corsair-platinum-pro-xt-guide.md) | USB HID | <sup>_E_</sup> |
| AIO liquid cooler | [Corsair Hydro H100i Pro XT, H115i Pro XT](docs/corsair-platinum-pro-xt-guide.md) | USB HID | <sup>_E_</sup> |
| AIO liquid cooler | [Corsair Hydro H150i Pro XT](docs/corsair-platinum-pro-xt-guide.md) | USB HID | <sup>_EN_</sup> |
| AIO liquid cooler | [Corsair Hydro H100i Pro XT, H115i Pro XT, H150i Pro XT](docs/corsair-platinum-pro-xt-guide.md) | USB HID | <sup>_E_</sup> |
| AIO liquid cooler | [EVGA CLC 120 (CL12), 240, 280, 360](docs/asetek-690lc-guide.md) | USB | <sup>_Z_</sup> |
| 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 | <sup>_LZE_</sup> |
| AIO liquid cooler | [NZXT Kraken X31, X41, X61](docs/asetek-690lc-guide.md) | USB | <sup>_LZE_</sup> |
| 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 | <sup>_E_</sup> |
| AIO liquid cooler | [NZXT Kraken Z53, Z63, Z73](docs/kraken-x3-z3-guide.md) | USB & USB HID | <sup>_E_</sup> |
| DDR4 DRAM | [Corsair Vengeance RGB](docs/ddr4-guide.md) | SMBus | <sup>_EUX_</sup> |
| DDR4 DRAM | [DIMMs with a standard temperature sensor](docs/ddr4-guide.md) | SMBus | <sup>_EUX_</sup> |
| Fan/LED controller | [Corsair Commander Pro](docs/corsair-commander-guide.md) | USB HID | <sup>_E_</sup> |
| Fan/LED controller | [Corsair Lighting Node Core](docs/corsair-commander-guide.md) | USB HID | <sup>_EN_</sup> |
| Fan/LED controller | [Corsair Lighting Node Pro](docs/corsair-commander-guide.md) | USB HID | <sup>_E_</sup> |
| Fan/LED controller | [Corsair Lighting Node Core, Pro](docs/corsair-commander-guide.md) | USB HID | <sup>_E_</sup> |
| 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 | <sup>_UXN_</sup> |
| Graphics card | [ASUS Strix GTX 1070](docs/nvidia-guide.md) | I²C | <sup>_UX_</sup> |
| Graphics card | [ASUS Strix RTX 2080 Ti OC](docs/nvidia-guide.md) | I²C | <sup>_UX_</sup> |
| Graphics card | [EVGA GTX 1080 FTW](docs/nvidia-guide.md) | I²C | <sup>_UX_</sup> |
| 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

View File

@ -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
```

View File

@ -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<version>"
- [ ] 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<version>"`:_
- [ ] 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<version>` and short summary annotation (signed)
- [ ] Push HEAD and `v<version>` 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-<version>.tar.gz` and that generated `extraversion.py` makes sense
- [ ] Sign the source distribution: `gpg --detach-sign -a dist/liquidctl-<version>.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-<version>-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-<version>.tar.gz{,.asc}`
- [ ] Upgrade the `v<version>` 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`

View File

@ -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.

View File

@ -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
```

View File

@ -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 <profile> --fan <profile>
krakencurve-poc --help
krakencurve-poc --version
Options:
--pump <profile> Profile to use for the pump
--fan <profile> Profile to use for the fan
--pump-sensor <sensor> Select alternate sensor for pump speed
--fan-sensor <sensor> Select alternate sensor for fan speed
--interval <seconds> 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) 20182021 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 temperatureduty 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 == '<number>' 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()

View File

@ -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

View File

@ -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"')

View File

@ -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 <sam@borntyping.co.uk>
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 20092017 Wander Lairson Costa

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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 []

View File

@ -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) 20202021 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,
})

View File

@ -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, \

View File

@ -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]

View File

@ -1 +1 @@
__version__ = '1.5.1'
__version__ = '1.6.0'

View File

@ -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')

View File

@ -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):

View File

@ -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