Merge #326: improve Smart Device and Commander Pro output

This commit is contained in:
Jonas Malaco 2021-04-16 20:21:04 -03:00 committed by GitHub
commit bec98c15b2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 173 additions and 147 deletions

View File

@ -20,17 +20,17 @@ Device #4: NZXT Kraken X (X42, X52, X62 or X72)
# liquidctl status # liquidctl status
NZXT Smart Device (V1) NZXT Smart Device (V1)
├── Fan 1 PWM ├── Fan 1 speed 1473 rpm
├── Fan 1 current 0.04 A
├── Fan 1 speed 1368 rpm
├── Fan 1 voltage 11.91 V ├── Fan 1 voltage 11.91 V
├── Fan 2 — ├── Fan 1 current 0.01 A
├── Fan 3 — ├── Fan 1 control mode PWM
├── Firmware version 1.0.7 ├── Fan 2 [...]
├── LED accessories 2 ├── Fan 2 [...]
├── LED accessory type HUE+ Strip ├── Firmware version 1.0.7
├── LED count (total) 20 ├── LED accessories 2
└── Noise level 67 dB ├── LED accessory type HUE+ Strip
├── LED count (total) 20
└── Noise level 65 dB
NZXT Kraken X (X42, X52, X62 or X72) NZXT Kraken X (X42, X52, X62 or X72)
├── Liquid temperature 31.7 °C ├── Liquid temperature 31.7 °C

View File

@ -13,25 +13,25 @@ sensors and fan types are currently connected.
``` ```
# liquidctl initialize # liquidctl initialize
Corsair Commander Pro (experimental) Corsair Commander Pro (experimental)
├── Firmware version 0.9.212 ├── Firmware version 0.9.212
├── Bootloader version 0.5 ├── Bootloader version 0.5
├── Temp sensor 1 Connected ├── Temperature probe 1 Yes
├── Temp sensor 2 Connected ├── Temperature probe 2 Yes
├── Temp sensor 3 Connected ├── Temperature probe 3 No
├── Temp sensor 4 Connected ├── Temperature probe 4 No
├── Fan 1 Mode DC ├── Fan 1 control mode PWM
├── Fan 2 Mode DC ├── Fan 2 control mode PWM
├── Fan 3 Mode DC ├── Fan 3 control mode DC
├── Fan 4 Mode Auto/Disconnected ├── Fan 4 control mode N/A
├── Fan 5 Mode Auto/Disconnected ├── Fan 5 control mode N/A
└── Fan 6 Mode Auto/Disconnected └── Fan 6 control mode N/A
``` ```
``` ```
# liquidctl initialize # liquidctl initialize
Corsair Lighting Node Pro (experimental) Corsair Lighting Node Pro (experimental)
├── Firmware version 0.10.4 ├── Firmware version 0.10.4
└── Bootloader version 3.0 └── 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 # liquidctl status
Corsair Commander Pro (experimental) Corsair Commander Pro (experimental)
├── 12 volt rail 12.06 V ├── Temperature 1 26.4 °C
├── 5 volt rail 4.96 V ├── Temperature 2 27.5 °C
├── 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
├── Fan 1 speed 927 rpm ├── Fan 1 speed 927 rpm
├── Fan 2 speed 927 rpm ├── Fan 2 speed 927 rpm
├── Fan 3 speed 1195 rpm ├── Fan 3 speed 1195 rpm
├── Fan 4 speed 0 rpm ├── +12V rail 12.06 V
├── Fan 5 speed 0 rpm ├── +5V rail 4.96 V
└── Fan 6 speed 0 rpm └── +3.3V rail 3.36 V
``` ```

View File

@ -32,23 +32,23 @@ The device can report fan information for each channel, the noise level at the o
``` ```
# liquidctl status # liquidctl status
NZXT Smart Device (V1) NZXT Smart Device (V1)
├── Fan 1 PWM ├── Fan 1 speed 1473 rpm
├── Fan 1 current 0.04 A
├── Fan 1 speed 1064 rpm
├── Fan 1 voltage 11.91 V ├── Fan 1 voltage 11.91 V
├── Fan 2 PWM ├── Fan 1 current 0.01 A
├── Fan 2 current 0.01 A ├── Fan 1 control mode PWM
├── Fan 2 speed 1051 rpm ├── Fan 2 speed 1341 rpm
├── Fan 2 voltage 11.77 V ├── Fan 2 voltage 11.91 V
├── Fan 3 PWM ├── Fan 2 current 0.02 A
├── Fan 3 current 0.09 A ├── Fan 2 control mode DC
├── Fan 3 speed 1581 rpm ├── Fan 3 speed 1352 rpm
├── Fan 3 voltage 11.77 V ├── Fan 3 voltage 11.91 V
├── Firmware version 1.0.7 ├── Fan 3 current 0.02 A
├── LED accessories 2 ├── Fan 3 control mode N/A
├── LED accessory type HUE+ Strip ├── Firmware version 1.0.7
├── LED count (total) 20 ├── LED accessories 2
└── Noise level 62 dB ├── LED accessory type HUE+ Strip
├── LED count (total) 20
└── Noise level 65 dB
``` ```

View File

@ -200,7 +200,10 @@ def _print_dev_status(dev, status):
for k, v, u in status: for k, v, u in status:
if isinstance(v, datetime.timedelta): if isinstance(v, datetime.timedelta):
v = str(v) v = str(v)
u = '' elif isinstance(v, bool):
v = 'Yes' if v else 'No'
elif v is None:
v = 'N/A'
else: else:
valfmt = _VALUE_FORMATS.get(u, '') valfmt = _VALUE_FORMATS.get(u, '')
v = f'{v:{valfmt}}' v = f'{v:{valfmt}}'

View File

@ -99,18 +99,18 @@ def _quoted(*names):
return ', '.join(map(repr, 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. """This will convert the fan mode value to a descriptive name.
""" """
if mode == _FAN_MODE_DISCONNECTED: if mode == _FAN_MODE_DC:
return 'Auto/Disconnected'
elif mode == _FAN_MODE_DC:
return 'DC' return 'DC'
elif mode == _FAN_MODE_PWM: elif mode == _FAN_MODE_PWM:
return 'PWM' return 'PWM'
else: else:
return 'UNKNOWN' if mode != _FAN_MODE_DISCONNECTED:
_LOGGER.warning('unknown fan mode: {mode:#04x}')
return None
class CommanderPro(UsbHidDriver): class CommanderPro(UsbHidDriver):
@ -178,10 +178,8 @@ class CommanderPro(UsbHidDriver):
temp_connected = res[1:5] temp_connected = res[1:5]
self._data.store('temp_sensors_connected', temp_connected) self._data.store('temp_sensors_connected', temp_connected)
status += [ status += [
('Temp sensor 1', 'Connected' if temp_connected[0] else 'Not Connected', ''), (f'Temperature probe {i + 1}', bool(temp_connected[i]), '')
('Temp sensor 2', 'Connected' if temp_connected[1] else 'Not Connected', ''), for i in range(4)
('Temp sensor 3', 'Connected' if temp_connected[2] else 'Not Connected', ''),
('Temp sensor 4', 'Connected' if temp_connected[3] else 'Not Connected', ''),
] ]
if self._fan_count > 0: if self._fan_count > 0:
@ -190,12 +188,8 @@ class CommanderPro(UsbHidDriver):
fanModes = res[1:self._fan_count+1] fanModes = res[1:self._fan_count+1]
self._data.store('fan_modes', fanModes) self._data.store('fan_modes', fanModes)
status += [ status += [
('Fan 1 Mode', _get_fan_mode_description(fanModes[0]), ''), (f'Fan {i + 1} control mode', _fan_mode_desc(fanModes[i]), '')
('Fan 2 Mode', _get_fan_mode_description(fanModes[1]), ''), for i in range(6)
('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]), ''),
] ]
return status return status
@ -210,42 +204,28 @@ class CommanderPro(UsbHidDriver):
_LOGGER.debug('only the Commander Pro supports this') _LOGGER.debug('only the Commander Pro supports this')
return [] 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) fan_modes = self._data.load('fan_modes', default=[0]*self._fan_count)
status = []
# get the temperature sensor values # get the temperature sensor values
temp = [0]*self._temp_probs for i, probe_enabled in enumerate(temp_probes):
for num, enabled in enumerate(connected_temp_sensors): if probe_enabled:
if enabled: temp = self._get_temp(i)
temp[num] = self._get_temp(num) status.append((f'Temperature {i + 1}', temp, '°C'))
# 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
# get fan RPMs of connected fans # get fan RPMs of connected fans
fanspeeds = [0]*self._fan_count for i, fan_mode in enumerate(fan_modes):
for fan_num, mode in enumerate(fan_modes): if fan_mode == _FAN_MODE_DC or fan_mode == _FAN_MODE_PWM:
if mode == _FAN_MODE_DC or mode == _FAN_MODE_PWM: speed = self._get_fan_rpm(i)
fanspeeds[fan_num] = self._get_fan_rpm(fan_num) status.append((f'Fan {i + 1} speed', speed, 'rpm'))
status = [ # get the real power supply voltages
('12 volt rail', volt_12, 'V'), for i, rail in enumerate(["+12V", "+5V", "+3.3V"]):
('5 volt rail', volt_5, 'V'), raw = self._send_command(_CMD_GET_VOLTS, [i])
('3.3 volt rail', volt_3, 'V'), voltage = u16be_from(raw, offset=1) / 1000
] status.append((f'{rail} rail', voltage, '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')]
return status return status

View File

@ -261,22 +261,29 @@ class SmartDevice(_CommonSmartDeviceDriver):
""" """
status = [] status = []
fans = [None] * len(self._speed_channels)
noise = [] noise = []
self.device.clear_enqueued_reports() self.device.clear_enqueued_reports()
for i, _ in enumerate(self._speed_channels): for i, _ in enumerate(self._speed_channels):
msg = self.device.read(self._READ_LENGTH) msg = self.device.read(self._READ_LENGTH)
num = (msg[15] >> 4) + 1 num = (msg[15] >> 4) + 1
state = msg[15] & 0x3 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]) 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: if i != 0:
continue continue
fw = '{}.{}.{}'.format(msg[0xb], msg[0xc] << 8 | msg[0xd], msg[0xe]) fw = '{}.{}.{}'.format(msg[0xb], msg[0xc] << 8 | msg[0xd], msg[0xe])
status.append(('Firmware version', fw, '')) status.append(('Firmware version', fw, ''))
if self._color_channels: if self._color_channels:
lcount = msg[0x11] lcount = msg[0x11]
status.append(('LED accessories', lcount, '')) status.append(('LED accessories', lcount, ''))
@ -284,8 +291,11 @@ class SmartDevice(_CommonSmartDeviceDriver):
ltype, lsize = [('HUE+ Strip', 10), ('Aer RGB', 8)][msg[0x10] >> 3] ltype, lsize = [('HUE+ Strip', 10), ('Aer RGB', 8)][msg[0x10] >> 3]
status.append(('LED accessory type', ltype, '')) status.append(('LED accessory type', ltype, ''))
status.append(('LED count (total)', lcount*lsize, '')) status.append(('LED count (total)', lcount*lsize, ''))
status.append(('Noise level', round(sum(noise)/len(noise)), 'dB')) 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'): def _write_colors(self, cid, mode, colors, sval, direction='forward'):
mval, mod3, mod4, _, _ = self._COLOR_MODES[mode] mval, mod3, mod4, _, _ = self._COLOR_MODES[mode]

View File

@ -1,7 +1,7 @@
import pytest import pytest
from _testutils import MockHidapiDevice, Report, MockRuntimeStorage 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 from liquidctl.error import NotSupportedByDevice
@ -117,22 +117,22 @@ def test_quoted_not_string():
# fan modes # fan modes
def test_get_fan_mode_description_auto(): 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(): def test_get_fan_mode_description_unknown():
assert _get_fan_mode_description(0x03) == 'UNKNOWN' assert _fan_mode_desc(0x03) == None
assert _get_fan_mode_description(0x04) == 'UNKNOWN' assert _fan_mode_desc(0x04) == None
assert _get_fan_mode_description(0x10) == 'UNKNOWN' assert _fan_mode_desc(0x10) == None
assert _get_fan_mode_description(0xff) == 'UNKNOWN' assert _fan_mode_desc(0xff) == None
def test_get_fan_mode_description_dc(): 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(): def test_get_fan_mode_description_pwm():
assert _get_fan_mode_description(0x02) == 'PWM' assert _fan_mode_desc(0x02) == 'PWM'
# class methods # class methods
@ -177,20 +177,20 @@ def test_initialize_commander_pro(commanderProDevice):
res = commanderProDevice.initialize() res = commanderProDevice.initialize()
assert len(res) == 12 assert len(res) == 12
assert res[0][1] == '0.9.212' assert res[0] == ('Firmware version', '0.9.212', '')
assert res[1][1] == '0.5' assert res[1] == ('Bootloader version', '0.5', '')
assert res[2][1] == 'Connected' assert res[2] == ('Temperature probe 1', True, '')
assert res[3][1] == 'Connected' assert res[3] == ('Temperature probe 2', True, '')
assert res[4][1] == 'Not Connected' assert res[4] == ('Temperature probe 3', False, '')
assert res[5][1] == 'Connected' assert res[5] == ('Temperature probe 4', True, '')
assert res[6][1] == 'DC' assert res[6] == ('Fan 1 control mode', 'DC', '')
assert res[7][1] == 'DC' assert res[7] == ('Fan 2 control mode', 'DC', '')
assert res[8][1] == 'PWM' assert res[8] == ('Fan 3 control mode', 'PWM', '')
assert res[9][1] == 'Auto/Disconnected' assert res[9] == ('Fan 4 control mode', None, '')
assert res[10][1] == 'Auto/Disconnected' assert res[10] == ('Fan 5 control mode', None, '')
assert res[11][1] == 'Auto/Disconnected' assert res[11] == ('Fan 6 control mode', None, '')
data = commanderProDevice._data.load('fan_modes', None) data = commanderProDevice._data.load('fan_modes', None)
assert data is not None assert data is not None
@ -238,12 +238,12 @@ def test_get_status_commander_pro(commanderProDevice):
'000a8300000000000000000000000000', # temp sensor 1 '000a8300000000000000000000000000', # temp sensor 1
'000b6a00000000000000000000000000', # temp sensor 2 '000b6a00000000000000000000000000', # temp sensor 2
'000a0e00000000000000000000000000', # temp sensor 4 '000a0e00000000000000000000000000', # temp sensor 4
'0003ac00000000000000000000000000', # fan speed 1
'0003ab00000000000000000000000000', # fan speed 2
'0003db00000000000000000000000000', # fan speed 3
'002f2200000000000000000000000000', # get 12v '002f2200000000000000000000000000', # get 12v
'00136500000000000000000000000000', # get 5v '00136500000000000000000000000000', # get 5v
'000d1f00000000000000000000000000', # get 3.3v '000d1f00000000000000000000000000', # get 3.3v
'0003ac00000000000000000000000000', # fan speed 1
'0003ab00000000000000000000000000', # fan speed 2
'0003db00000000000000000000000000' # fan speed 3
] ]
for d in responses: for d in responses:
commanderProDevice.device.preload_read(Report(0, bytes.fromhex(d))) 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]) commanderProDevice._data.store('temp_sensors_connected', [0x01, 0x01, 0x00, 0x01])
res = commanderProDevice.get_status() res = commanderProDevice.get_status()
print(res)
assert len(res) == 13 assert len(res) == 9
# voltages
assert res[0][1] == 12.066 # 12v
assert res[1][1] == 4.965 # 5v
assert res[2][1] == 3.359 # 3.3v
# temp probes # temp probes
assert res[3][1] == 26.91 assert res[0] == ('Temperature 1', 26.91, '°C')
assert res[4][1] == 29.22 assert res[1] == ('Temperature 2', 29.22, '°C')
assert res[5][1] == 0.0 assert res[2] == ('Temperature 4', 25.74, '°C')
assert res[6][1] == 25.74
# fans rpm # fans rpm
assert res[7][1] == 940 assert res[3] == ('Fan 1 speed', 940, 'rpm')
assert res[8][1] == 939 assert res[4] == ('Fan 2 speed', 939, 'rpm')
assert res[9][1] == 987 assert res[5] == ('Fan 3 speed', 987, 'rpm')
assert res[10][1] == 0
assert res[11][1] == 0 # voltages
assert res[12][1] == 0 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 # check the commands sent
sent = commanderProDevice.device.sent sent = commanderProDevice.device.sent
@ -282,13 +279,13 @@ def test_get_status_commander_pro(commanderProDevice):
assert sent[1].data[0] == 0x11 assert sent[1].data[0] == 0x11
assert sent[2].data[0] == 0x11 assert sent[2].data[0] == 0x11
assert sent[3].data[0] == 0x12 assert sent[3].data[0] == 0x21
assert sent[4].data[0] == 0x12 assert sent[4].data[0] == 0x21
assert sent[5].data[0] == 0x12 assert sent[5].data[0] == 0x21
assert sent[6].data[0] == 0x21 assert sent[6].data[0] == 0x12
assert sent[7].data[0] == 0x21 assert sent[7].data[0] == 0x12
assert sent[8].data[0] == 0x21 assert sent[8].data[0] == 0x12
def test_get_status_lighting_pro(lightingNodeProDevice): 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 from liquidctl.driver.smart_device import SmartDevice
SAMPLE_RESPONSES = [
'043e00056e00000b5b000301000007200002001e00',
'04400005b500000b5b000201000007020002001e00',
'044000053800000b5b000201000007120102001e00',
]
@pytest.fixture @pytest.fixture
def mockSmartDevice(): def mockSmartDevice():
@ -35,3 +41,38 @@ def test_smart_device_not_totally_broken(mockSmartDevice):
speed='fastest') speed='fastest')
dev.set_fixed_speed(channel='fan3', duty=50) 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