Created
March 19, 2026 11:51
-
-
Save rmuxnet/002a2ae105ffca290ada36a39d645f70 to your computer and use it in GitHub Desktop.
ps4 icc led
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
| """ | |
| ps4_icc_led.py | |
| -------------- | |
| LED / indicator control via ICC major 0x09, minor 0x20. | |
| set_led(color_name) set LED to a named color/effect | |
| set_led_custom(blue, white, prog) set LED with explicit channel values | |
| Hardware: PS4 Fat (Aeolia CXD90025G) | |
| Kernel: Linux 6.18.18-Strawberry-ThinLTO-LTS+ (CachyOS x86_64) | |
| DEVICE PERMISSIONS | |
| ------------------ | |
| If /dev/icc does not exist or you get PermissionError: | |
| cat /proc/devices | grep icc | |
| sudo mknod /dev/icc c <major> 0 | |
| sudo chmod 666 /dev/icc | |
| Permanent udev rule (survives reboots): | |
| echo 'KERNEL=="icc", MODE="0666"' | sudo tee /etc/udev/rules.d/99-ps4-icc.rules | |
| sudo udevadm control --reload-rules | |
| sudo udevadm trigger | |
| HARDWARE CHANNELS | |
| ----------------- | |
| 0x10 Blue | |
| 0x11 White | |
| 0x02 Orange (controlled via a 3-operation timed sequence) | |
| Red is not achievable. There is no red LED element on any PS4 hardware revision. | |
| PAYLOAD FORMAT (35 bytes) | |
| -------------------------- | |
| bytes [0-3] header: 03 01 00 00 | |
| bytes [4-10] Blue channel: 10 01 02 [BLUE_VAL] 02 01 00 | |
| bytes [11-17] White channel: 11 01 02 [WHITE_VAL] 02 01 00 | |
| bytes [18-19] Orange header: 02 03 | |
| bytes [20-34] Orange program: 3 operations x 5 bytes each | |
| Operation format (5 bytes): | |
| byte[0] opcode: 0x01=FADE-FROM, 0x02=HOLD/FADE-TO | |
| 0x03 and 0x04 accepted but visual behavior not yet observed | |
| byte[1] value: 0x00=off, 0xFF=full | |
| byte[2] timing_hi | |
| byte[3] timing_lo | |
| byte[4] flags: 0xFF=loop forever, 0x00=one-shot, 0xBF=loop with hold | |
| ORANGE PROGRAMS (bytes 20-34) | |
| ------------------------------ | |
| OFF 01 00 04 01 bf 02 00 05 01 ff 02 00 05 01 ff | |
| ON 02 ff 02 01 00 02 ff 05 01 ff 02 ff 05 01 00 | |
| COMBINED 01 00 04 01 bf 02 ff 05 01 ff 02 00 05 01 ff | |
| PULSATE 01 ff 04 01 00 02 ff 05 01 ff 02 ff 05 01 00 | |
| COLOR TABLE | |
| ----------- | |
| off blue=0x00 white=0x00 orange=OFF | |
| blue blue=0xFF white=0x00 orange=OFF | |
| white blue=0x00 white=0xFF orange=OFF | |
| white_blue blue=0xFF white=0xFF orange=OFF | |
| orange blue=0x00 white=0x00 orange=ON | |
| orange_blue blue=0xFF white=0x00 orange=COMBINED | |
| orange_white blue=0x00 white=0xFF orange=COMBINED | |
| orange_white_blue blue=0xFF white=0xFF orange=COMBINED | |
| violet_blue blue=0x57 white=0x00 orange=ON | |
| pink blue=0x00 white=0x30 orange=ON | |
| pink_blue blue=0x20 white=0x00 orange=ON | |
| pulsate_orange blue=0x00 white=0x00 orange=PULSATE | |
| HDMI WARNING | |
| ------------ | |
| Orange channel at timing (0x00, 0x01) causes monitor dropout via voltage noise | |
| on a shared power rail. This is a hardware electrical issue. Minimum safe | |
| timing is (0x01, 0x01). The programs defined in this module use safe timings. | |
| Usage: | |
| python ps4_icc_led.py blue | |
| python ps4_icc_led.py pulsate_orange | |
| python ps4_icc_led.py list | |
| """ | |
| import struct | |
| import ctypes | |
| import fcntl | |
| import sys | |
| from ps4_icc_core import ICC_IOCTL, ICC_STRUCT, ICCError | |
| LED_MAJOR = 0x09 | |
| LED_MINOR = 0x20 | |
| LED_PAYLOAD_LEN = 35 | |
| LED_REPLY_LEN = 0x30 | |
| ORANGE_OFF = bytes([ | |
| 0x01, 0x00, 0x04, 0x01, 0xbf, | |
| 0x02, 0x00, 0x05, 0x01, 0xff, | |
| 0x02, 0x00, 0x05, 0x01, 0xff, | |
| ]) | |
| ORANGE_ON = bytes([ | |
| 0x02, 0xff, 0x02, 0x01, 0x00, | |
| 0x02, 0xff, 0x05, 0x01, 0xff, | |
| 0x02, 0xff, 0x05, 0x01, 0x00, | |
| ]) | |
| ORANGE_COMBINED = bytes([ | |
| 0x01, 0x00, 0x04, 0x01, 0xbf, | |
| 0x02, 0xff, 0x05, 0x01, 0xff, | |
| 0x02, 0x00, 0x05, 0x01, 0xff, | |
| ]) | |
| ORANGE_PULSATE = bytes([ | |
| 0x01, 0xff, 0x04, 0x01, 0x00, | |
| 0x02, 0xff, 0x05, 0x01, 0xff, | |
| 0x02, 0xff, 0x05, 0x01, 0x00, | |
| ]) | |
| COLOR_TABLE = { | |
| 'off': (0x00, 0x00, ORANGE_OFF), | |
| 'blue': (0xFF, 0x00, ORANGE_OFF), | |
| 'white': (0x00, 0xFF, ORANGE_OFF), | |
| 'white_blue': (0xFF, 0xFF, ORANGE_OFF), | |
| 'orange': (0x00, 0x00, ORANGE_ON), | |
| 'orange_blue': (0xFF, 0x00, ORANGE_COMBINED), | |
| 'orange_white': (0x00, 0xFF, ORANGE_COMBINED), | |
| 'orange_white_blue': (0xFF, 0xFF, ORANGE_COMBINED), | |
| 'violet_blue': (0x57, 0x00, ORANGE_ON), | |
| 'pink': (0x00, 0x30, ORANGE_ON), | |
| 'pink_blue': (0x20, 0x00, ORANGE_ON), | |
| 'pulsate_orange': (0x00, 0x00, ORANGE_PULSATE), | |
| } | |
| def build_led_payload(blue_val, white_val, orange_program): | |
| assert 0x00 <= blue_val <= 0xFF | |
| assert 0x00 <= white_val <= 0xFF | |
| assert len(orange_program) == 15 | |
| payload = ( | |
| bytes([0x03, 0x01, 0x00, 0x00]) + | |
| bytes([0x10, 0x01, 0x02, blue_val, 0x02, 0x01, 0x00]) + | |
| bytes([0x11, 0x01, 0x02, white_val, 0x02, 0x01, 0x00]) + | |
| bytes([0x02, 0x03]) + | |
| orange_program | |
| ) | |
| assert len(payload) == LED_PAYLOAD_LEN | |
| return payload | |
| def _send_payload(payload): | |
| data_buf = ctypes.create_string_buffer(bytes(payload), LED_PAYLOAD_LEN) | |
| reply_buf = ctypes.create_string_buffer(LED_REPLY_LEN) | |
| cmd = struct.pack( | |
| ICC_STRUCT, | |
| LED_MAJOR, | |
| LED_MINOR, | |
| ctypes.addressof(data_buf), | |
| LED_PAYLOAD_LEN, | |
| ctypes.addressof(reply_buf), | |
| LED_REPLY_LEN, | |
| ) | |
| cmd_buf = ctypes.create_string_buffer(cmd) | |
| try: | |
| with open('/dev/icc', 'rb') as fd: | |
| fcntl.ioctl(fd, ICC_IOCTL, cmd_buf) | |
| except PermissionError: | |
| raise ICCError("Permission denied on /dev/icc. Fix: sudo chmod 666 /dev/icc") | |
| except FileNotFoundError: | |
| raise ICCError("/dev/icc not found. Run: cat /proc/devices | grep icc -> sudo mknod /dev/icc c <major> 0") | |
| if reply_buf[0] != 0: | |
| raise ICCError(f"LED command rejected: status=0x{reply_buf[0]:02X}") | |
| return True | |
| def set_led(color_name): | |
| if color_name not in COLOR_TABLE: | |
| raise ValueError(f"Unknown color '{color_name}'. Valid: {', '.join(sorted(COLOR_TABLE))}") | |
| blue_val, white_val, orange_prog = COLOR_TABLE[color_name] | |
| return _send_payload(build_led_payload(blue_val, white_val, orange_prog)) | |
| def set_led_custom(blue_val, white_val, orange_program=ORANGE_OFF): | |
| return _send_payload(build_led_payload(blue_val, white_val, orange_program)) | |
| if __name__ == '__main__': | |
| PROG_NAMES = { | |
| bytes(ORANGE_OFF): 'ORANGE_OFF', | |
| bytes(ORANGE_ON): 'ORANGE_ON', | |
| bytes(ORANGE_COMBINED): 'ORANGE_COMBINED', | |
| bytes(ORANGE_PULSATE): 'ORANGE_PULSATE', | |
| } | |
| if len(sys.argv) == 2 and sys.argv[1] == 'list': | |
| for name in sorted(COLOR_TABLE): | |
| bv, wv, op = COLOR_TABLE[name] | |
| print(f" {name:<22s} blue=0x{bv:02X} white=0x{wv:02X} orange={PROG_NAMES.get(bytes(op), '?')}") | |
| sys.exit(0) | |
| if len(sys.argv) == 2: | |
| set_led(sys.argv[1]) | |
| print(f"LED set to: {sys.argv[1]}") | |
| sys.exit(0) | |
| print(f"Usage: python {sys.argv[0]} <color>") | |
| print(f" python {sys.argv[0]} list") | |
| sys.exit(1) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment