aura_led: add driver for Asus Aura LED USB controllers (#456)

This commit is contained in:
CaseySJ 2022-05-27 16:58:14 -07:00 committed by GitHub
parent 71aca8703c
commit 622d83c428
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 647 additions and 5 deletions

1
.gitignore vendored
View File

@ -5,3 +5,4 @@
/dist/
__pycache__/
liquidctl/_version.py
.DS_Store

View File

@ -78,7 +78,7 @@ Corsair Vengeance RGB DIMM4
1. [Automation and running at boot]
1. [Set up Linux using systemd]
1. [Set up Windows using Task Scheduler]
1. [Set up macOS using launchd]
1. [Set up macOS using various methods]
1. [Troubleshooting]
1. [Additional documentation]
1. [License]
@ -139,7 +139,8 @@ The notes are sorted alphabetically, major (upper case) notes before minor
| Graphics card RGB | [ASUS Strix RTX 2080 Ti OC](docs/nvidia-guide.md) | I²C | <sup>_Ux_</sup> |
| Graphics card RGB | [Additional ASUS GTX and RTX cards](docs/nvidia-guide.md) | I²C | <sup>_Uex_</sup> |
| Graphics card RGB | [EVGA GTX 1080 FTW](docs/nvidia-guide.md) | I²C | <sup>_Ux_</sup> |
| Graphics card RGB | [Additional EVGA GTX 1070 and 1070 Ti cards](docs/nvidia-guide.md) | I²C | <sup>_Uex_</sup> |
| Graphics card RGB | [Additional EVGA GTX 1070 and 1070 Ti cards](docs/nvidia-guide.md) | I²C | <sup>_Uenx_</sup> |
| Motherboard | [Asus Aura LED motherboards](docs/asus-aura-led-guide.md) | USB HID | <sup>_e_</sup> |
| Motherboard | [Gigabyte RGB Fusion 2.0 motherboards](docs/gigabyte-rgb-fusion2-guide.md) | USB HID | |
| Power supply | [Corsair HX750i, HX850i, HX1000i, HX1200i](docs/corsair-hxi-rmi-psu-guide.md) | USB HID | <sup>_h_</sup> |
| Power supply | [Corsair RM650i, RM750i, RM850i, RM1000i](docs/corsair-hxi-rmi-psu-guide.md) | USB HID | <sup>_h_</sup> |
@ -705,10 +706,13 @@ A slightly more complex example can be seen in [issue #14](https://github.com/li
As an alternative to using Task Scheduler, the batch file can simply be placed in the startup folder; you can run `shell:startup` to [find out where that is](https://support.microsoft.com/en-us/help/4026268/windows-10-change-startup-apps).
### Set up macOS using launchd
[Set up macOS using launchd]: #set-up-macos-using-launchd
### Set up macOS using various methods
[Set up macOS using various methods]: #set-up-macos-using-various-methods
You can use a shell script and launchd to automatically configure your devices during login or after waking from sleep. A [detailed guide](https://www.tonymacx86.com/threads/gigabyte-z490-vision-d-thunderbolt-3-i5-10400-amd-rx-580.298642/page-24#post-2138475) is available on tonymacx86.
You can follow either or both of the guides below to automatically configure your devices during login or after waking from sleep. The guides are hosted on tonymacx86:
- [This guide](https://www.tonymacx86.com/threads/gigabyte-z490-vision-d-thunderbolt-3-i5-10400-amd-rx-580.298642/post-2138475) is for controllers that lose their state during sleep (e.g. Gigabyte RGB Fusion 2.0) and need to be reinitialized after wake-from-sleep. This guide uses _Automator_ to initialize supported devices at login, and _sleepwatcher_ to initialize supported devices after wake-from-sleep.
- [This guide](https://www.tonymacx86.com/threads/asus-z690-proart-creator-wifi-thunderbolt-4-i7-12700k-amd-rx-6800-xt.318311/post-2306524) is for controllers that do not lose their state during sleep (e.g. Asus Aura LED). This driver uses the _launchctl_ method to initialize supported devices at login.
## Troubleshooting

136
docs/asus-aura-led-guide.md Normal file
View File

@ -0,0 +1,136 @@
# Asus Aura LED (USB-based) controllers
_Driver API and source code available in [`liquidctl.driver.aura_led`](../liquidctl/driver/aura_led.py)._
__NOTE:__
This driver is marked `experimental` because of the need for additional testing and feedback by the community. The API is unstable and may be changed in the future.
This driver supports Asus Aura USB-based lighting controllers that appear in various Asus Z490, Z590, and Z690 motherboards. These controllers operate in either (a) direct mode or (b) effect mode. _Direct_ mode is employed by Aura Crate in Windows. It requires the application to send a continuous stream of commands to the controller in order to modulate lighting effects on each addressable LED. The other mode is _effect_ mode in which the controller itself modulates lighting effects on each addressable LED. Effect mode requires the application to issue a single set of command codes to the controller in order to initiate the given effect. The controller continues to process that effect until the application sends a different command.
This driver employs the _effect_ mode (fire and forget). The selected lighting mode remains in effect until it is explicitly changed. This means the selected lighting mode remains in effect (a) on cold boot, (b) on warm boot, (c) after wake-from-sleep.
The disadvantage, however, is the inability to set different lighting modes to different lighting channels. All channels remain synchronized.
There are three known variants of the Aura LED USB-based controller:
- Device `0x19AF`: found in Asus ProArt Z690-Creator WiFi
- Device `0x1939` [^1]
- Device `0x18F3`[^1]: found in Asus ROG Maximus Z690 Formula
[^1]: Support for devices `0x1939` and `0x18F3` may not be sufficiently developed so users are asked to experiment and provide feedback. [Wireshark USB traffic capture](./developer/capturing-usb-traffic.md), in particular, will be very helpful.
## Initialization
Asus Aura LED controller does not need to be initialized before use. Initialization is optional.
```
# liquidctl -m Aura initialize
AsusTek Aura LED Controller
└── Firmware version AULA3-AR32-0207
```
## Status
The `status` function returns the number of ARGB and RGB channels detected by the controller. If the command is invoked with `--debug` flag, the entire reply from the controller will be displayed in groups of 6 bytes. This information has not been fully decoded, but is provided in the event that someone is able to decipher it.
On Asus ProArt Z690-Creator WiFi the following is returned:
```
% liquidctl -m Aura status
AsusTek Aura LED Controller (experimental)
├── ARGB channels: 2
└── RGB channels: 1
```
To display the set of 6-byte status values, use `--debug` on the command line. The following will be returned:
```
% liquidctl --debug -m Aura status
AsusTek Aura LED Controller (experimental)
├── ARGB channels: 2
├── RGB channels: 1
├── Device Config: 1 0x1e, 0x9f, 0x02, 0x01, 0x00, 0x00
├── Device Config: 2 0x78, 0x3c, 0x00, 0x01, 0x00, 0x00
├── Device Config: 3 0x78, 0x3c, 0x00, 0x00, 0x00, 0x00
├── Device Config: 4 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
├── Device Config: 5 0x00, 0x00, 0x00, 0x01, 0x03, 0x02
├── Device Config: 6 0x01, 0xf4, 0x00, 0x00, 0x00, 0x00
├── Device Config: 7 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
├── Device Config: 8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
├── Device Config: 9 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
└── Device Config: 10 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
```
On Asus ROG Strix Z690-i Gaming WiFi (mini-ITX) the following is returned:
```
% liquidctl --debug -m Aura status
AsusTek Aura LED Controller (experimental)
├── ARGB channels: 2
├── RGB channels: 1
├── Device Config: 1 0x1e, 0x9f, 0x02, 0x01, 0x00, 0x00
├── Device Config: 2 0x78, 0x3c, 0x00, 0x01, 0x00, 0x00
├── Device Config: 3 0x78, 0x3c, 0x00, 0x00, 0x00, 0x00
├── Device Config: 4 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
├── Device Config: 5 0x00, 0x00, 0x00, 0x01, 0x03, 0x02
├── Device Config: 6 0x01, 0xf4, 0x00, 0x00, 0x00, 0x00
├── Device Config: 7 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
├── Device Config: 8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
├── Device Config: 9 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
└── Device Config: 10 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
```
On some Asus Z490 boards (controller ID 0x18F3) the following is returned:
```
% liquidctl --debug -m Aura status
AsusTek Aura LED Controller (experimental)
├── ARGB channels: 1
├── RGB channels: 1
├── Device Config: 1 0x1e, 0x9f, 0x01, 0x01, 0x00, 0x00
├── Device Config: 2 0x78, 0x3c, 0x00, 0x00, 0x00, 0x00
├── Device Config: 3 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
├── Device Config: 4 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
├── Device Config: 5 0x00, 0x00, 0x00, 0x06, 0x07, 0x02
├── Device Config: 6 0x01, 0xf4, 0x00, 0x00, 0x00, 0x00
├── Device Config: 7 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
├── Device Config: 8 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
├── Device Config: 9 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
└── Device Config: 10 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
```
## RGB lighting
The driver supports one 12V RGB channel named `led1` and three 5V Addressable RGB channels named `led2`, `led3`, and `led4`. Because the driver uses `effect` mode, all channels are synchronized. It is not possible at this time to set different color modes to different channels (`direct` mode is used for that). Nevertheless, independent channel names are provided in case a future BIOS update provides more flexibility in `effect` mode.
```
# liquidctl -m Aura set led1 color static af5a2f
# liquidctl -m Aura set led2 color breathing 350017
# liquidctl -m Aura set led3 color rainbow
# liquidctl -m Aura set led4 color spectrum-cycle
# liquidctl -m Aura set sync color gentle-transition
```
Colors can be specified in RGB, HSV or HSL (see [Supported color specification formats](../README.md#supported-color-specification-formats)), and each animation mode supports zero or one color.
| Mode | Colors | Notes |
| --- | --- | --- |
| `off` | None |
| `static` | One |
| `breathing` | One |
| `flashing` | One |
| `spectrum_cycle` | None |
| `rainbow` | None |
| `spectrum_cycle_breathing` | None |
| `chase_fade` | One |
| `spectrum_cycle_chase_fade` | None |
| `chase` | One |
| `spectrum_cycle_chase` | None |
| `spectrum_cycle_wave` | None |
| `chase_rainbow_pulse` | None |
| `rainbow_flicker` | None |
| `gentle_transition` | None | name given by us |
| `wave_propagation` | None | name given by us |
| `wave_propagation_pause` | None | name given by us |
| `red_pulse` | None | name given by us |
In addition to these, it is also possible to use the `sync` pseudo-channel to apply a setting to all lighting channels.

View File

@ -329,6 +329,15 @@ KERNEL=="i2c-*", ATTR{name}=="NVIDIA i2c adapter 1 *", ATTRS{vendor}=="0x10de",
# Asetek 690LC (assuming NZXT Kraken X)
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2433", ATTRS{idProduct}=="b200", TAG+="uaccess"
# Asus Aura LED Controller
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0b05", ATTRS{idProduct}=="19af", TAG+="uaccess"
# Asus Aura LED Controller
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0b05", ATTRS{idProduct}=="1939", TAG+="uaccess"
# Asus Aura LED Controller
SUBSYSTEMS=="usb", ATTRS{idVendor}=="0b05", ATTRS{idProduct}=="18f3", TAG+="uaccess"
# Corsair Commander Core
SUBSYSTEMS=="usb", ATTRS{idVendor}=="1b1c", ATTRS{idProduct}=="0c1c", TAG+="uaccess"

View File

@ -579,6 +579,37 @@ When applicable the animation speed can be set with
where the allowed values are: \fIslowest\fR, \fIslower\fR, \fInormal\fR,
\fIfaster\fR, \fIfastest\fR.
.
.SS ASUS Aura USB LED 0x19AF Controller
.SS ASUS Aura USB LED 0x1939 Controller
.SS ASUS Aura USB LED 0x18F3 Controller
Fan channels: none.
.PP
Lighting channels: \fIled[1\(en4]\fR, \fIsync\fR.
.TS
l c
----
l c .
Mode #colors
\fIoff\fR 0
\fIstatic\fR 1
\fIbreathing\fR 1
\fIflashing\fR 1
\fIspectrum_cycle\fR 0
\fIrainbow\fR 0
\fIspectrum_cycle_breathing\fR 0
\fIchase_fade\fR 1
\fIspectrum_cycle_chase_fade\fR 0
\fIchase\fR 1
\fIspectrum_cycle_chase\fR 0
\fIspectrum_cycle_wave\fR 0
\fIchase_rainbow_pulse\fR 0
\fIrainbow_flicker\fR 0
\fIgentle_transition\fR 0
\fIwave_propagation\fR 0
\fIwave_propagation_pause\fR 0
\fIred_pulse\fR 0
.TE
.
.SS Gigabyte RGB Fusion 2.0 5702 Controller
.SS Gigabyte RGB Fusion 2.0 8297 Controller
Fan channels: none.

View File

@ -25,6 +25,7 @@ from liquidctl.driver.base import BaseBus, find_all_subclasses
# automatically enabled drivers
from liquidctl.driver import asetek
from liquidctl.driver import asetek_pro
from liquidctl.driver import aura_led
from liquidctl.driver import commander_core
from liquidctl.driver import commander_pro
from liquidctl.driver import corsair_hid_psu

View File

@ -0,0 +1,345 @@
"""liquidctl driver for Asus Aura LED USB controllers.
Supported controllers:
- AsusTek Aura LED Controller: idVendor=0x0b05, idProduct=0x19af
This controller is found on Asus ProArt Z690-Creator WiFi and other boards
Additional controllers (requires user testing and feedback):
- idVendor=0x0B05, idProduct=0x1939
- idVendor=0x0B05, idProduct=0x18F3
These controllers have not been sufficiently tested. Users are asked to test and
report issues.
Limitations:
This controller only supports 'effect' modes that are managed entirely by the
controller. The other mode is 'direct' that is managed by software, but this
requires the continuous transmission of command codes to control dynamic lighting
effects (i.e. all lighting modes in which colors either change or the LED
blinks/fades/pulses).
In 'effect' mode, the software simply issues a 'fire-and-forget' command to the
controller, which subsequently manages the lighting effect on its own. There are
some limitations in 'effect' mode, as follows:
- Dynamic color modes (spectrum, rainbow, etc.) cannot be applied to individual
color channels, but apply to all channels
- Static color modes (static) can be applied to individual color channels
- Off mode turns all channels off regardless of which channel is selected. However,
individual channels can subsequently be enabled, while others will remain off.
This applies only to static modes.
Acknowledgements:
- Aura Addressable Header Controller (for list of color mode names)
https://gitlab.com/cneil02/aura-addressable-header-controller
- OpenRGB Project (for list of color mode names)
https://github.com/CalcProgrammer1/OpenRGB
- @dehjomz for discovering color modes 0x10, 0x11, 0x12, 0x13
Aura LED control codes were independently obtained from USB traffic captured using
Wireshark on Windows. This Aura LED controller uses very different control codes
from previous Aura LED controllers.
Copyright (C) 2022 CaseySJ
SPDX-License-Identifier: GPL-3.0-or-later
"""
import logging
import sys
from collections import namedtuple
from liquidctl.driver.usb import UsbHidDriver
from liquidctl.error import NotSupportedByDevice
from liquidctl.util import clamp
_LOGGER = logging.getLogger(__name__)
_READ_LENGTH = 65
_WRITE_LENGTH = 65
_RESET_CHANNEL_ITERATIONS = 5 # Aura Crate software uses 38
_CMD_CODE = 0xEC
_FUNCTION_CODE = {
"direct": [_CMD_CODE, 0x40],
"end_direct": [_CMD_CODE, 0x35, 0x00, 0x00, 0x00, 0x00],
"end_effect": [_CMD_CODE, 0x35, 0x00, 0x00, 0x00, 0xFF],
"start_seq1": [_CMD_CODE, 0x35],
"start_seq2": [_CMD_CODE, 0x36, 0x00],
"end_seq1": [_CMD_CODE, 0x35, 0x00, 0x00, 0x01, 0x05],
"end_seq2": [_CMD_CODE, 0x3F, 0x55, 0x00, 0x00],
"reset_seq1": [_CMD_CODE, 0x38, 0x01, 0x01],
"reset_seq2": [_CMD_CODE, 0x38, 0x00, 0x01],
"channel_off_pre1": [_CMD_CODE, 0x38, 0x01, 0x00],
"channel_off_pre2": [_CMD_CODE, 0x38, 0x00, 0x00],
"channel_off_prefix": [_CMD_CODE, 0x35, 0x01, 0x00, 0x00, 0xFF],
"firmware": [_CMD_CODE, 0x82],
"config": [_CMD_CODE, 0xB0],
}
# channel_type 0 designates RGB bus
# channel_type 1 designates ARGB bus
# "effect" mode channel IDs are different from "direct" mode channel IDs
_ColorChannel = namedtuple(
"_ColorChannel", ["name", "channel_id", "direct_channel_id", "channel_type", "rgb_offset"]
)
_COLOR_CHANNELS = {
channel.name: channel
for channel in [
_ColorChannel("led1", 0x01, 0x00, 0x00, 0x0), # rgb channel
_ColorChannel("led2", 0x02, 0x00, 0x01, 0x01), # argb channel
_ColorChannel("led3", 0x04, 0x01, 0x01, 0x02), # argb channel
_ColorChannel("led4", 0x08, 0x02, 0x01, 0x03), # argb channel
]
}
_ColorMode = namedtuple("_ColorMode", ["name", "value", "takes_color"])
_COLOR_MODES = {
mode.name: mode
for mode in [
_ColorMode("off", 0x00, takes_color=False),
_ColorMode("static", 0x01, takes_color=True),
_ColorMode("breathing", 0x02, takes_color=True),
_ColorMode("flashing", 0x03, takes_color=True),
_ColorMode("spectrum_cycle", 0x04, takes_color=False),
_ColorMode("rainbow", 0x05, takes_color=False),
_ColorMode("spectrum_cycle_breathing", 0x06, takes_color=False),
_ColorMode("chase_fade", 0x07, takes_color=True),
_ColorMode("spectrum_cycle_chase_fade", 0x08, takes_color=False),
_ColorMode("chase", 0x09, takes_color=True),
_ColorMode("spectrum_cycle_chase", 0x0A, takes_color=False),
_ColorMode("spectrum_cycle_wave", 0x0B, takes_color=False),
_ColorMode("chase_rainbow_pulse", 0x0C, takes_color=False),
_ColorMode("rainbow_flicker", 0x0D, takes_color=False),
_ColorMode("gentle_transition", 0x10, takes_color=False),
_ColorMode("wave_propagation", 0x11, takes_color=False),
_ColorMode("wave_propagation_pause", 0x12, takes_color=False),
_ColorMode("red_pulse", 0x13, takes_color=False),
]
}
class AuraLed(UsbHidDriver):
"""
liquidctl driver for Asus Aura LED USB controllers.
This driver only supports 'effect' mode, hence no speed/color channels
Devices 0x1939 and 0x18F3 are not fully supported at this time; users are asked
to experiment with this driver and provide feedback
"""
SUPPORTED_DEVICES = [
(0x0B05, 0x19AF, None, "AsusTek Aura LED Controller (experimental)", {}),
(0x0B05, 0x1939, None, "AsusTek Aura LED Controller (experimental)", {}),
(0x0B05, 0x18F3, None, "AsusTek Aura LED Controller (experimental)", {}),
]
def initialize(self, **kwargs):
"""Initialize the device.
Returns a list of `(property, value, unit)` tuples, containing the
firmware version and other useful information provided by the hardware.
"""
# Get firmware version
self._write(_FUNCTION_CODE["firmware"])
# Build reply string
status = []
data = self.device.read(_READ_LENGTH)
if data[1] == 0x02:
status.append(("Firmware version", "".join(map(chr, data[2:17])), ""))
else:
status.append("Unexpected reply for firmware", "", "")
return status
# This stops Direct mode if it was previously applied
self._write(_FUNCTION_CODE["end_direct"])
"""
Extra operations during initialization
This is experimental and may not be necessary
self._write([0xec, 0x31, 0x0d, 0x00]);
self._write([0xec, 0xb1, 0x00, 0x00]);
self.device.read(_READ_LENGTH)
self._write([0xec, 0x31, 0x0e, 0x00]);
self._write([0xec, 0xb1, 0x00, 0x00]);
self.device.read(_READ_LENGTH)
self._write([0xec, 0x31, 0x0f, 0x00]);
self._write([0xec, 0xb1, 0x00, 0x00]);
self.device.read(_READ_LENGTH)
self._write([0xec, 0x83, 0x00, 0x00]);
self.device.read(_READ_LENGTH)
"""
return status
def get_status(self, **kwargs):
"""Get a status report."""
status = []
# Get config table
self._write(_FUNCTION_CODE["config"])
data = self.device.read(_READ_LENGTH)
if data[1] == 0x30:
start_index = 4 # index of first record
argb_channels = data[start_index + 2]
rgb_channels = data[start_index + 3]
status.append(("ARGB channels: " + str(argb_channels), "", ""))
status.append((" RGB channels: " + str(rgb_channels), "", ""))
if "debug" in kwargs and kwargs["debug"] == True:
num = 6 # number of bytes per record
count = 1
while start_index + num < _READ_LENGTH:
status.append(
(
"Device Config: " + str(count),
", ".join(
"0x{:02x}".format(x) for x in data[start_index : start_index + num]
),
"",
)
)
start_index += num
count += 1
else:
status.append("Unexpected reply for config", "", "")
return status
def set_color(self, channel, mode, colors, speed="normal", **kwargs):
"""Set the color mode for a specific channel.
`colors` should be an iterable of zero or one `[red, green, blue]`
triples, where each red/green/blue component is a value in the range
0255.
"""
colors = iter(colors)
if _COLOR_MODES[mode].takes_color:
try:
r, g, b = next(colors)
single_color = (r, g, b)
except:
raise ValueError(f"one color required for this mode") from None
else:
single_color = (0, 0, 0)
if channel != "sync" and channel not in _COLOR_CHANNELS:
message = "valid channels are "
for chan in _COLOR_CHANNELS:
message += chan + " "
raise KeyError(message) from None
return
"""
This is experimental (it's an example of direct mode)
if mode == 'off':
self.channel_off(channel)
self.reset_all_channels()
return
"""
if channel == "sync":
selected_channels = _COLOR_CHANNELS.values()
else:
selected_channels = (_COLOR_CHANNELS[channel],)
full_cmd_seq = [] # entire series of commands are added to this list
"""
Experimental code for treating RGB channel differently from others
if channel == "led1":
cmd_tuple=self.construct_color_commands(channel, mode, single_color)
self._write(cmd_tuple[0])
self._write(cmd_tuple[1])
self._write(_FUNCTION_CODE["end_seq2"])
self.end_color_sequence()
self._write(cmd_tuple[0])
self._write(cmd_tuple[1])
self._write(_FUNCTION_CODE["end_seq2"])
else:
"""
for chan in selected_channels:
cmd_tuple = self.construct_color_commands(chan.name, mode, single_color)
full_cmd_seq.append(cmd_tuple[0])
full_cmd_seq.append(cmd_tuple[1])
full_cmd_seq.append(_FUNCTION_CODE["end_seq2"])
"""
Asus Aura Crate sends command sequence twice, but our tests show
that this may be redundant. Nevertheless, let's keep this code here
in case we need to send commands twice as well
#full_cmd_seq.append(cmd_tuple[0])
#full_cmd_seq.append(cmd_tuple[1])
#full_cmd_seq.append(_FUNCTION_CODE["end_seq2"])
"""
for cmd_seq in full_cmd_seq:
self._write(cmd_seq)
self.end_color_sequence()
def set_speed_profile(self, channel, profile, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()
def set_fixed_speed(self, channel, duty, **kwargs):
"""Not supported by this device."""
raise NotSupportedByDevice()
def reset_all_channels(self):
"""Reset all LED channels."""
for i in range(_RESET_CHANNEL_ITERATIONS):
self._write(_FUNCTION_CODE["reset_seq1"])
self._write(_FUNCTION_CODE["reset_seq2"])
def channel_off(self, channel):
"""
Uses direct mode to disable a specific channel
"""
self._write(_FUNCTION_CODE["end_effect"])
for i in range(_RESET_CHANNEL_ITERATIONS):
self._write(_FUNCTION_CODE["channel_off_pre1"])
self._write(_FUNCTION_CODE["channel_off_pre2"])
self._write(_FUNCTION_CODE["channel_off_prefix"])
# set all LEDs to off, 20 at a time
for i in (0, 20, 40, 60, 80, 100):
self._write(
_FUNCTION_CODE["direct"]
+ [_COLOR_CHANNELS[channel].direct_channel_id | (0x80 * (i == 100)), i, 20]
)
self.end_color_sequence()
self._write(_FUNCTION_CODE["end_direct"])
def construct_color_commands(self, channel, mode, single_color):
"""
Create command strings for specified color channel
"""
mode = _COLOR_MODES[mode]
channel_type = _COLOR_CHANNELS[channel].channel_type # 0=RGB, 1=ARGB
channel_id = _COLOR_CHANNELS[channel].channel_id
rgb_offset = _COLOR_CHANNELS[channel].rgb_offset
data1 = _FUNCTION_CODE["start_seq1"] + [channel_type, 0x00, 0x00, mode.value]
data2 = _FUNCTION_CODE["start_seq2"] + [channel_id, 0x00] + [0, 0, 0] * rgb_offset
data2 += single_color
return (data1, data2)
def end_color_sequence(self):
self._write(_FUNCTION_CODE["end_seq1"])
self._write(_FUNCTION_CODE["end_seq2"])
def _write(self, data):
padding = [0x0] * (_WRITE_LENGTH - len(data))
self.device.write(data + padding)

115
tests/test_aura_led.py Normal file
View File

@ -0,0 +1,115 @@
import pytest
from _testutils import MockHidapiDevice, Report
from collections import deque
from liquidctl.driver.aura_led import AuraLed
# Sample data for Aura LED controller from Asus ProArt Z690-Creator WiFi
_INIT_19AF_FIRMWARE_DATA = bytes.fromhex(
"ec0241554c41332d415233322d30323037000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000"
)
_INIT_19AF_FIRMWARE = Report(_INIT_19AF_FIRMWARE_DATA[0], _INIT_19AF_FIRMWARE_DATA[1:])
_INIT_19AF_CONFIG_DATA = bytes.fromhex(
"ec3000001e9f03010000783c00010000783c00010000783c0000000000000001"
"040201f40000000000000000000000000000000000000000000000000000000000"
)
_INIT_19AF_CONFIG = Report(_INIT_19AF_CONFIG_DATA[0], _INIT_19AF_CONFIG_DATA[1:])
@pytest.fixture
def mockAuraLed_19AFDevice():
device = MockHidapiDevice(vendor_id=0x0B05, product_id=0x19AF, address="addr")
dev = AuraLed(device, "mock Aura LED Controller")
dev.connect()
return dev
def test_aura_led_19AF_device_command_format(mockAuraLed_19AFDevice):
mockAuraLed_19AFDevice.device.preload_read(_INIT_19AF_FIRMWARE)
mockAuraLed_19AFDevice.device.preload_read(_INIT_19AF_CONFIG)
mockAuraLed_19AFDevice.initialize() # should perform 3 writes
mockAuraLed_19AFDevice.set_color(
channel="sync", mode="off", colors=[]
) # should perform 14 writes
assert len(mockAuraLed_19AFDevice.device.sent) == 2 + 14
for i, (report, data) in enumerate(mockAuraLed_19AFDevice.device.sent):
assert report == 0xEC
assert len(data) == 64
def test_aura_led_19AF_device_get_status(mockAuraLed_19AFDevice):
mockAuraLed_19AFDevice.device.preload_read(_INIT_19AF_CONFIG)
assert mockAuraLed_19AFDevice.get_status() != []
def test_aura_led_19AF_device_initialize_status(mockAuraLed_19AFDevice):
mockAuraLed_19AFDevice.device.preload_read(_INIT_19AF_FIRMWARE)
mockAuraLed_19AFDevice.device.preload_read(_INIT_19AF_CONFIG)
status_list = mockAuraLed_19AFDevice.initialize()
firmware_tuple = status_list[0]
assert firmware_tuple[1] == "AULA3-AR32-0207"
def test_aura_led_19AF_device_off_with_some_channel(mockAuraLed_19AFDevice):
colors = [[0xFF, 0, 0x80]] # should be ignored
mockAuraLed_19AFDevice.set_color(channel="led2", mode="off", colors=iter(colors))
assert len(mockAuraLed_19AFDevice.device.sent) == 5
data1 = mockAuraLed_19AFDevice.device.sent[0].data
data2 = mockAuraLed_19AFDevice.device.sent[1].data
assert data1[1] == 0x01 # key for led2
assert data1[4] == 0x00 # off
assert data2[2] == 0x02 # channel led2
assert data2[7:10] == [0x00, 0x00, 0x00]
def test_aura_led_19AF_static_with_some_channel(mockAuraLed_19AFDevice):
colors = [[0xFF, 0, 0x80], [0x30, 0x30, 0x30]] # second color should be ignored
mockAuraLed_19AFDevice.set_color(channel="led2", mode="static", colors=iter(colors))
assert len(mockAuraLed_19AFDevice.device.sent) == 5
data1 = mockAuraLed_19AFDevice.device.sent[0].data
data2 = mockAuraLed_19AFDevice.device.sent[1].data
assert data1[1] == 0x01 # key for led2
assert data1[4] == 0x01 # static mode
assert data2[2] == 0x02 # channel led2
assert data2[7:10] == [0xFF, 0x00, 0x80]
def test_aura_led_19AF_spectrum_cycle_with_some_channel(mockAuraLed_19AFDevice):
colors = [[0xFF, 0, 0x80], [0x30, 0x30, 0x30]] # second color should be ignored
mockAuraLed_19AFDevice.set_color(channel="led3", mode="spectrum_cycle", colors=iter(colors))
assert len(mockAuraLed_19AFDevice.device.sent) == 5
data1 = mockAuraLed_19AFDevice.device.sent[0].data
data2 = mockAuraLed_19AFDevice.device.sent[1].data
assert data1[1] == 0x01 # key for led3
assert data1[4] == 0x04 # spectrum cycle
assert data2[2] == 0x04 # channel led3
assert data2[7:10] == [0x00, 0x00, 0x00]
def test_aura_led_19AF_device_sync_channel(mockAuraLed_19AFDevice):
colors = [[0xFF, 0, 0x80]]
mockAuraLed_19AFDevice.set_color(channel="sync", mode="static", colors=iter(colors))
assert len(mockAuraLed_19AFDevice.device.sent) == 14 # 14 writes
def test_aura_led_19AF_device_invalid_set_color_arguments(mockAuraLed_19AFDevice):
with pytest.raises(KeyError):
mockAuraLed_19AFDevice.set_color("invalid", "off", [])
with pytest.raises(KeyError):
mockAuraLed_19AFDevice.set_color("led2", "invalid", [])
with pytest.raises(ValueError):
mockAuraLed_19AFDevice.set_color("led3", "static", [])
def test_aura_led_19AF_device_initialize_status(mockAuraLed_19AFDevice):
mockAuraLed_19AFDevice.device.preload_read(_INIT_19AF_FIRMWARE)
mockAuraLed_19AFDevice.device.preload_read(_INIT_19AF_CONFIG)
status_list = mockAuraLed_19AFDevice.initialize()
firmware_tuple = status_list[0]
assert firmware_tuple[1] == "AULA3-AR32-0207"