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

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

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

@ -200,7 +200,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}}'

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

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