GitHub - bhoot1234567890/ROG-Ally-LED-Control: Control ASUS ROG Ally joystick RGB ring LEDs without Armoury Crate. Decoded HID protocol. · GitHub
Skip to content

About

Control ASUS ROG Ally joystick RGB ring LEDs without Armoury Crate. Decoded HID protocol.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

3 Commits

Folders and files

Repository files navigation

ROG Ally LED Control

Python Platform Interface Depends on Armoury Crate

Control the RGB rings around the joysticks on the ASUS ROG Ally and ROG Ally X over USB HID — no Armoury Crate, no ASUS services, no GUI.

The ROG Ally slowly pulses its joystick RGB rings whenever it sleeps on a charger, and there is no hardware switch to stop it. In a dark room at 3 AM, that strobe is relentless. The only official fix lives inside Armoury Crate — a 500 MB+ Windows-only stack of services, DLLs and COM objects that re-spawn each other and that you cannot run on SteamOS or Bazzite at all.

This is a single Python file. It talks to the Ally's microcontroller directly through USB HID feature reports, the setting is written into the MCU's own flash, and it survives reboots and even OS reinstalls. The HID protocol was decoded from the open-source asusctl project (Rust).

✨ Features

  • One flag kills the charging strobe — the reason this project exists (--no-sleep-strobe).
  • Settings persist in MCU flash across reboots and across OS reinstalls. Wipe Windows, install Linux — the byte you wrote is still there.
  • No Armoury Crate, no LightingService, no GUI. One Python file plus hidapi. Nothing to install, nothing running in the background.
  • Four independent power-state bits — boot, awake, sleep, shutdown — each toggleable.
  • 12 LED effects with per-call color, speed and direction: static, breathe, rainbow cycle/wave, stars, rain, laser, ripple, pulse, comet, flash and more.
  • Supports both handhelds — ROG Ally (RC71L, PID 0x1ABE) and ROG Ally X (RC72L, PID 0x1B4C).
  • Cross-platform via hidapi — runs on Windows, SteamOS and Bazzite.

📦 Installation

Prerequisites

  • Python 3.8+
  • A ROG Ally (RC71L) or ROG Ally X (RC72L), connected over USB
  • The hidapi Python binding (provides the hid module)

Install the dependency

pip install hidapi

On Windows, run from an admin shell if Armoury Crate's LightingService is still installed — it can overwrite your changes (see Service conflict).

🚀 Usage

Minimal — stop the charging strobe right now:

python ally_led_control.py --no-sleep-strobe

Expected output:

Power: boot=1 awake=1 sleep=0 shutdown=1
  byte=0x0B (00001011)  sent=17

Turn the rings fully off / fully on:

python ally_led_control.py --all-off
python ally_led_control.py --all-on

Set a raw power byte (bitmask, see Power byte):

python ally_led_control.py --power 0x0B

Set an effect — name, RGB triple, speed, direction:

python ally_led_control.py --effect rainbow_wave --color 255,0,128
python ally_led_control.py --effect static --color 0,255,0
python ally_led_control.py --effect breathe --speed low
python ally_led_control.py --effect comet --color 0,128,255 --speed high --direction left

Expected output:

Effect: mode=0x3 color=(255,0,128) speed=med dir=right
  effect sent=17  apply sent=17

List detected ASUS HID devices (useful for debugging enumeration):

python ally_led_control.py --list

⚙️ Configuration

Everything is driven by CLI flags — there is no config file and no environment variables.

CLI flags

Flag Default Description
--no-sleep-strobe — boot=1 awake=1 sleep=0 shutdown=1 → byte 0x0B. Disables the charging/sleep strobe.
--all-on — All four power bits set → byte 0x0F.
--all-off — All four power bits cleared → byte 0x00.
--power BYTE — Set the power state as a hex byte, e.g. 0x0B.
--effect MODE — Apply an LED effect (see Effects).
--color R,G,B 255,0,0 RGB color for the effect, each channel 0–255.
--speed med Effect speed: low | med | high.
--direction right Effect direction: left | right.
--list — Print every ASUS HID device matching VID 0x0B05.

Power byte

The MCU stores four power-state bits in its own flash. The byte is boot | (awake << 1) | (sleep << 2) | (shutdown << 3):

Bit Value Name What it controls
0 0x01 boot LEDs on during system boot
1 0x02 awake LEDs on while the system is active
2 0x04 sleep LEDs on during sleep / charging — the strobe
3 0x08 shutdown LEDs on during shutdown

Common values: 0x0F = everything on, 0x00 = everything off, 0x0B = everything except the sleep strobe.

Effects

Mode Hex Mode Hex
static 0x00 highlight 0x06
breathe 0x01 laser 0x07
rainbow_cycle 0x02 ripple 0x08
rainbow_wave 0x03 pulse 0x0A
stars 0x04 comet 0x0B
rain 0x05 flash 0x0C

Speeds map to low=0xE1, med=0xEB, high=0xF5; directions to left=0x01, right=0x00.

🧱 How It Works

Device & endpoint

The Ally's MCU is a USB composite device. The LED control lives in one specific HID collection, which the script locates by usage rather than by interface number:

  • VID: 0x0B05 (ASUS)
  • PID: 0x1ABE (Ally) or 0x1B4C (Ally X)
  • HID collection: UsagePage 0xFF31, Usage 0x0079 (Collection 03)
  • Report ID: 0x5D

All commands are 17-byte HID feature reports led by the report ID.

Packet layout

Power:  5D D1 09 01 <power> 00 00 ...                         (17 bytes)
Effect: 5D B3 00 <mode> <R> <G> <B> <speed> <dir> 00 <R> <G> <B> 00 00 00 00
Apply:  5D B4 00 00 ...                                       (17 bytes)

Effects are a two-step write: 0xB3 stages the effect, then 0xB4 commits it.

sequenceDiagram
    participant U as ally_led_control.py
    participant H as hidapi (USB HID)
    participant M as ROG Ally MCU (Col03)
    U->>H: enumerate(VID 0x0B05, PID 0x1ABE / 0x1B4C)
    H-->>U: path of UsagePage 0xFF31 / Usage 0x0079
    Note over U,M: Power state
    U->>M: feature report 0x5D · cmd 0xD1 [power byte]
    M->>M: write MCU flash
    Note over U,M: Effect
    U->>M: feature report 0x5D · cmd 0xB3 [effect]
    U->>M: feature report 0x5D · cmd 0xB4 [apply]
Loading

Armoury Crate service conflict

On Windows, Armoury Crate's LightingService.exe re-applies its own config on startup and will overwrite direct MCU writes. To make a setting stick:

  1. Stop the service.
    net stop LightingService
  2. Send the command.
    python ally_led_control.py --no-sleep-strobe
  3. Update or remove C:\ProgramData\ASUS\RogAura30\AuraSync.ini so Armoury Crate re-applies your preference on the next reboot — or disable the service entirely.

About the "plugged in / battery" toggles

Armoury Crate's UI exposes separate AC and battery toggles for the awake state. Those are software-only: the MCU has a single awake bit. Armoury Crate stores two preferences in AuraSync.ini and re-sends the right one when the power source changes. This script writes the hardware bit directly.

How the protocol was decoded

  1. Mapped the USB device — the MCU is a composite device with six HID collections across three interfaces; LED control is Collection 03.

  2. Tried Frida hooks on HidD_SetFeature, DeviceIoControl and WriteFile in LightingService.exe and ArmouryCrateSE.Service.exe — zero HID calls captured, because on Windows the control path runs through ACPI/WMI, not HID.

  3. Analyzed the DLLs — AuraSdk_x86.dll is a COM client; ServiceCore.dll imports DeviceIoControl and GetFirmwareEnvironmentVariableW, confirming the WMI/ACPI path.

  4. Found the Rosetta Stone — the asusctl Rust project had already decoded the full protocol. rog-aura/src/keyboard/power.rs gives the exact power-byte encoding; rog-aura/src/builtin_modes.rs gives the effect packet format.

    PowerZones::Ally => vec![
        0x5d, 0xd1, 0x09, 0x01,
        (self.boot as u32) | ((self.awake as u32) << 1)
            | ((self.sleep as u32) << 2) | ((self.shutdown as u32) << 3),
    ]

🤝 Contributing

Single-file project — open an issue or PR against ally_led_control.py. The effect table and packet format are the most useful places to extend (new modes, brightness, per-joystick zones).

📄 License

This repository does not include a LICENSE file. The author has stated the intent is MIT ("the protocol is a fact, not IP"); add a LICENSE file to formalize redistribution terms.

See Also

  • asusctl — Linux system daemon for ASUS computers; the source of the decoded protocol.
  • allycenter — Decky plugin for Ally controls on SteamOS.

About

Control ASUS ROG Ally joystick RGB ring LEDs without Armoury Crate. Decoded HID protocol.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages