1import pytest
2from _testutils import MockHidapiDevice, Report, MockRuntimeStorage
3
4from liquidctl.driver.commander_pro import _quoted, _prepare_profile, _fan_mode_desc, CommanderPro
5from liquidctl.error import NotSupportedByDevice
6
7
8# hardcoded responce data expected for some of the calls:
9# commander pro: firmware request (0.9.214)
10# commander pro: bootloader req (2.3)
11# commander pro: get temp config ( 3 sensors)
12# commander pro: get fan configs (3 DC fans, 1 PWM fan) # note I have not tested it with pwm fans
13# commander pro:
14
15
16@pytest.fixture
17def commanderProDeviceUnconnected():
18    device = MockHidapiDevice(vendor_id=0x1b1c, product_id=0x0c10, address='addr')
19    return CommanderPro(device, 'Corsair Commander Pro (experimental)', 6, 4, 2)
20
21
22@pytest.fixture
23def lightingNodeProDeviceUnconnected():
24    device = MockHidapiDevice(vendor_id=0x1b1c, product_id=0x0c0b, address='addr')
25    return CommanderPro(device, 'Corsair Lighting Node Pro (experimental)', 0, 0, 2)
26
27
28@pytest.fixture
29def commanderProDevice():
30    device = MockHidapiDevice(vendor_id=0x1b1c, product_id=0x0c10, address='addr')
31    pro = CommanderPro(device, 'Corsair Commander Pro (experimental)', 6, 4, 2)
32
33
34    runtime_storage = MockRuntimeStorage(key_prefixes=['testing'])
35    pro.connect(runtime_storage=runtime_storage)
36    return pro
37
38
39@pytest.fixture
40def lightingNodeProDevice():
41    device = MockHidapiDevice(vendor_id=0x1b1c, product_id=0x0c0b, address='addr')
42    node = CommanderPro(device, 'Corsair Lighting Node Pro (experimental)', 0, 0, 2)
43    runtime_storage = MockRuntimeStorage(key_prefixes=['testing'])
44    node.connect(runtime_storage=runtime_storage)
45    return node
46
47@pytest.fixture
48def lightingNodeCoreDevice():
49    device = MockHidapiDevice(vendor_id=0x1b1c, product_id=0x0c1a, address='addr')
50    node = CommanderPro(device, 'Corsair Lighting Node Core (experimental)', 0, 0, 1)
51    runtime_storage = MockRuntimeStorage(key_prefixes=['testing'])
52    node.connect(runtime_storage=runtime_storage)
53    return node
54
55
56
57# prepare profile
58def test_prepare_profile_valid_max_rpm():
59    assert _prepare_profile([[10, 400], [20, 5000]], 60) == [(10, 400), (20, 5000), (60, 5000), (60, 5000), (60, 5000), (60, 5000)]
60
61
62def test_prepare_profile_add_max_rpm():
63    assert _prepare_profile([[10, 400]], 60) == [(10, 400), (60, 5000), (60, 5000), (60, 5000), (60, 5000), (60, 5000)]
64    assert _prepare_profile([[10, 400], [20, 500], [30, 600], [40, 700], [50, 800]], 60) == [(10, 400), (20, 500), (30, 600), (40, 700), (50, 800), (60, 5000)]
65
66
67def test_prepare_profile_missing_max_rpm():
68    with pytest.raises(ValueError):
69        _prepare_profile([[10, 400], [20, 500], [30, 600], [40, 700], [50, 800], [55, 900]], 60)
70
71
72def test_prepare_profile_full_set():
73    assert _prepare_profile([[10, 400], [20, 500], [30, 600], [40, 700], [45, 2000], [50, 5000]], 60) == [(10, 400), (20, 500), (30, 600), (40, 700), (45, 2000), (50, 5000)]
74
75
76def test_prepare_profile_too_many_points():
77    with pytest.raises(ValueError):
78        _prepare_profile([[10, 400], [20, 500], [30, 600], [40, 700], [50, 800], [55, 900]], 60)
79
80
81def test_prepare_profile_no_points():
82    assert _prepare_profile([], 60) == [(60, 5000), (60, 5000), (60, 5000), (60, 5000), (60, 5000), (60, 5000)]
83
84
85def test_prepare_profile_empty_list():
86    assert _prepare_profile([], 60) == [(60, 5000), (60, 5000), (60, 5000), (60, 5000), (60, 5000), (60, 5000)]
87
88
89def test_prepare_profile_above_max_temp():
90    assert _prepare_profile([[10, 400], [70, 2000]], 60) == [(10, 400), (60, 5000), (60, 5000), (60, 5000), (60, 5000), (60, 5000)]
91
92
93def test_prepare_profile_temp_low():
94    assert _prepare_profile([[-10, 400], [70, 2000]], 60) == [(-10, 400), (60, 5000), (60, 5000), (60, 5000), (60, 5000), (60, 5000)]
95
96
97def test_prepare_profile_max_temp():
98    assert _prepare_profile([], 100) == [(100, 5000), (100, 5000), (100, 5000), (100, 5000), (100, 5000), (100, 5000)]
99
100
101# quoted
102def test_quoted_empty():
103    assert _quoted() == ''
104
105
106def test_quoted_single():
107    assert _quoted('one arg') == "'one arg'"
108
109
110def test_quoted_valid():
111    assert _quoted('one', 'two') == "'one', 'two'"
112
113
114def test_quoted_not_string():
115    assert _quoted('test', 500) == "'test', 500"
116
117
118# fan modes
119def test_get_fan_mode_description_auto():
120    assert _fan_mode_desc(0x00) == None
121
122
123def test_get_fan_mode_description_unknown():
124    assert _fan_mode_desc(0x03) == None
125    assert _fan_mode_desc(0x04) == None
126    assert _fan_mode_desc(0x10) == None
127    assert _fan_mode_desc(0xff) == None
128
129
130def test_get_fan_mode_description_dc():
131    assert _fan_mode_desc(0x01) == 'DC'
132
133
134def test_get_fan_mode_description_pwm():
135    assert _fan_mode_desc(0x02) == 'PWM'
136
137
138# class methods
139def test_commander_constructor(commanderProDeviceUnconnected):
140
141    assert commanderProDeviceUnconnected._data is None
142    assert commanderProDeviceUnconnected._fan_names == ['fan1', 'fan2', 'fan3', 'fan4', 'fan5', 'fan6']
143    assert commanderProDeviceUnconnected._led_names == ['led1', 'led2']
144    assert commanderProDeviceUnconnected._temp_probs == 4
145    assert commanderProDeviceUnconnected._fan_count == 6
146
147
148def test_lighting_constructor(lightingNodeProDeviceUnconnected):
149    assert lightingNodeProDeviceUnconnected._data is None
150    assert lightingNodeProDeviceUnconnected._fan_names == []
151    assert lightingNodeProDeviceUnconnected._led_names == ['led1', 'led2']
152    assert lightingNodeProDeviceUnconnected._temp_probs == 0
153    assert lightingNodeProDeviceUnconnected._fan_count == 0
154
155
156def test_connect_commander(commanderProDeviceUnconnected):
157    commanderProDeviceUnconnected.connect()
158    assert commanderProDeviceUnconnected._data is not None
159
160
161def test_connect_lighting(lightingNodeProDeviceUnconnected):
162    lightingNodeProDeviceUnconnected.connect()
163    assert lightingNodeProDeviceUnconnected._data is not None
164
165
166def test_initialize_commander_pro(commanderProDevice):
167
168    responses = [
169        '000009d4000000000000000000000000',  # firmware
170        '00000500000000000000000000000000',  # bootloader
171        '00010100010000000000000000000000',  # temp probes
172        '00010102000000000000000000000000'   # fan probes
173    ]
174    for d in responses:
175        commanderProDevice.device.preload_read(Report(0, bytes.fromhex(d)))
176
177    res = commanderProDevice.initialize()
178
179    assert len(res) == 12
180    assert res[0] == ('Firmware version', '0.9.212', '')
181    assert res[1] == ('Bootloader version', '0.5', '')
182
183    assert res[2] == ('Temperature probe 1', True, '')
184    assert res[3] == ('Temperature probe 2', True, '')
185    assert res[4] == ('Temperature probe 3', False, '')
186    assert res[5] == ('Temperature probe 4', True, '')
187
188    assert res[6] == ('Fan 1 control mode', 'DC', '')
189    assert res[7] == ('Fan 2 control mode', 'DC', '')
190    assert res[8] == ('Fan 3 control mode', 'PWM', '')
191    assert res[9] == ('Fan 4 control mode', None, '')
192    assert res[10] == ('Fan 5 control mode', None, '')
193    assert res[11] == ('Fan 6 control mode', None, '')
194
195    data = commanderProDevice._data.load('fan_modes', None)
196    assert data is not None
197    assert len(data) == 6
198    assert data[0] == 0x01
199    assert data[1] == 0x01
200    assert data[2] == 0x02
201    assert data[3] == 0x00
202    assert data[4] == 0x00
203    assert data[5] == 0x00
204
205    data = commanderProDevice._data.load('temp_sensors_connected', None)
206    assert data is not None
207    assert len(data) == 4
208    assert data[0] == 0x01
209    assert data[1] == 0x01
210    assert data[2] == 0x00
211    assert data[3] == 0x01
212
213
214def test_initialize_lighting_node(lightingNodeProDevice):
215    responses = [
216        '000009d4000000000000000000000000',  # firmware
217        '00000500000000000000000000000000'  # bootloader
218    ]
219    for d in responses:
220        lightingNodeProDevice.device.preload_read(Report(0, bytes.fromhex(d)))
221
222    res = lightingNodeProDevice.initialize()
223
224    assert len(res) == 2
225    assert res[0][1] == '0.9.212'
226    assert res[1][1] == '0.5'
227
228    data = lightingNodeProDevice._data.load('fan_modes', None)
229    assert data is None
230
231    data = lightingNodeProDevice._data.load('temp_sensors_connected', None)
232    assert data is None
233
234
235def test_get_status_commander_pro(commanderProDevice):
236
237    responses = [
238        '000a8300000000000000000000000000',  # temp sensor 1
239        '000b6a00000000000000000000000000',  # temp sensor 2
240        '000a0e00000000000000000000000000',  # temp sensor 4
241        '0003ac00000000000000000000000000',  # fan speed 1
242        '0003ab00000000000000000000000000',  # fan speed 2
243        '0003db00000000000000000000000000',  # fan speed 3
244        '002f2200000000000000000000000000',  # get 12v
245        '00136500000000000000000000000000',  # get 5v
246        '000d1f00000000000000000000000000',  # get 3.3v
247    ]
248    for d in responses:
249        commanderProDevice.device.preload_read(Report(0, bytes.fromhex(d)))
250
251    commanderProDevice._data.store('fan_modes', [0x01, 0x01, 0x02, 0x00, 0x00, 0x00])
252    commanderProDevice._data.store('temp_sensors_connected', [0x01, 0x01, 0x00, 0x01])
253
254    res = commanderProDevice.get_status()
255    print(res)
256
257    assert len(res) == 9
258
259    # temp probes
260    assert res[0] == ('Temperature 1', 26.91, '°C')
261    assert res[1] == ('Temperature 2', 29.22, '°C')
262    assert res[2] == ('Temperature 4', 25.74, '°C')
263
264    # fans rpm
265    assert res[3] == ('Fan 1 speed', 940, 'rpm')
266    assert res[4] == ('Fan 2 speed', 939, 'rpm')
267    assert res[5] == ('Fan 3 speed', 987, 'rpm')
268
269    # voltages
270    assert res[6] == ('+12V rail', 12.066, 'V')
271    assert res[7] == ('+5V rail', 4.965, 'V')
272    assert res[8] == ('+3.3V rail', 3.359, 'V')
273
274    # check the commands sent
275    sent = commanderProDevice.device.sent
276    assert len(sent) == 9
277
278    assert sent[0].data[0] == 0x11
279    assert sent[1].data[0] == 0x11
280    assert sent[2].data[0] == 0x11
281
282    assert sent[3].data[0] == 0x21
283    assert sent[4].data[0] == 0x21
284    assert sent[5].data[0] == 0x21
285
286    assert sent[6].data[0] == 0x12
287    assert sent[7].data[0] == 0x12
288    assert sent[8].data[0] == 0x12
289
290
291def test_get_status_lighting_pro(lightingNodeProDevice):
292
293    res = lightingNodeProDevice.get_status()
294    assert len(res) == 0
295
296
297def test_get_temp_valid_sensor_commander(commanderProDevice):
298
299    response = '000a8300000000000000000000000000'
300    commanderProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
301
302    commanderProDevice._data.store('temp_sensors_connected', [0x01, 0x01, 0x01, 0x01])
303
304    res = commanderProDevice._get_temp(1)
305
306    assert res == 26.91
307
308    # check the commands sent
309    sent = commanderProDevice.device.sent
310    assert len(sent) == 1
311    assert sent[0].data[0] == 0x11
312    assert sent[0].data[1] == 1
313
314
315def test_get_temp_invalid_sensor_low_commander(commanderProDevice):
316    response = '000a8300000000000000000000000000'
317    commanderProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
318
319    commanderProDevice._data.store('temp_sensors_connected', [0x01, 0x01, 0x01, 0x01])
320
321    with pytest.raises(ValueError):
322        commanderProDevice._get_temp(-1)
323
324    # check the commands sent
325    sent = commanderProDevice.device.sent
326    assert len(sent) == 0
327
328
329def test_get_temp_invalid_sensor_high_commander(commanderProDevice):
330    response = '000a8300000000000000000000000000'
331    commanderProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
332
333    commanderProDevice._data.store('temp_sensors_connected', [0x01, 0x01, 0x01, 0x01])
334
335    with pytest.raises(ValueError):
336        commanderProDevice._get_temp(4)
337
338    # check the commands sent
339    sent = commanderProDevice.device.sent
340    assert len(sent) == 0
341
342
343def test_get_temp_lighting(lightingNodeProDevice):
344    response = '000a8300000000000000000000000000'
345    lightingNodeProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
346
347    lightingNodeProDevice._data.store('temp_sensors_connected', [0x00, 0x00, 0x00, 0x00])
348
349    with pytest.raises(ValueError):
350        lightingNodeProDevice._get_temp(2)
351
352    # check the commands sent
353    sent = lightingNodeProDevice.device.sent
354    assert len(sent) == 0
355
356
357def test_get_fan_rpm_valid_commander(commanderProDevice):
358
359    response = '0003ac00000000000000000000000000'
360    commanderProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
361    commanderProDevice._data.store('fan_modes', [0x01, 0x01, 0x02, 0x00, 0x00, 0x00])
362
363    res = commanderProDevice._get_fan_rpm(1)
364    assert res == 940
365
366    # check the commands sent
367    sent = commanderProDevice.device.sent
368    assert len(sent) == 1
369    assert sent[0].data[0] == 0x21
370    assert sent[0].data[1] == 1
371
372
373def test_get_fan_rpm_invalid_low_commander(commanderProDevice):
374
375    response = '0003ac00000000000000000000000000'
376    commanderProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
377
378    commanderProDevice._data.store('fan_modes', [0x01, 0x01, 0x02, 0x00, 0x00, 0x00])
379
380    with pytest.raises(ValueError):
381        commanderProDevice._get_fan_rpm(-1)
382
383    # check the commands sent
384    sent = commanderProDevice.device.sent
385    assert len(sent) == 0
386
387
388def test_get_fan_rpm_invalid_high_commander(commanderProDevice):
389    response = '0003ac00000000000000000000000000'
390    commanderProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
391
392    commanderProDevice._data.store('fan_modes', [0x01, 0x01, 0x02, 0x00, 0x00, 0x00])
393
394    with pytest.raises(ValueError):
395        commanderProDevice._get_fan_rpm(7)
396
397    # check the commands sent
398    sent = commanderProDevice.device.sent
399    assert len(sent) == 0
400
401
402def test_get_fan_rpm_lighting(lightingNodeProDevice):
403    response = '0003ac00000000000000000000000000'
404    lightingNodeProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
405
406    with pytest.raises(ValueError):
407        lightingNodeProDevice._get_fan_rpm(7)
408
409    # check the commands sent
410    sent = lightingNodeProDevice.device.sent
411    assert len(sent) == 0
412
413
414def test_get_hw_fan_channels_all(commanderProDevice):
415
416    res = commanderProDevice._get_hw_fan_channels('sync')
417    assert res == [0, 1, 2, 3, 4, 5]
418
419
420def test_get_hw_fan_channels_lowercase(commanderProDevice):
421    res = commanderProDevice._get_hw_fan_channels('fan2')
422    assert res == [1]
423
424
425@pytest.mark.parametrize('channel', [
426    'fan23', 'fan7', 'fan0', 'fan', 'led', 'led1', 'bob'
427    ])
428def test_get_hw_fan_channels_invalid(commanderProDevice, channel):
429    with pytest.raises(ValueError):
430        commanderProDevice._get_hw_fan_channels(channel)
431
432
433@pytest.mark.parametrize('channel,expected', [
434    ('led1', [0]), ('led2', [1]), ('sync', [0, 1])
435    ])
436def test_get_hw_led_channel_valid(commanderProDevice, channel, expected):
437    res = commanderProDevice._get_hw_led_channels(channel)
438    assert res == expected
439
440
441@pytest.mark.parametrize('channel,expected', [('led', [0])])
442def test_get_hw_led_channel_valid_node_core(lightingNodeCoreDevice, channel,
443                                            expected):
444    res = lightingNodeCoreDevice._get_hw_led_channels(channel)
445    assert res == expected
446
447
448@pytest.mark.parametrize('channel', [
449    'led0', 'led3', 'led', 'fan', 'fan1', 'bob'
450    ])
451def test_get_hw_led_channels_invalid(commanderProDevice, channel):
452    with pytest.raises(ValueError):
453        commanderProDevice._get_hw_led_channels(channel)
454
455
456def test_set_fixed_speed_low(commanderProDevice):
457
458    response = '00000000000000000000000000000000'
459    commanderProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
460    commanderProDevice._data.store('fan_modes', [0x01, 0x01, 0x01, 0x01, 0x01, 0x01])
461
462    commanderProDevice.set_fixed_speed('fan4', -10)
463
464    # check the commands sent
465    sent = commanderProDevice.device.sent
466    assert len(sent) == 1
467
468    assert sent[0].data[0] == 0x23
469    assert sent[0].data[1] == 0x03
470    assert sent[0].data[2] == 0x00
471
472
473def test_set_fixed_speed_high(commanderProDevice):
474    response = '00000000000000000000000000000000'
475    commanderProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
476    commanderProDevice._data.store('fan_modes', [0x01, 0x01, 0x01, 0x01, 0x01, 0x01])
477
478    commanderProDevice.set_fixed_speed('fan3', 110)
479
480    # check the commands sent
481    sent = commanderProDevice.device.sent
482    assert len(sent) == 1
483
484    assert sent[0].data[0] == 0x23
485    assert sent[0].data[1] == 0x02
486    assert sent[0].data[2] == 0x64
487
488
489def test_set_fixed_speed_valid(commanderProDevice):
490
491    response = '00000000000000000000000000000000'
492    commanderProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
493    commanderProDevice._data.store('fan_modes', [0x01, 0x01, 0x01, 0x01, 0x01, 0x01])
494
495    commanderProDevice.set_fixed_speed('fan2', 50)
496
497    # check the commands sent
498    sent = commanderProDevice.device.sent
499    assert len(sent) == 1
500
501    assert sent[0].data[0] == 0x23
502    assert sent[0].data[1] == 0x01
503    assert sent[0].data[2] == 0x32
504
505
506def test_set_fixed_speed_valid_unconfigured(commanderProDevice):
507
508    response = '00000000000000000000000000000000'
509    commanderProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
510    commanderProDevice._data.store('fan_modes', [0x00, 0x00, 0x00, 0x00, 0x00, 0x00])
511
512    commanderProDevice.set_fixed_speed('fan2', 50)
513
514    # check the commands sent
515    sent = commanderProDevice.device.sent
516    assert len(sent) == 0
517
518
519def test_set_fixed_speed_valid_multi_fan(commanderProDevice):
520    responses = [
521        '00000000000000000000000000000000',
522        '00000000000000000000000000000000',
523        '00000000000000000000000000000000',
524        '00000000000000000000000000000000',
525        '00000000000000000000000000000000'
526    ]
527
528    for d in responses:
529        commanderProDevice.device.preload_read(Report(0, bytes.fromhex(d)))
530
531    commanderProDevice._data.store('fan_modes', [0x01, 0x00, 0x01, 0x01, 0x00, 0x00])
532
533    commanderProDevice.set_fixed_speed('sync', 50)
534
535    # check the commands sent
536    sent = commanderProDevice.device.sent
537    assert len(sent) == 3
538
539    assert sent[0].data[0] == 0x23
540    assert sent[0].data[1] == 0x00
541    assert sent[0].data[2] == 0x32
542
543    assert sent[1].data[0] == 0x23
544    assert sent[1].data[1] == 0x02
545    assert sent[1].data[2] == 0x32
546
547    assert sent[2].data[0] == 0x23
548    assert sent[2].data[1] == 0x03
549    assert sent[2].data[2] == 0x32
550
551
552def test_set_fixed_speed_lighting(lightingNodeProDevice):
553    response = '00000000000000000000000000000000'
554    lightingNodeProDevice.device.preload_read(Report(0, bytes.fromhex(response)))
555
556    with pytest.raises(NotSupportedByDevice):
557        lightingNodeProDevice.set_fixed_speed('sync', 50)
558
559    # check the commands sent
560    sent = lightingNodeProDevice.device.sent
561    assert len(sent) == 0
562
563
564def test_set_speed_profile_valid_multi_fan(commanderProDevice):
565    ignore = Report(0, bytes(16))
566    for _ in range(5):
567        commanderProDevice.device.preload_read(ignore)
568
569    commanderProDevice._data.store('temp_sensors_connected', [0x01, 0x01, 0x00, 0x01])
570    commanderProDevice._data.store('fan_modes', [0x01, 0x00, 0x01, 0x01, 0x00, 0x00])
571    commanderProDevice.set_speed_profile('sync', [(10, 500), (20, 1000)])
572
573    # check the commands sent
574    sent = commanderProDevice.device.sent
575    assert len(sent) == 3
576
577    assert sent[0].data[0] == 0x25
578    assert sent[0].data[1] == 0x00
579    assert sent[0].data[2] == 0x00
580
581    assert sent[0].data[3] == 0x03
582    assert sent[0].data[4] == 0xe8
583    assert sent[0].data[15] == 0x01
584    assert sent[0].data[16] == 0xf4
585
586    assert sent[1].data[0] == 0x25
587    assert sent[1].data[1] == 0x02
588    assert sent[1].data[2] == 0x00
589
590    assert sent[2].data[0] == 0x25
591    assert sent[2].data[1] == 0x03
592    assert sent[2].data[2] == 0x00
593
594
595def test_set_speed_profile_invalid_temp_sensor(commanderProDevice):
596    ignore = Report(0, bytes(16))
597    for _ in range(5):
598        commanderProDevice.device.preload_read(ignore)
599
600    commanderProDevice._data.store('temp_sensors_connected', [0x01, 0x01, 0x00, 0x01])
601    commanderProDevice._data.store('fan_modes', [0x01, 0x00, 0x01, 0x01, 0x00, 0x00])
602
603    commanderProDevice.set_speed_profile('fan1', [(10, 500), (20, 1000)], temperature_sensor=10)
604
605    # check the commands sent
606    sent = commanderProDevice.device.sent
607    assert len(sent) == 1
608
609    assert sent[0].data[0] == 0x25
610    assert sent[0].data[1] == 0x00
611    assert sent[0].data[2] == 0x03
612
613    assert sent[0].data[3] == 0x03
614    assert sent[0].data[4] == 0xe8
615    assert sent[0].data[15] == 0x01
616    assert sent[0].data[16] == 0xf4
617
618
619def test_set_speed_profile_no_temp_sensors(commanderProDevice):
620    ignore = Report(0, bytes(16))
621    for _ in range(5):
622        commanderProDevice.device.preload_read(ignore)
623
624    commanderProDevice._data.store('temp_sensors_connected', [0x00, 0x00, 0x00, 0x00])
625    commanderProDevice._data.store('fan_modes', [0x01, 0x00, 0x01, 0x01, 0x00, 0x00])
626
627    with pytest.raises(ValueError):
628        commanderProDevice.set_speed_profile('sync', [(10, 500), (20, 1000)], temperature_sensor=1)
629
630    # check the commands sent
631    sent = commanderProDevice.device.sent
632    assert len(sent) == 0
633
634
635def test_set_speed_profile_valid(commanderProDevice):
636    ignore = Report(0, bytes(16))
637    for _ in range(5):
638        commanderProDevice.device.preload_read(ignore)
639
640    commanderProDevice._data.store('temp_sensors_connected', [0x01, 0x01, 0x00, 0x01])
641    commanderProDevice._data.store('fan_modes', [0x01, 0x00, 0x01, 0x01, 0x00, 0x00])
642    commanderProDevice.set_speed_profile('fan3', [(10, 500), (20, 1000)])
643
644    # check the commands sent
645    sent = commanderProDevice.device.sent
646    assert len(sent) == 1
647
648    assert sent[0].data[0] == 0x25
649    assert sent[0].data[1] == 0x02
650    assert sent[0].data[2] == 0x00
651
652    assert sent[0].data[3] == 0x03
653    assert sent[0].data[4] == 0xe8
654    assert sent[0].data[15] == 0x01
655    assert sent[0].data[16] == 0xf4
656
657
658def test_set_speed_profile_node(lightingNodeProDevice):
659    ignore = Report(0, bytes(16))
660    for _ in range(5):
661        lightingNodeProDevice.device.preload_read(ignore)
662
663    lightingNodeProDevice._data.store('temp_sensors_connected', [0x01, 0x00, 0x00, 0x00])
664    lightingNodeProDevice._data.store('fan_modes', [0x01, 0x00, 0x01, 0x01, 0x00, 0x00])
665
666    with pytest.raises(NotSupportedByDevice):
667        lightingNodeProDevice.set_speed_profile('sync', [(10, 500), (20, 1000)])
668
669    # check the commands sent
670    sent = lightingNodeProDevice.device.sent
671    assert len(sent) == 0
672
673
674def test_set_speed_profile_core(lightingNodeCoreDevice):
675    ignore = Report(0, bytes(16))
676    for _ in range(5):
677        lightingNodeCoreDevice.device.preload_read(ignore)
678
679    lightingNodeCoreDevice._data.store('temp_sensors_connected', [0x01, 0x00, 0x00, 0x00])
680    lightingNodeCoreDevice._data.store('fan_modes', [0x01, 0x00, 0x01, 0x01, 0x00, 0x00])
681
682    with pytest.raises(NotSupportedByDevice):
683        lightingNodeCoreDevice.set_speed_profile('sync', [(10, 500), (20, 1000)])
684
685    # check the commands sent
686    sent = lightingNodeCoreDevice.device.sent
687    assert len(sent) == 0
688
689
690def test_set_speed_profile_valid_unconfigured(commanderProDevice):
691    ignore = Report(0, bytes(16))
692    for _ in range(5):
693        commanderProDevice.device.preload_read(ignore)
694
695    commanderProDevice._data.store('temp_sensors_connected', [0x00, 0x00, 0x00, 0x00])
696    commanderProDevice._data.store('fan_modes', [0x01, 0x00, 0x01, 0x01, 0x00, 0x00])
697
698    with pytest.raises(ValueError):
699        commanderProDevice.set_speed_profile('fan2', [(10, 500), (20, 1000)])
700
701    # check the commands sent
702    sent = commanderProDevice.device.sent
703    assert len(sent) == 0
704
705
706def test_set_color_hardware_clear(commanderProDevice):
707    ignore = Report(0, bytes(16))
708    for _ in range(6):
709        commanderProDevice.device.preload_read(ignore)
710
711    effect = {
712            'channel': 0x01,
713            'start_led': 0x00,
714            'num_leds': 0x0f,
715            'mode': 0x0a,
716            'speed': 0x00,
717            'direction': 0x00,
718            'random_colors': 0x00,
719            'colors': []
720        }
721    commanderProDevice._data.store('saved_effects', [effect])
722
723    commanderProDevice.set_color('led1', 'clear', [], )
724
725    # check the commands sent
726    sent = commanderProDevice.device.sent
727    assert len(sent) == 0
728
729    effects = commanderProDevice._data.load('saved_effects', default=None)
730
731    assert effects is None
732
733
734def test_set_color_hardware_off(commanderProDevice):
735    ignore = Report(0, bytes(16))
736    for _ in range(6):
737        commanderProDevice.device.preload_read(ignore)
738
739    effect = {
740            'channel': 0x01,
741            'start_led': 0x00,
742            'num_leds': 0x0f,
743            'mode': 0x0a,
744            'speed': 0x00,
745            'direction': 0x00,
746            'random_colors': 0x00,
747            'colors': []
748        }
749    commanderProDevice._data.store('saved_effects', [effect])
750
751    commanderProDevice.set_color('led1', 'off', [])
752
753    # check the commands sent
754    sent = commanderProDevice.device.sent
755    assert len(sent) == 5
756
757    assert sent[3].data[0] == 0x35
758    assert sent[3].data[4] == 0x04
759
760    effects = commanderProDevice._data.load('saved_effects', default=None)
761
762    assert effects is None
763
764
765@pytest.mark.parametrize('directionStr,expected',
766                         [('forward', 0x01), ('backward', 0x00)])
767def test_set_color_hardware_direction(commanderProDevice, directionStr, expected):
768    ignore = Report(0, bytes(16))
769    for _ in range(6):
770        commanderProDevice.device.preload_read(ignore)
771
772    colors = [[0xaa, 0xbb, 0xcc]]
773    commanderProDevice.set_color('led1', 'fixed', colors, direction=directionStr)
774
775    # check the commands sent
776    sent = commanderProDevice.device.sent
777    assert len(sent) == 5
778
779    assert sent[3].data[0] == 0x35
780    assert sent[3].data[6] == expected
781
782    effects = commanderProDevice._data.load('saved_effects', default=None)
783
784    assert effects is not None
785    assert len(effects) == 1
786
787
788def test_set_color_hardware_direction_default(commanderProDevice):
789    ignore = Report(0, bytes(16))
790    for _ in range(6):
791        commanderProDevice.device.preload_read(ignore)
792
793    colors = [[0xaa, 0xbb, 0xcc]]
794    commanderProDevice.set_color('led1', 'fixed', colors)
795
796    # check the commands sent
797    sent = commanderProDevice.device.sent
798    assert len(sent) == 5
799
800    assert sent[3].data[0] == 0x35
801    assert sent[3].data[6] == 0x01
802
803    effects = commanderProDevice._data.load('saved_effects', default=None)
804
805    assert effects is not None
806    assert len(effects) == 1
807
808
809def test_set_color_hardware_speed_default(commanderProDevice):
810    ignore = Report(0, bytes(16))
811    for _ in range(6):
812        commanderProDevice.device.preload_read(ignore)
813
814    colors = [[0xaa, 0xbb, 0xcc]]
815    commanderProDevice.set_color('led1', 'fixed', colors)
816
817    # check the commands sent
818    sent = commanderProDevice.device.sent
819    assert len(sent) == 5
820
821    assert sent[3].data[0] == 0x35
822    assert sent[3].data[5] == 0x01
823
824    effects = commanderProDevice._data.load('saved_effects', default=None)
825
826    assert effects is not None
827    assert len(effects) == 1
828
829
830@pytest.mark.parametrize('speedStr,expected',
831                         [('slow', 0x02), ('fast', 0x00), ('medium', 0x01)])
832def test_set_color_hardware_speed(commanderProDevice, speedStr, expected):
833    ignore = Report(0, bytes(16))
834    for _ in range(6):
835        commanderProDevice.device.preload_read(ignore)
836
837    colors = [[0xaa, 0xbb, 0xcc]]
838    commanderProDevice.set_color('led1', 'fixed', colors, speed=speedStr)
839
840    # check the commands sent
841    sent = commanderProDevice.device.sent
842    assert len(sent) == 5
843
844    assert sent[3].data[0] == 0x35
845    assert sent[3].data[5] == expected
846
847    effects = commanderProDevice._data.load('saved_effects', default=None)
848
849    assert effects is not None
850    assert len(effects) == 1
851
852
853def test_set_color_hardware_default_start_end(commanderProDevice):
854    ignore = Report(0, bytes(16))
855    for _ in range(6):
856        commanderProDevice.device.preload_read(ignore)
857
858    colors = [[0xaa, 0xbb, 0xcc]]
859    commanderProDevice.set_color('led1', 'fixed', colors)
860
861    # check the commands sent
862    sent = commanderProDevice.device.sent
863    assert len(sent) == 5
864
865    assert sent[3].data[0] == 0x35
866    assert sent[3].data[2] == 0  # start led
867    assert sent[3].data[3] == 204  # num leds
868
869    effects = commanderProDevice._data.load('saved_effects', default=None)
870
871    assert effects is not None
872    assert len(effects) == 1
873
874
875@pytest.mark.parametrize('startLED,expected', [
876    (1, 0x00), (30, 0x1d), (92, 0x5b)
877    ])
878def test_set_color_hardware_start_set(commanderProDevice, startLED, expected):
879    ignore = Report(0, bytes(16))
880    for _ in range(6):
881        commanderProDevice.device.preload_read(ignore)
882
883    colors = [[0xaa, 0xbb, 0xcc]]
884    commanderProDevice.set_color('led1', 'fixed', colors, start_led=startLED)
885
886    # check the commands sent
887    sent = commanderProDevice.device.sent
888    assert len(sent) == 5
889
890    assert sent[3].data[0] == 0x35
891    assert sent[3].data[2] == expected  # start led
892
893    effects = commanderProDevice._data.load('saved_effects', default=None)
894
895    assert effects is not None
896    assert len(effects) == 1
897
898
899@pytest.mark.parametrize('numLED,expected', [
900    (1, 1), (30, 30), (96, 96), (203, 203), (204, 204), (205, 204)    ])
901def test_set_color_hardware_num_leds(commanderProDevice, numLED, expected):
902    ignore = Report(0, bytes(16))
903    for _ in range(6):
904        commanderProDevice.device.preload_read(ignore)
905
906    colors = [[0xaa, 0xbb, 0xcc]]
907    commanderProDevice.set_color('led1', 'fixed', colors, start_led=1, maximum_leds=numLED)
908
909    # check the commands sent
910    sent = commanderProDevice.device.sent
911    assert len(sent) == 5
912
913    assert sent[3].data[0] == 0x35
914    assert sent[3].data[3] == expected  # num leds
915
916    effects = commanderProDevice._data.load('saved_effects', default=None)
917
918    assert effects is not None
919    assert len(effects) == 1
920
921def test_set_color_hardware_too_many_leds(commanderProDevice):
922    ignore = Report(0, bytes(16))
923    for _ in range(6):
924        commanderProDevice.device.preload_read(ignore)
925
926    colors = [[0xaa, 0xbb, 0xcc]]
927    commanderProDevice.set_color('led1', 'fixed', colors, start_led=200, maximum_leds=50)
928
929    # check the commands sent
930    sent = commanderProDevice.device.sent
931    assert len(sent) == 5
932
933    assert sent[3].data[0] == 0x35
934    assert sent[3].data[2] == 0xc7  # start led
935    assert sent[3].data[3] == 5  # num led
936
937    effects = commanderProDevice._data.load('saved_effects', default=None)
938
939    assert effects is not None
940    assert len(effects) == 1
941
942
943def test_set_color_hardware_too_few_leds(commanderProDevice):
944    ignore = Report(0, bytes(16))
945    for _ in range(6):
946        commanderProDevice.device.preload_read(ignore)
947
948    colors = [[0xaa, 0xbb, 0xcc]]
949    commanderProDevice.set_color('led1', 'fixed', colors, start_led=1, maximum_leds=0)
950
951    # check the commands sent
952    sent = commanderProDevice.device.sent
953    assert len(sent) == 5
954
955    assert sent[3].data[0] == 0x35
956    assert sent[3].data[2] == 0x00  # start led
957    assert sent[3].data[3] == 0x01  # num led
958
959    effects = commanderProDevice._data.load('saved_effects', default=None)
960
961    assert effects is not None
962    assert len(effects) == 1
963
964
965@pytest.mark.parametrize('channel,expected',
966                         [('led1', 0x00), ('led2', 0x01)])
967def test_set_color_hardware_channel(commanderProDevice, channel, expected):
968    ignore = Report(0, bytes(16))
969    for _ in range(6):
970        commanderProDevice.device.preload_read(ignore)
971
972    colors = [[0xaa, 0xbb, 0xcc]]
973    commanderProDevice.set_color(channel, 'fixed', colors)
974
975    # check the commands sent
976    sent = commanderProDevice.device.sent
977    assert len(sent) == 5
978
979    assert sent[0].data[1] == expected
980    assert sent[1].data[1] == expected
981    assert sent[2].data[1] == expected
982    assert sent[3].data[0] == 0x35
983    assert sent[3].data[1] == expected
984
985    effects = commanderProDevice._data.load('saved_effects', default=None)
986
987    assert effects is not None
988    assert len(effects) == 1
989
990
991def test_set_color_hardware_sync_channel(commanderProDevice):
992    ignore = Report(0, bytes(16))
993    for _ in range(9):
994        commanderProDevice.device.preload_read(ignore)
995
996    colors = [[0xaa, 0xbb, 0xcc]]
997    commanderProDevice.set_color('sync', 'fixed', colors)
998
999    # check the commands sent
1000    sent = commanderProDevice.device.sent
1001    assert len(sent) == 2*(3 + 1) + 1
1002
1003    assert sent[0].data[1] == 0
1004    assert sent[1].data[1] == 0
1005    assert sent[2].data[1] == 0
1006
1007    assert sent[3].data[1] == 1
1008    assert sent[4].data[1] == 1
1009    assert sent[5].data[1] == 1
1010
1011    assert sent[6].data[0] == 0x35
1012    assert sent[6].data[1] == 0
1013
1014    assert sent[7].data[0] == 0x35
1015    assert sent[7].data[1] == 1
1016
1017    effects = commanderProDevice._data.load('saved_effects', default=None)
1018
1019    assert effects is not None
1020    assert len(effects) == 2
1021
1022
1023def test_set_color_hardware_random_color(commanderProDevice):
1024    ignore = Report(0, bytes(16))
1025    for _ in range(6):
1026        commanderProDevice.device.preload_read(ignore)
1027
1028    colors = []
1029    commanderProDevice.set_color('led1', 'fixed', colors)
1030
1031    # check the commands sent
1032    sent = commanderProDevice.device.sent
1033    assert len(sent) == 5
1034
1035    assert sent[3].data[0] == 0x35
1036    assert sent[3].data[7] == 0x01
1037
1038    effects = commanderProDevice._data.load('saved_effects', default=None)
1039
1040    assert effects is not None
1041    assert len(effects) == 1
1042
1043
1044def test_set_color_hardware_not_random_color(commanderProDevice):
1045    ignore = Report(0, bytes(16))
1046    for _ in range(6):
1047        commanderProDevice.device.preload_read(ignore)
1048
1049    colors = [[0xaa, 0xbb, 0xcc]]
1050    commanderProDevice.set_color('led1', 'fixed', colors)
1051
1052    # check the commands sent
1053    sent = commanderProDevice.device.sent
1054    assert len(sent) == 5
1055
1056    assert sent[3].data[0] == 0x35
1057    assert sent[3].data[7] == 0x00
1058
1059    effects = commanderProDevice._data.load('saved_effects', default=None)
1060
1061    assert effects is not None
1062    assert len(effects) == 1
1063
1064
1065def test_set_color_hardware_too_many_colors(commanderProDevice):
1066    ignore = Report(0, bytes(16))
1067    for _ in range(6):
1068        commanderProDevice.device.preload_read(ignore)
1069
1070    colors = [[0xaa, 0xbb, 0xcc], [0x00, 0x11, 0x22], [0x33, 0x44, 0x55]]
1071    commanderProDevice.set_color('led1', 'fixed', colors)
1072
1073    # check the commands sent
1074    sent = commanderProDevice.device.sent
1075    assert len(sent) == 5
1076
1077    assert sent[3].data[0] == 0x35
1078    assert sent[3].data[7] == 0x00
1079
1080    assert sent[3].data[9] == 0xaa
1081    assert sent[3].data[10] == 0xbb
1082    assert sent[3].data[11] == 0xcc
1083
1084    effects = commanderProDevice._data.load('saved_effects', default=None)
1085
1086    assert effects is not None
1087    assert len(effects) == 1
1088
1089
1090def test_set_color_hardware_too_few_colors(commanderProDevice):
1091    ignore = Report(0, bytes(16))
1092    for _ in range(6):
1093        commanderProDevice.device.preload_read(ignore)
1094
1095    commanderProDevice.set_color('led1', 'fixed', [])
1096
1097    # check the commands sent
1098    sent = commanderProDevice.device.sent
1099    assert len(sent) == 5
1100
1101    assert sent[3].data[0] == 0x35
1102    assert sent[3].data[7] == 0x01
1103
1104    assert sent[3].data[9] == 0x00
1105    assert sent[3].data[10] == 0x00
1106    assert sent[3].data[11] == 0x00
1107
1108    effects = commanderProDevice._data.load('saved_effects', default=None)
1109
1110    assert effects is not None
1111    assert len(effects) == 1
1112
1113
1114@pytest.mark.parametrize('modeStr,expected', [
1115    ('rainbow', 0x00), ('color_shift', 0x01), ('color_pulse', 0x02),
1116    ('color_wave', 0x03), ('fixed', 0x04), ('visor', 0x06), ('marquee', 0x07),
1117    ('blink', 0x08), ('sequential', 0x09), ('rainbow2', 0x0a),
1118    ])
1119def test_set_color_hardware_valid_mode(commanderProDevice, modeStr, expected):
1120    ignore = Report(0, bytes(16))
1121    for _ in range(6):
1122        commanderProDevice.device.preload_read(ignore)
1123
1124    commanderProDevice.set_color('led1', modeStr, [])
1125
1126    # check the commands sent
1127    sent = commanderProDevice.device.sent
1128    assert len(sent) == 5
1129
1130    assert sent[3].data[0] == 0x35
1131    assert sent[3].data[4] == expected
1132
1133    effects = commanderProDevice._data.load('saved_effects', default=None)
1134
1135    assert effects is not None
1136    assert len(effects) == 1
1137
1138
1139def test_set_color_hardware_invalid_mode(commanderProDevice):
1140    ignore = Report(0, bytes(16))
1141    for _ in range(6):
1142        commanderProDevice.device.preload_read(ignore)
1143
1144    colors = [[0xaa, 0xbb, 0xcc]]
1145
1146    with pytest.raises(ValueError):
1147        commanderProDevice.set_color('led1', 'invalid', colors)
1148
1149    # check the commands sent
1150    sent = commanderProDevice.device.sent
1151    assert len(sent) == 0
1152
1153    effects = commanderProDevice._data.load('saved_effects', default=None)
1154    assert effects is None
1155
1156
1157def test_set_color_hardware_multipe_commands(commanderProDevice):
1158    ignore = Report(0, bytes(16))
1159    for _ in range(6):
1160        commanderProDevice.device.preload_read(ignore)
1161
1162    effect = {
1163            'channel': 0x01,
1164            'start_led': 0x00,
1165            'num_leds': 0x0f,
1166            'mode': 0x0a,
1167            'speed': 0x00,
1168            'direction': 0x00,
1169            'random_colors': 0x00,
1170            'colors': [0xaa, 0xbb, 0xcc]
1171        }
1172    commanderProDevice._data.store('saved_effects', [effect])
1173
1174    commanderProDevice.set_color('led1', 'fixed', [[0x00, 0x11, 0x22]], start_led=16, maximum_leds=5)
1175
1176    # check the commands sent
1177    sent = commanderProDevice.device.sent
1178    assert len(sent) == 6
1179
1180    assert sent[3].data[0] == 0x35
1181    assert sent[4].data[0] == 0x35
1182
1183    effects = commanderProDevice._data.load('saved_effects', default=None)
1184
1185    assert effects is not None
1186    assert len(effects) == 2
1187
1188
1189def test_set_color_hardware_last_commands(commanderProDevice):
1190
1191    for i in range(15):
1192        commanderProDevice.device.preload_read(Report(0, bytes.fromhex('00000000000000000000000000000000')))
1193
1194    effect = {
1195            'channel': 0x01,
1196            'start_led': 0x00,
1197            'num_leds': 0x0f,
1198            'mode': 0x0a,
1199            'speed': 0x00,
1200            'direction': 0x00,
1201            'random_colors': 0x00,
1202            'colors': [0xaa, 0xbb, 0xcc]
1203        }
1204    commanderProDevice._data.store('saved_effects', [effect, effect, effect, effect, effect, effect, effect])
1205
1206    commanderProDevice.set_color('led1', 'fixed', [[0x00, 0x11, 0x22]], start_led=16, maximum_leds=5)
1207
1208    # check the commands sent
1209    sent = commanderProDevice.device.sent
1210    assert len(sent) == 12
1211
1212    effects = commanderProDevice._data.load('saved_effects', default=None)
1213
1214    assert effects is not None
1215    assert len(effects) == 8
1216
1217
1218
1219def test_set_color_hardware_max_commands(commanderProDevice):
1220
1221    for i in range(15):
1222        commanderProDevice.device.preload_read(Report(0, bytes.fromhex('00000000000000000000000000000000')))
1223
1224    effect = {
1225            'channel': 0x01,
1226            'start_led': 0x00,
1227            'num_leds': 0x0f,
1228            'mode': 0x0a,
1229            'speed': 0x00,
1230            'direction': 0x00,
1231            'random_colors': 0x00,
1232            'colors': [0xaa, 0xbb, 0xcc]
1233        }
1234    commanderProDevice._data.store('saved_effects', [effect, effect, effect, effect, effect, effect, effect, effect])
1235
1236    commanderProDevice.set_color('led1', 'fixed', [[0x00, 0x11, 0x22]], start_led=16, maximum_leds=5)
1237
1238    # check the commands sent
1239    sent = commanderProDevice.device.sent
1240    assert len(sent) == 0
1241
1242    effects = commanderProDevice._data.load('saved_effects', default=None)
1243
1244    assert effects is not None
1245    assert len(effects) == 8
1246
1247
1248def test_set_color_hardware_too_many_commands(commanderProDevice):
1249    ignore = Report(0, bytes(16))
1250    for _ in range(6):
1251        commanderProDevice.device.preload_read(ignore)
1252
1253    effect = {
1254            'channel': 0x01,
1255            'start_led': 0x00,
1256            'num_leds': 0x0f,
1257            'mode': 0x0a,
1258            'speed': 0x00,
1259            'direction': 0x00,
1260            'random_colors': 0x00,
1261            'colors': [0xaa, 0xbb, 0xcc]
1262        }
1263    commanderProDevice._data.store('saved_effects', [effect, effect, effect, effect, effect, effect, effect, effect, effect])
1264
1265    commanderProDevice.set_color('led1', 'fixed', [[0x00, 0x11, 0x22]], start_led=16, maximum_leds=5)
1266
1267    # check the commands sent
1268    sent = commanderProDevice.device.sent
1269    assert len(sent) == 0
1270
1271
1272def test_send_command_valid_data(commanderProDevice):
1273    ignore = Report(0, bytes(16))
1274    for _ in range(2):
1275        commanderProDevice.device.preload_read(ignore)
1276
1277    commanderProDevice._send_command(6, [255, 0, 20, 10, 15])
1278
1279    # check the commands sent
1280    sent = commanderProDevice.device.sent
1281    assert len(sent) == 1
1282    assert len(sent[0].data) == 64
1283    assert sent[0].data[0] == 6
1284    assert sent[0].data[1] == 255
1285
1286
1287def test_send_command_no_data(commanderProDevice):
1288    ignore = Report(0, bytes(16))
1289    for _ in range(2):
1290        commanderProDevice.device.preload_read(ignore)
1291
1292    commanderProDevice._send_command(6)
1293
1294    # check the commands sent
1295    sent = commanderProDevice.device.sent
1296    assert len(sent) == 1
1297    assert len(sent[0].data) == 64
1298    assert sent[0].data[0] == 6
1299    assert sent[0].data[1] == 0
1300
1301
1302def test_send_command_data_too_long(commanderProDevice):
1303    ignore = Report(0, bytes(16))
1304    for _ in range(2):
1305        commanderProDevice.device.preload_read(ignore)
1306
1307    data = bytearray(100)
1308    commanderProDevice._send_command(3, data)
1309
1310    # check the commands sent
1311    sent = commanderProDevice.device.sent
1312    assert len(sent) == 1
1313    assert len(sent[0].data) == 64
1314    assert sent[0].data[0] == 3
1315    assert sent[0].data[1] == 0
1316