Keyboard backlight: drive the ENE K5130 directly on Predator PHN16S-71 - #8
Open
HectorHuaripata wants to merge 7 commits into
Open
Keyboard backlight: drive the ENE K5130 directly on Predator PHN16S-71#8HectorHuaripata wants to merge 7 commits into
HectorHuaripata wants to merge 7 commits into
Conversation
On the Predator PHN16S-71 the ACPI-WMI lighting path is only partially
implemented in firmware. Measured behaviour:
brightness applied
per-zone RGB stored, read back faithfully, never applied to the LEDs
effect mode ignored
This is why per_zone_mode appeared to work: writing it succeeds and the
sysfs read-back returns exactly what was written, while the keyboard keeps
running the factory rainbow. On this model the read-back is not evidence.
The LEDs are actually driven by an ENE K5130 on I2C-1 at 0x50, exposed as
HID-over-I2C 0018:0CF2:5130 with four vendor feature reports. Writing them
directly does work, verified against a fully dark baseline.
archer_ene.py new backend: select device via 0xA2, configure via
0xA4. Resolves the target by HID identity rather than
hidraw number, which is not stable across boots, and
refuses the internal keyboard and the touchpad.
archer_daemon.py both keyboard setters prefer the ENE backend and keep
the sysfs write as a fallback for other hardware.
keyboard.py the effect list now holds the five effects actually
verified on this controller instead of the eight from
the WMI documentation, most of which did nothing.
Speed and direction are hidden: they map to bytes 3
and 4 of report 0xA4, whose meaning is not yet known,
and a control that silently does nothing is worse than
no control.
Zones are addressed by a bitmask of the low four bits, so they combine:
0x3 paints the left half in one write. Full notes in docs/ENE_PROTOCOL.md.
Reverse-engineered and written with AI assistance; every behavioural claim
was verified by a human observing the hardware.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bytes 3 and 4 of report 0xA4 are now identified, and the keyboard mode sweep was extended past 7. byte 3 speed, 0-9, rising monotonically byte 4 direction, 1 = left-to-right, 2 = right-to-left Note the direction encoding is the REVERSE of the convention Archer and the Linuwu-Sense docs use, where 2 means left-to-right. set_effect() takes Archer's value and flips it for the wire, so the UI label stays correct. Four further effects were observed on the keyboard and are now exposed: mode 8 (a random segment, then a full-board flash), 9 (circular, outside inward), 10 (a streak crossing and returning over a dark board) and 11 (two of the four segments lit at random). Modes 13-31 produced nothing visible. Names are matched to the effects PredatorSense advertises, by behaviour rather than by any documented mapping; "Shifting" is the least certain and is marked as tentative in the docs. The mode ceiling is now per device: the keyboard accepts the full verified range, while other device ids stay capped at 7, because on the performance-mode button LED modes >= 8 coincided with fans starting and stopping. Speed and direction return to the UI as working controls. Verified on hardware against a dark baseline, one variable per step. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mode 10 is the one with a visible direction: light crosses the board and returns. That matches the documented description of Shifting, "shifting light effect, full control over speed, direction, and color", far better than Meteor does. Mode 8 -- a point flaring at random followed by a full-board flash -- reads as Meteor instead. Both remain marked tentative: there is no documented mapping between the ENE's mode numbers and the effect names PredatorSense advertises, so these are behavioural matches, not authority. Effect list order is unchanged, so saved settings indices stay valid. Also refreshes the module docstring, which still described bytes 3 and 4 as unknown and the mode ceiling as a flat limit rather than per device. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…file The lid logo (device 0x83) does follow the requested RGB. An earlier round concluded otherwise because the same green was sent with modes 1, 2 and 4 and produced off, green and red: three modes with three different behaviours, read as one capricious colour. With the mode held at 2 the logo matches red, green, blue and white exactly, and repeating an identical write repeats the identical result, so there is no hidden counter either. Logo modes: 1 off, 2 static honouring RGB, 3 off, 4 fixed red, 5 slow cycle, 6 fixed yellow, 7 off. The performance-mode button LED (0x65) is lit from the factory in a colour that tracks the active platform profile. Once this backend takes the controller over the firmware stops driving it and the button just goes dark, so the daemon now keeps it in step: set_thermal_profile() colours it, which also covers the restore path at startup since that goes through the same call. low-power cyan · quiet green · balanced white balanced-performance purple (the factory colour here) · performance red Unknown profile names are left alone rather than guessed at, so a kernel that grows a new profile does not silently get the wrong colour. The behaviour is gated on button_follows_profile in settings.json, default on, and any failure is logged and swallowed: a lighting detail must never make a thermal profile change report failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rable Lighting did not survive suspend. Neither acer_suspend() nor acer_resume() touch RGB, and the ENE loses its state across the sleep, so the keyboard came back under whatever the EC decided. The daemon now listens for logind's PrepareForSleep and reapplies the saved state on the way back up. Hooking that signal keeps this self-contained: no extra systemd unit to install, and the daemon already runs a GLib loop. The write is deferred two seconds because the I2C-HID controller re-enumerates shortly after resume and PrepareForSleep(False) arrives before that. reapply_lighting() is deliberately narrow: lighting only. A resume is not the moment to start rewriting the thermal profile or the fans, so those are left alone even though the same settings file holds them. Also: the per-profile button colours are now overridable through button_colours in settings.json, and the defaults are documented for what they are -- a choice made here, not Acer's mapping. Acer does not publish the per-mode colours, and once this backend takes the controller over the factory firmware stops driving the LED, so there is nothing left to read them from. Only the purple on balanced-performance came from observed hardware. The balanced default is now blue, to match Archer's own palette. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every write to the performance-mode button LED silently did nothing,
because BUTTON_STATIC was 1 and mode 1 is a no-op on that device. A fresh
sweep from a known state gives:
1 nothing · 2 static, honours RGB · 3 nothing · 4 breathing
5 colour cycle · 6 off · 7 nothing
So mode 2 is static colour on ALL THREE devices -- keyboard, button and lid
logo alike. That is a simpler rule than the one documented before, and the
earlier claim that the button's static lived at mode 1 came from a single
observation during the first sweep that later evidence does not support.
What remains per device is the rest of the map: on the keyboard mode 1
turns it off, on the button mode 1 does nothing and mode 6 does. Mode 6 on
the button also briefly darkens the keyboard, which the EC restores a
second or two later.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
set_thermal_profile() only covers changes made through Archer. The Plasma widget, powerprofilesctl, the driver's own restore at module load and the hardware mode button all write platform_profile directly, and the LED would sit there showing a profile the machine is no longer in. A three-second poll of one small sysfs file catches every writer without needing to know who they are, which matters here because there are several and they do not coordinate. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Keyboard backlight: drive the ENE K5130 directly on Predator PHN16S-71
Summary
On the Acer Predator Helios Neo 16S AI (PHN16S-71) the keyboard lighting page
looks like it works and does nothing. Setting zone colours succeeds, the value
is stored, the sysfs read-back returns exactly what was written — and the
keyboard keeps running the factory rainbow.
The cause is in firmware, not in Archer: the ACPI-WMI lighting path on this
model is partially implemented. Measured, with a single WMI call:
Because the read-back is faithful, every layer above it reports success. That
is what makes this bug so quiet, and it is worth stating plainly: on this model
the sysfs read-back is not evidence that anything happened.
The LEDs are actually driven by an ENE K5130 on I2C-1 at address
0x50,exposed as a standard HID-over-I2C device (
0018:0CF2:5130) carrying fourvendor feature reports. Writing those reports directly does work.
This PR adds that backend and routes the two keyboard setters through it,
keeping the existing sysfs write as a fallback for other hardware.
Changes
gui/archer_ene.pygui/archer_daemon.pygui/archer/pages/keyboard.pydocs/ENE_PROTOCOL.mdinstall-ene-keyboard.shNothing changes for hardware where the ENE is absent:
archer_eneis importedin a
try,available()gates the feature, and the old code path is untouched.The protocol, briefly
Device ids:
0x21keyboard (4 zones),0x65performance-mode button LED,0x83lid logo.Two things that cost the most time to find, and that anyone extending this
should know:
Mode semantics are per device and do not carry over. On the keyboard mode 1
turns it off and mode 2 is static colour. On the button LED mode 1 is static
colour. Generalising from one device to another produces silent no-ops.
The zone field is a bitmask of the low four bits, so zones combine:
0x3paints the left half in a single write. The high byte is ignored.
Verified keyboard effects: 2 static, 4 fade, 5 colour cycle, 6 colour cycle
fast, 7 rainbow wave. Modes 5 and 6 are not exposed by any existing tool for
this machine.
Why speed and direction are hidden in the UI
They correspond to bytes 3 and 4 of report
0xA4, whose meaning is notestablished. The daemon accepts them for signature compatibility and ignores
them rather than guessing. A control that silently does nothing is worse than
no control, so both are hidden until decoded.
Safety notes
numbering is not stable on this machine: the ENE's reset times out during
probe so it enumerates last, and the touchpad can rebind and reclaim
hidraw0.with fans starting and stopping, so in that range the report probably reaches
the performance profile and not just the LED. Not thermally dangerous — the
EC governs the fan curve — but out of scope for a lighting control.
POLKIT_ACTIONSalready maps both keyboard methods, so authorisation isunchanged and no new method bypasses it.
Testing
Verified on Predator PHN16S-71, BIOS V1.26, kernel 6.17 series, Arch Linux with
linuwu-sense 1.0.0 (DKMS).
Every result below was taken against a fully dark baseline — keyboard,
button LED and lid logo all off — so that anything lighting up admits no
alternative reading. That matters: an earlier round of this work reached a
wrong conclusion precisely because no baseline was recorded before the first
write.
0x1/0x2/0x4/0x8each paint theirown quarter,
0x3paints the left half,0xFand0xFFFFthe whole board,0x0100does nothing.Known gaps
0x83(lid logo) responds, but returned green/yellow/red for threeconsecutive writes of the same colour, so it does not interpret the RGB
bytes the way the keyboard does. Not wired into the UI.
0xA4undecoded.acer_suspend/acer_resumedo not touch RGB.