From ac2f35eadfd500e87c7dcc356b4fea3165206613 Mon Sep 17 00:00:00 2001 From: p1r473 Date: Wed, 19 Mar 2025 09:58:01 -0400 Subject: [PATCH 1/2] Update cli.py --- rpi_backlight/cli.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/rpi_backlight/cli.py b/rpi_backlight/cli.py index c65e1ba..18bfb89 100644 --- a/rpi_backlight/cli.py +++ b/rpi_backlight/cli.py @@ -102,8 +102,12 @@ def main(): "-b/--set-brightness must be used without other options except for -d/--duration" ) # backlight.fade context manager can be used always as args.fade defaults to zero + if (backlight.power is False) and (args.set_brightness > 0): + backlight.power = True with backlight.fade(duration=args.duration): backlight.brightness = args.set_brightness + if (backlight.power is True) and (args.set_brightness == 0): + backlight.power = False return if args.set_power: @@ -122,6 +126,19 @@ def main(): backlight.power = True with backlight.fade(duration=args.duration): backlight.brightness = 100 + elif args.set_power == "on": + # Ensure brightness is 0 when we turn the display on + backlight.brightness = 0 + if args.board_type == "raspberry-pi": + backlight.power = True + with backlight.fade(duration=args.duration): + backlight.brightness = 100 + elif args.set_power == "off": + if backlight.power: + with backlight.fade(duration=args.duration): + backlight.brightness = 0 + if args.board_type == "raspberry-pi": + backlight.power = False else: backlight.power = True if args.set_power == "on" else False return From daca902145e2975b4db402bb527dd1638cf37cd2 Mon Sep 17 00:00:00 2001 From: p1r473 Date: Wed, 21 Jan 2026 17:24:30 -0500 Subject: [PATCH 2/2] Auto-detect functional backlight sysfs device on Raspberry Pi On systems exposing multiple /sys/class/backlight entries (e.g. KMS/DSI on newer Raspberry Pi models), the previous name-based selection could pick a non-functional device and fail with EINVAL/EIO. Select the backlight device by probing for one that accepts a brightness write, falling back to the previous *-0045 / rpi_backlight logic if none succeed. This makes device selection robust across different kernels, display revisions, and overlays without hardcoding bus numbers or model checks. Also improve CLI input validation by rejecting "on"/"off" as SYSFS_PATH and guiding users to the proper -p flag usage. --- rpi_backlight/__init__.py | 59 +++++++++++++++++++++++++++++++++++++-- rpi_backlight/cli.py | 5 ++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/rpi_backlight/__init__.py b/rpi_backlight/__init__.py index 1883241..bc1525a 100644 --- a/rpi_backlight/__init__.py +++ b/rpi_backlight/__init__.py @@ -29,7 +29,9 @@ class BoardType(Enum): _BACKLIGHT_SYSFS_PATHS = { - BoardType.RASPBERRY_PI: next(iglob("/sys/class/backlight/*-0045/"), "/sys/class/backlight/rpi_backlight/"), + BoardType.RASPBERRY_PI: next( + iglob("/sys/class/backlight/*-0045/"), "/sys/class/backlight/rpi_backlight/" + ), BoardType.TINKER_BOARD: "/sys/devices/platform/ff150000.i2c/i2c-3/3-0045/", BoardType.TINKER_BOARD_2: "/sys/devices/platform/ff3e0000.i2c/i2c-8/8-0045/", BoardType.GENERIC: "/sys/class/backlight/backlight/", @@ -38,6 +40,56 @@ class BoardType(Enum): _EMULATOR_MAGIC_STRING = ":emulator:" +def _detect_working_rpi_backlight_sysfs_path() -> Path: + """ + Pick a working backlight device on systems that expose multiple + /sys/class/backlight/* entries (e.g. Pi 5 + KMS/DSI). + We probe by writing the current brightness value back to 'brightness'. + """ + base = Path("/sys/class/backlight") + if not base.exists(): + return Path("/sys/class/backlight/rpi_backlight/") + + try: + candidates = sorted(p for p in base.iterdir() if p.is_dir()) + except OSError: + return Path( + next( + iglob("/sys/class/backlight/*-0045/"), + "/sys/class/backlight/rpi_backlight/", + ) + ) + + def _read_int(path: Path): + try: + return int(path.read_text()) + except Exception: + return None + + for p in candidates: + brightness = p / "brightness" + if not brightness.exists(): + continue + + current = _read_int(p / "actual_brightness") + if current is None: + current = _read_int(brightness) + if current is None: + continue + + try: + brightness.write_text(str(current)) + return p + except OSError as e: + if e.errno in (errno.EINVAL, errno.EIO, getattr(errno, "EREMOTEIO", 121)): + continue + if e.errno == errno.EPERM: + raise + continue + + return Path("/sys/class/backlight/rpi_backlight/") + + def _permission_denied() -> None: raise PermissionError( "You must either run this program as root or change the permissions " @@ -60,7 +112,10 @@ def __init__( ) if not backlight_sysfs_path: - backlight_sysfs_path = _BACKLIGHT_SYSFS_PATHS[board_type] + if board_type == BoardType.RASPBERRY_PI: + backlight_sysfs_path = str(_detect_working_rpi_backlight_sysfs_path()) + else: + backlight_sysfs_path = _BACKLIGHT_SYSFS_PATHS[board_type] elif backlight_sysfs_path == _EMULATOR_MAGIC_STRING: if not _EMULATOR_SYSFS_TMP_FILE_PATH.exists(): raise RuntimeError( diff --git a/rpi_backlight/cli.py b/rpi_backlight/cli.py index 18bfb89..c1f9251 100644 --- a/rpi_backlight/cli.py +++ b/rpi_backlight/cli.py @@ -77,6 +77,11 @@ def main(): parser = _create_argument_parser() args = parser.parse_args() + if args.sysfs_path in ("on", "off"): + parser.error( + "invalid SYSFS_PATH. Use -p on/off (or -p toggle) instead of 'rpi-backlight on|off'." + ) + backlight = Backlight( board_type=STRING_TO_BOARD_TYPE[args.board_type], backlight_sysfs_path=args.sysfs_path,