geerlingguy/pi-kiosk

Allow for screen to dim after a certain amount of time

Opened this issue · 19 comments

For this feature to be useful:

  1. A timeout could be configured, after which (if there's no activity), the screen could dim to a preconfigured level (e.g. 10%).
  2. Tapping anywhere on the display while the screen is dimmed would first 'wake' the display and bring it to full brightness, then after that's done, the user could touch elements on the display.

That latter point would require a bit more work—not sure if the Pi Touch Display 2 has integration into Linux itself to have some kind of automatic screen dimming support out of the box (or failing that, turn it off or dim it to 0%)...

Note: There is built-in screen blanking functionality (settings for it are in the Pi Configuration app, or in raspi-config), but it doesn't seem to include much configurability... or the option of making the screen go dim instead of off.

Might be configurable with xset? (Does that work under Wayland?)

pi@pi-touch:~ $ DISPLAY=:0 xset q
Keyboard Control:
  auto repeat:  on    key click percent:  0    LED mask:  00000000
  XKB indicators:
    00: Caps Lock: off
  auto repeat delay:  600    repeat rate:  25
  auto repeating keys:  00ffffffdffffbbf
                        fadfffefffedffff
                        9fffffffffffffff
                        fff7ffffffffffff
  bell percent:  50    bell pitch:  400    bell duration:  100
Pointer Control:
  acceleration:  2/1    threshold:  4
Screen Saver:
  prefer blanking:  yes    allow exposures:  yes
  timeout:  0    cycle:  0
Colors:
  default colormap:  0x2b    BlackPixel:  0x0    WhitePixel:  0xffffff
Font Path:
  /usr/share/fonts/X11/Type1,built-ins
DPMS (Energy Star):
  Server does not have the DPMS Extension

See this StackExchange answer.

I did find the command swayidle -w timeout 600 'wlopm --off \*' resume 'wlopm --on \*' & tucked away inside ~/.config/labwc/autostart. If the timeout is 10 minutes, that may be where it's set...

Indeed, changing that value changes the screen timeout, and persists across reboots.

HOWEVER, I did a quick test to see if a tap on the blank screen just wakes the screen, or is sent through as a normal 'click'. And annoyingly, it's sent through as a 'click'.

So 'tap to wake' is not really an option, at least unless you designate a 'safe area' on your UI that you can tap to wake... or if you design a circuit that jiggles a mouse input when vibration is detected, and you tap the side of the display, something like that :/

Demo:

tap-to-wake-screen.MOV

Going to ask on the forums: 'Tap to wake' Touch Display is risky

From @6by9 on the Pi Forums:

I suspect this is will be common across any touch device under labwc or Wayfire, and wants to be masked at that level.
The tricky part is likely to be that a single touch almost always ends up moving slightly, and is registering effectively "button down". So effectively you've had a first "movement" that is potentially ignored as we unblank, but unlike a mouse you've now got more "down" events triggering things. So you actually want to ignore events until the first "up" response has been received. Not my area of knowledge for how that is implemented.

So it sounds like a fix could be made, but it may be a little complicated. It'd be awesome to have that fix on the OS level so I don't have to do some weird shenanigans with a system service.

For now, I'm just touching in a part of the screen where I know there are no buttons.

It's probably broken now that everything is wayland, but you can take a look at my fork of this pi-touchscreen-dimmer, maybe just for some more inspiration. I've been using it to get auto dimming on my pi for years now https://github.com/gabeklavans/pi-touchscreen-dimmer. It doesn't quite achieve your 2. goal because the first tap both "wakes" and registers as a normal tap, but it's close.

Just tested it on a latest raspberry pi OS with wayland, dimming works, but the touch event doesn't. Might need more tinkering. I think any of the logic there can be lifted for this project pretty easily.

It's more of a workaround than fix but I've setup an automation in Home Assistant that will turn the display on/off when motion is detected. It doesn't fix the issue however does mean that when someone's in the room and may want to use the panel the display is on so they don't accidentally press a button when waking the screen up.

I'm using a Pi 3B with Bookworm 64bit installed and the original Raspberry Pi display so there may be some difference.

You can turn the backlight on or off by changing the value in /sys/class/backlight/10-0045/bl_power
Note: the 10-0045 may change with the version/type of screen along with the values of bl_power

For my display setting bl_power to 0 = on and 4 = off. Although from testing it appears that as long as the value is set to anything other than 0 it turns the display off but not 100% sure if its doing something else in the background with the value. I established my values by turning on screen blanking in raspi-config and checking the value when it turned the screen off.

To setup the automation I setup passwordless SSH authentication between Home Assistant and the Pi.

On the Pi I then created two scripts and changed the permissions for bl_power:

sudo chmod 777 /sys/class/backlight/10-0045/bl_power

Backlight On

#!/bin/bash
echo 0 > /sys/class/backlight/10-0045/bl_power

Backlight Off

#!/bin/bash
echo 4 > /sys/class/backlight/10-0045/bl_power

I had to create them as scripts on the Pi as running the command directly through Home Assistant would fail whereas calling the script didn't.

Then I setup shell commands in Home Assistant by adding the below to my configuration.yaml

shell_command:
    cp_backlight_off: ssh -i /config/.ssh/id_rsa -o 'StrictHostKeyChecking=no' {USER}@192.168.1.127 /home/{USER}/kiosk/backlight_off.sh
    cp_backlight_on: ssh -i /config/.ssh/id_rsa -o 'StrictHostKeyChecking=no' {USER}@192.168.1.127 /home/{USER}/kiosk/backlight_on.sh

You can run the command directly from the Home Assistant terminal with the -o 'StrictHostKeyChecking=no' option however when run as part of an automation it is required (at least I couldn't figure out how to get round it).

After a restart of HA to get the shell commands to appear I created two automations.

alias: Control Panel Backlight On
description: Turn on the control panel backlight
triggers:
  - type: motion
    device_id: 5edc0dfac74a3d66ca014d9842f949bc
    entity_id: 6d291b8c0219488b10f4d1bca8271668
    domain: binary_sensor
    trigger: device
conditions: []
actions:
  - action: shell_command.cp_backlight_on
    metadata: {}
    data: {}
mode: single
alias: Control Panel Backlight Off
description: Turn off the control panel backlight
triggers:
  - type: no_motion
    device_id: 5edc0dfac74a3d66ca014d9842f949bc
    entity_id: 6d291b8c0219488b10f4d1bca8271668
    domain: binary_sensor
    trigger: device
conditions: []
actions:
  - action: shell_command.cp_backlight_off
    metadata: {}
    data: {}
mode: single

Now whenever motion is detected the screen comes on and goes off when people leave the room. Like I said it doesn't fix the issue but depending on use case it may do the job. A possible next step would be to present the backlight controls to HA as entities that could then be used as part of a normal motion-activated blueprint rather than create two seperate automations.

Something like this might be just the ticket.

Couple it with radar-based presence sensors (I've been testing some Everything Presence sensors around the studio), and you could have the screen only come on while someone is detected in the room a bit more reliably than standard PIR sensors too.

Otherwise I could tie it into my Simplisafe automation (when 'Off', turn on the display...).

Indeed, one other thing I've found is that the permissions on bl_power appear to reset after a reboot of the kiosk pi so I might add a cronjob to it to open the permissions up after a reboot but other than that it's been working as expected so far.

I am using a rpi 4 and the older screen as well as the official case for both. (hacked a bit to hold a pwm fan) I think I am going to use one of two methods...

  1. It's manual and a bit crude but I think i'm simply going to put a tiny push button (that has a light glowing led) on the left hand side of the screen, in the blank section that is suppose to be used for a camera. I plan to have the button press simply trigger a xset dpms force off or xset dpms force on

  2. Or a more automatic way (that is already integrated into my home assistant)... the "24GHz mmWave Sensor - Human Static Presence Module Lite" $6.90 sensor from seeedstudio. SImply set the screen to come on if the room is occupied and to turn it off if it isn't.

Neither option is fantastic but it should get me by in the meantime.

Could you use an HC-SR04 distance sensor, and rather than use tap to wake, just have the display turn on when there's something within x distance? That way, it's not only off when you're away from the building but remains off if you're nowhere near the screen, that might be even more useful than tap to wake.

Swayidle sounds like the right approach. You can execute multiple actions on different timeouts as well. Example

Regarding the wake-up button press also being passed through to Chromium: maybe you could spawn some kind of "lockscreen" which doesn't require a password and just unlocks everything once it receives any input. If its started shortly before disabling the output via wlopm that should eat the wake-up event. In case there is no lockscreen that supports unlock-on-input, something like this might work for you instead:

test_screen_block.py
#!/usr/bin/env python3

import gi
gi.require_version("Gtk", "3.0")
gi.require_version("Gdk", "3.0")
from gi.repository import Gtk, Gdk

class ScreenSaver(Gtk.Window):
	def __init__(self):
		super().__init__()
		self.connect('motion-notify-event', Gtk.main_quit)
		self.connect('button-release-event', Gtk.main_quit)
		self.set_events(Gdk.EventMask.POINTER_MOTION_MASK)
		self.fullscreen()
		self.show()

if __name__ == '__main__':
	win = ScreenSaver()
	Gtk.main()

This seems like a great idea, but apparently we would need to run the following everytime the Pi is rebooted?

sudo chmod 777 /sys/class/backlight/10-0045/bl_power

Or is there a way to make the permission persistent?

6by9 commented

This seems like a great idea, but apparently we would need to run the following everytime the Pi is rebooted?

sudo chmod 777 /sys/class/backlight/10-0045/bl_power

Or is there a way to make the permission persistent?

You need a udev rule if you want to use bl_power and the permissions are wrong.

I think brightness already has one, and could be used for the same purpose (although you need to remember the "on" brightness).
https://github.com/torvalds/linux/blob/master/Documentation/ABI/stable/sysfs-class-backlight

This seems like a great idea, but apparently we would need to run the following everytime the Pi is rebooted?

sudo chmod 777 /sys/class/backlight/10-0045/bl_power

Or is there a way to make the permission persistent?

You need a udev rule if you want to use bl_power and the permissions are wrong.

I think brightness already has one, and could be used for the same purpose (although you need to remember the "on" brightness). https://github.com/torvalds/linux/blob/master/Documentation/ABI/stable/sysfs-class-backlight

Thanks for your input!

I'm a bit of a newbie in Linux. I found out that I could possibly create a udev rule, but couldn't figure out how to do it for this specific permission(didn't find an example for this parameter). Unfortunately, I also can't figure out how to leverage the info in the link you provided.
Can anyone help with instructions or a link that explains how to do it?

Thanks in advance

6by9 commented

If you look at /etc/udev/rules.d/99-com.rules, you'll see a number of lines at the start

SUBSYSTEM=="input", GROUP="input", MODE="0660"
SUBSYSTEM=="i2c-dev", GROUP="i2c", MODE="0660"
SUBSYSTEM=="spidev", GROUP="spi", MODE="0660"
SUBSYSTEM=="*gpiomem*", GROUP="gpio", MODE="0660"
SUBSYSTEM=="rpivid-*", GROUP="video", MODE="0660"

SUBSYSTEM is probably "backlight" (you can use udevadm to confirm).
Best to add it to the "video" group, as that is what is used for the /dev/dri display pipeline nodes.
Mode is the permissions that you want, which is probably 0664 rather than 777 (you don't need to be able to execute it).

I don’t know whether I’m missing something but is it not just simplest to put a cronjob in that changes the permissions on each reboot?

All I’ve done is add the below to roots crontab and it’s doing the job.

@reboot chmod 777 /sys/class/backlight/10-0045/bl_power

6by9 commented

I don’t know whether I’m missing something but is it not just simplest to put a cronjob in that changes the permissions on each reboot?

Using cron fails if the module is unloaded and reloaded. That may be an unusual situation, but it's possible.

As I've said to thebishop85, 777 is incorrect for the permissions as you should not be able to execute that device.

I found out that I could possibly create a udev rule, but couldn't figure out how to do it for this specific permission.
Can anyone help with instructions or a link that explains how to do it?

@thebishop85: Something like this will probably work to make the permissions permanent:

echo 'SUBSYSTEM=="backlight", KERNEL=="10-0045", RUN+="/bin/chmod 666 /sys/class/backlight/%k/brightness"' | sudo tee -a /etc/udev/rules.d/backlight-permissions.rules

I use something similar for my touch kiosk project, which also provides a Home Assistant integration to directly control the display brightness (using a dimmable light sensor):

mqtt

HOWEVER, I did a quick test to see if a tap on the blank screen just wakes the screen, or is sent through as a normal 'click'. And annoyingly, it's sent through as a 'click'.

@geerlingguy: I addressed this issue by removing focus from the kiosk window. As a result, the first click will focus the window instead of sending a click command into the webview. However, I still view this as a temporary workaround rather than a proper solution.