393 lines
14 KiB
Python
393 lines
14 KiB
Python
import pytest
|
|
from _testutils import MockHidapiDevice, Report, MockRuntimeStorage
|
|
|
|
from liquidctl.driver.hydro_platinum import HydroPlatinum, _sequence
|
|
from liquidctl.util import mkCrcFun
|
|
|
|
_SAMPLE_PATH = (r'IOService:/AppleACPIPlatformExpert/PCI0@0/AppleACPIPCI/XHC@14/XH'
|
|
r'C@14000000/HS11@14a00000/USB2.0 Hub@14a00000/AppleUSB20InternalH'
|
|
r'ub@14a00000/AppleUSB20HubPort@14a10000/USB2.0 Hub@14a10000/Apple'
|
|
r'USB20Hub@14a10000/AppleUSB20HubPort@14a12000/H100i Platinum@14a1'
|
|
r'2000/IOUSBHostInterface@0/AppleUserUSBHostHIDDevice+Win\\#!&3142')
|
|
|
|
_WIN_MAX_PATH = 260 # Windows API should be the bottleneck
|
|
|
|
_crc8 = mkCrcFun('crc-8')
|
|
|
|
|
|
@pytest.fixture
|
|
def h115iPlatinumDevice():
|
|
description = 'Mock H115i Platinum'
|
|
kwargs = {'fan_count': 2, 'fan_leds': 4}
|
|
device = _MockHydroPlatinumDevice()
|
|
dev = HydroPlatinum(device, description, **kwargs)
|
|
|
|
runtime_storage = MockRuntimeStorage(key_prefixes=['testing'])
|
|
runtime_storage.store('leds_enabled', 0)
|
|
|
|
dev.connect(runtime_storage=runtime_storage)
|
|
|
|
return dev
|
|
|
|
@pytest.fixture
|
|
def h100iPlatinumSeDevice():
|
|
description = 'Mock H100i Platinum SE'
|
|
kwargs = {'fan_count': 2, 'fan_leds': 16}
|
|
device = _MockHydroPlatinumDevice()
|
|
dev = HydroPlatinum(device, description, **kwargs)
|
|
|
|
runtime_storage = MockRuntimeStorage(key_prefixes=['testing'])
|
|
runtime_storage.store('leds_enabled', 0)
|
|
|
|
dev.connect(runtime_storage=runtime_storage)
|
|
|
|
return dev
|
|
|
|
@pytest.fixture
|
|
def h150iProXTDevice():
|
|
description = 'Mock H150i Pro XT'
|
|
kwargs = {'fan_count': 3, 'fan_leds': 0}
|
|
device = _MockHydroPlatinumDevice()
|
|
dev = HydroPlatinum(device, description, **kwargs)
|
|
|
|
runtime_storage = MockRuntimeStorage(key_prefixes=['testing'])
|
|
runtime_storage.store('leds_enabled', 0)
|
|
|
|
dev.connect(runtime_storage=runtime_storage)
|
|
|
|
return dev
|
|
|
|
class _MockHydroPlatinumDevice(MockHidapiDevice):
|
|
def __init__(self):
|
|
super().__init__(vendor_id=0xffff, product_id=0x0c17, address=_SAMPLE_PATH)
|
|
self.fw_version = (1, 1, 15)
|
|
self.temperature = 30.9
|
|
self.fan1_speed = 1499
|
|
self.fan2_speed = 1512
|
|
self.fan3_speed = 1777
|
|
self.pump_speed = 2702
|
|
|
|
def read(self, length):
|
|
pre = super().read(length)
|
|
if pre:
|
|
return pre
|
|
buf = bytearray(64)
|
|
buf[2] = self.fw_version[0] << 4 | self.fw_version[1]
|
|
buf[3] = self.fw_version[2]
|
|
buf[7] = int((self.temperature - int(self.temperature)) * 255)
|
|
buf[8] = int(self.temperature)
|
|
buf[14] = round(.10 * 255)
|
|
buf[15:17] = self.fan1_speed.to_bytes(length=2, byteorder='little')
|
|
buf[21] = round(.20 * 255)
|
|
buf[22:24] = self.fan2_speed.to_bytes(length=2, byteorder='little')
|
|
buf[28] = round(.70 * 255)
|
|
buf[29:31] = self.pump_speed.to_bytes(length=2, byteorder='little')
|
|
buf[42] = round(.30 * 255)
|
|
buf[43:44] = self.fan3_speed.to_bytes(length=2, byteorder='little')
|
|
buf[-1] = _crc8(buf[1:-1])
|
|
return buf[:length]
|
|
|
|
|
|
def test_sequence_numbers_are_correctly_generated():
|
|
runtime_storage = MockRuntimeStorage(key_prefixes=['testing'])
|
|
sequence = _sequence(runtime_storage)
|
|
|
|
for i in range(1, 32):
|
|
assert next(sequence) == i
|
|
|
|
for i in range(1, 32):
|
|
assert next(sequence) == i
|
|
|
|
|
|
def test_h115i_platinum_device_connect(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
dev.disconnect() # the fixture had by default connected to the device
|
|
|
|
def mock_open():
|
|
nonlocal opened
|
|
opened = True
|
|
dev.device.open = mock_open
|
|
opened = False
|
|
|
|
with dev.connect() as cm:
|
|
assert cm == dev
|
|
assert opened
|
|
|
|
|
|
def test_h115i_platinum_device_command_format(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
dev.initialize()
|
|
dev.get_status()
|
|
dev.set_fixed_speed(channel='fan', duty=100)
|
|
dev.set_speed_profile(channel='fan', profile=[])
|
|
dev.set_color(channel='led', mode='off', colors=[])
|
|
|
|
assert len(dev.device.sent) == 9
|
|
for i, (report, data) in enumerate(dev.device.sent):
|
|
assert report == 0
|
|
assert len(data) == 64
|
|
assert data[0] == 0x3f
|
|
assert data[1] >> 3 == i + 1
|
|
assert data[-1] == _crc8(bytes(data[1:-1]))
|
|
|
|
|
|
def test_h115i_platinum_device_command_format_enabled(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
|
|
# test that the led enable messages are not sent if they are sent again
|
|
dev.initialize()
|
|
dev._data.store('leds_enabled', 1)
|
|
dev.get_status()
|
|
dev.set_fixed_speed(channel='fan', duty=100)
|
|
dev.set_speed_profile(channel='fan', profile=[])
|
|
dev.set_color(channel='led', mode='off', colors=[])
|
|
|
|
assert len(dev.device.sent) == 6
|
|
for i, (report, data) in enumerate(dev.device.sent):
|
|
assert report == 0
|
|
assert len(data) == 64
|
|
assert data[0] == 0x3f
|
|
assert data[1] >> 3 == i + 1
|
|
assert data[-1] == _crc8(bytes(data[1:-1]))
|
|
|
|
|
|
def test_h115i_platinum_device_get_status(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
temp, fan1, fan1d, fan2, fan2d, pump, pumpd = dev.get_status()
|
|
|
|
assert temp[1] == pytest.approx(dev.device.temperature, abs=1 / 255)
|
|
assert fan1[1] == dev.device.fan1_speed
|
|
assert fan1d[1] == 10
|
|
assert fan2[1] == dev.device.fan2_speed
|
|
assert fan2d[1] == 20
|
|
assert pump[1] == dev.device.pump_speed
|
|
assert pumpd[1] == 70
|
|
assert dev.device.sent[0].data[1] & 0b111 == 0
|
|
assert dev.device.sent[0].data[2] == 0xff
|
|
|
|
|
|
def test_h150i_pro_xt_device_get_status(h150iProXTDevice):
|
|
dev = h150iProXTDevice
|
|
temp, fan1, fan1d, fan2, fan2d, fan3, fan3d, pump, pumpd = dev.get_status()
|
|
|
|
assert temp[1] == pytest.approx(dev.device.temperature, abs=1 / 255)
|
|
assert fan1[1] == dev.device.fan1_speed
|
|
assert fan1d[1] == 10
|
|
assert fan2[1] == dev.device.fan2_speed
|
|
assert fan2d[1] == 20
|
|
assert fan3[1] == dev.device.fan3_speed
|
|
assert fan3d[1] == 30
|
|
assert pump[1] == dev.device.pump_speed
|
|
assert pumpd[1] == 70
|
|
assert dev.device.sent[0].data[1] & 0b111 == 0
|
|
assert dev.device.sent[0].data[2] == 0xff
|
|
|
|
|
|
def test_h115i_platinum_device_handle_real_statuses(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
samples = [
|
|
(
|
|
'ff08110f0001002c1e0000aee803aed10700aee803aece0701aa0000aa9c0900'
|
|
'0000000000000000000000000000000000000000000000000000000000000010'
|
|
),
|
|
(
|
|
'ff40110f009e14011b0102ffe8037e6a0502ffe8037e6d0501aa0000aa350901'
|
|
'0000000000000000000000000000000000000000000000000000000000000098'
|
|
)
|
|
]
|
|
for sample in samples:
|
|
dev.device.preload_read(Report(0, bytes.fromhex(sample)))
|
|
status = dev.get_status()
|
|
assert len(status) == 7
|
|
assert status[0][1] != dev.device.temperature
|
|
|
|
|
|
def test_h115i_platinum_device_initialize_status(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
dev._data.store('leds_enabled', 1)
|
|
(fw_version, ) = dev.initialize()
|
|
|
|
assert fw_version[1] == '%d.%d.%d' % dev.device.fw_version
|
|
assert dev._data.load('leds_enabled', of_type=int, default=1) == 0
|
|
|
|
|
|
def test_h115i_platinum_device_common_cooling_prefix(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
dev.initialize(pump_mode='extreme')
|
|
dev.set_fixed_speed(channel='fan', duty=42)
|
|
dev.set_speed_profile(channel='fan', profile=[(20, 0), (55, 100)])
|
|
|
|
assert len(dev.device.sent) == 3
|
|
for _, data in dev.device.sent:
|
|
assert data[0x1] & 0b111 == 0
|
|
assert data[0x2] == 0x14
|
|
# opaque but apparently important prefix (see @makk50's comments in #82):
|
|
assert data[0x3:0xb] == [0x0, 0xff, 0x5] + 5 * [0xff]
|
|
|
|
|
|
def test_h115i_platinum_device_set_pump_mode(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
dev.initialize(pump_mode='extreme')
|
|
assert dev.device.sent[0].data[0x17] == 0x2
|
|
|
|
with pytest.raises(KeyError):
|
|
dev.initialize(pump_mode='invalid')
|
|
|
|
|
|
def test_h115i_platinum_device_fixed_fan_speeds(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
dev.set_fixed_speed(channel='fan', duty=42)
|
|
dev.set_fixed_speed(channel='fan1', duty=84)
|
|
|
|
assert dev.device.sent[-1].data[0x0b] == 0x2
|
|
assert dev.device.sent[-1].data[0x10] / 2.55 == pytest.approx(84, abs=1 / 2.55)
|
|
assert dev.device.sent[-1].data[0x11] == 0x2
|
|
assert dev.device.sent[-1].data[0x16] / 2.55 == pytest.approx(42, abs=1 / 2.55)
|
|
|
|
with pytest.raises(ValueError):
|
|
dev.set_fixed_speed('invalid', 0)
|
|
|
|
|
|
def test_h150i_pro_xt_device_fixed_fan_speeds(h150iProXTDevice):
|
|
dev = h150iProXTDevice
|
|
dev.set_fixed_speed(channel='fan', duty=42)
|
|
dev.set_fixed_speed(channel='fan1', duty=84)
|
|
dev.set_fixed_speed(channel='fan3', duty=50)
|
|
|
|
assert dev.device.sent[-1].data[0x01] & 0x7 == 0b000
|
|
assert dev.device.sent[-2].data[0x02] == 0x14
|
|
assert dev.device.sent[-1].data[0x0b] == 0x2
|
|
assert dev.device.sent[-1].data[0x10] / 2.55 == pytest.approx(84, abs=1 / 2.55)
|
|
assert dev.device.sent[-1].data[0x11] == 0x2
|
|
assert dev.device.sent[-1].data[0x16] / 2.55 == pytest.approx(42, abs=1 / 2.55)
|
|
|
|
assert dev.device.sent[-2].data[0x01] & 0x7 == 0b011
|
|
assert dev.device.sent[-2].data[0x02] == 0x14
|
|
assert dev.device.sent[-2].data[0x0b] == 0x2
|
|
assert dev.device.sent[-2].data[0x10] / 2.55 == pytest.approx(50, abs=1 / 2.55)
|
|
assert dev.device.sent[-2].data[0x11] == 0x00
|
|
assert dev.device.sent[-2].data[0x16] == 0x00
|
|
|
|
|
|
def test_h115i_platinum_device_custom_fan_profiles(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
dev.set_speed_profile(channel='fan', profile=iter([(20, 0), (55, 100)]))
|
|
dev.set_speed_profile(channel='fan1', profile=iter([(30, 20), (50, 80)]))
|
|
|
|
assert dev.device.sent[-1].data[0x0b] == 0x0
|
|
assert dev.device.sent[-1].data[0x1d] == 7
|
|
assert dev.device.sent[-1].data[0x1e:0x2c] == [30, 51, 50, 204] + 5 * [60, 255]
|
|
assert dev.device.sent[-1].data[0x11] == 0x0
|
|
assert dev.device.sent[-1].data[0x2c:0x3a] == [20, 0, 55, 255] + 5 * [60, 255]
|
|
|
|
with pytest.raises(ValueError):
|
|
dev.set_speed_profile('invalid', [])
|
|
|
|
with pytest.raises(ValueError):
|
|
dev.set_speed_profile('fan', zip(range(10), range(10)))
|
|
|
|
|
|
def test_h115i_platinum_device_address_leds(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
colors = [[i + 3, i + 2, i + 1] for i in range(0, 24 * 3, 3)]
|
|
encoded = list(range(1, 24 * 3 + 1))
|
|
dev.set_color(channel='led', mode='super-fixed', colors=iter(colors))
|
|
|
|
assert len(dev.device.sent) == 5 # 3 for enable, 2 for off
|
|
assert dev.device.sent[0].data[1] & 0b111 == 0b001
|
|
assert dev.device.sent[1].data[1] & 0b111 == 0b010
|
|
assert dev.device.sent[2].data[1] & 0b111 == 0b011
|
|
assert dev.device.sent[3].data[1] & 0b111 == 0b100
|
|
assert dev.device.sent[3].data[2:62] == encoded[:60]
|
|
assert dev.device.sent[4].data[1] & 0b111 == 0b101
|
|
assert dev.device.sent[4].data[2:14] == encoded[60:]
|
|
|
|
|
|
def test_h100i_platinum_se_device_address_leds(h100iPlatinumSeDevice):
|
|
dev = h100iPlatinumSeDevice
|
|
colors = [[i + 3, i + 2, i + 1] for i in range(0, 48 * 3, 3)]
|
|
encoded = list(range(1, 48 * 3 + 1))
|
|
dev.set_color(channel='led', mode='super-fixed', colors=iter(colors))
|
|
|
|
assert len(dev.device.sent) == 6 # 3 for enable, 3 for the leds
|
|
assert dev.device.sent[0].data[1] & 0b111 == 0b001
|
|
assert dev.device.sent[1].data[1] & 0b111 == 0b010
|
|
assert dev.device.sent[2].data[1] & 0b111 == 0b011
|
|
assert dev.device.sent[3].data[1] & 0b111 == 0b100
|
|
assert dev.device.sent[3].data[2:62] == encoded[:60]
|
|
assert dev.device.sent[4].data[1] & 0b111 == 0b101
|
|
assert dev.device.sent[4].data[2:62] == encoded[60:120]
|
|
assert dev.device.sent[5].data[1] & 0b111 == 0b110
|
|
assert dev.device.sent[5].data[2:26] == encoded[120:]
|
|
|
|
|
|
def test_h150i_pro_xt_device_address_leds(h150iProXTDevice):
|
|
dev = h150iProXTDevice
|
|
colors = [[i + 3, i + 2, i + 1] for i in range(0, 16 * 3, 3)]
|
|
encoded = list(range(1, 16 * 3 + 1))
|
|
dev.set_color(channel='led', mode='super-fixed', colors=iter(colors))
|
|
|
|
assert len(dev.device.sent) == 4 # 3 for enable, 1 for the leds
|
|
assert dev.device.sent[0].data[1] & 0b111 == 0b001
|
|
assert dev.device.sent[1].data[1] & 0b111 == 0b010
|
|
assert dev.device.sent[2].data[1] & 0b111 == 0b011
|
|
assert dev.device.sent[3].data[1] & 0b111 == 0b100
|
|
assert dev.device.sent[3].data[2:50] == encoded[:48]
|
|
|
|
|
|
def test_h115i_platinum_device_synchronize(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
colors = [[3, 2, 1]]
|
|
encoded = [1, 2, 3] * 24
|
|
|
|
dev.set_color(channel='led', mode='fixed', colors=iter(colors))
|
|
|
|
assert len(dev.device.sent) == 5 # 3 for enable, 2 for off
|
|
|
|
assert dev.device.sent[0].data[1] & 0b111 == 0b001
|
|
assert dev.device.sent[1].data[1] & 0b111 == 0b010
|
|
assert dev.device.sent[2].data[1] & 0b111 == 0b011
|
|
assert dev.device.sent[3].data[1] & 0b111 == 0b100
|
|
|
|
assert dev.device.sent[3].data[2:62] == encoded[:60]
|
|
assert dev.device.sent[4].data[1] & 0b111 == 0b101
|
|
assert dev.device.sent[4].data[2:14] == encoded[60:]
|
|
|
|
|
|
def test_h115i_platinum_device_leds_off(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
dev.set_color(channel='led', mode='off', colors=iter([]))
|
|
assert len(dev.device.sent) == 5 # 3 for enable, 2 for off
|
|
|
|
for _, data in dev.device.sent[3:5]:
|
|
assert data[2:62] == [0] * 60
|
|
|
|
|
|
def test_h115i_platinum_device_invalid_color_modes(h115iPlatinumDevice):
|
|
dev = h115iPlatinumDevice
|
|
|
|
with pytest.raises(ValueError):
|
|
dev.set_color('led', 'invalid', [])
|
|
|
|
with pytest.raises(ValueError):
|
|
dev.set_color('invalid', 'off', [])
|
|
|
|
assert len(dev.device.sent) == 0
|
|
|
|
|
|
def test_h115i_platinum_device_short_enough_storage_path():
|
|
description = 'Mock H115i Platinum'
|
|
kwargs = {'fan_count': 2, 'fan_leds': 4}
|
|
device = _MockHydroPlatinumDevice()
|
|
dev = HydroPlatinum(device, description, **kwargs)
|
|
dev.connect()
|
|
|
|
assert len(dev._data._backend._write_dir) < _WIN_MAX_PATH
|
|
assert dev._data._backend._write_dir.endswith('3142')
|
|
|
|
|
|
def test_h115i_platinum_device_bad_stored_data(h115iPlatinumDevice):
|
|
h115iPlatinumDevice
|
|
# TODO
|
|
pass
|