Changing the display brightness via software is really difficult to do from a Raspberry Pi. It works with some selected displays, but for the large majority, it doesn’t. But there is a workaround.

With a digital picture frame, you may want to automatically dim the screen a little bit in the evening so that it doesn’t shine too bright in the room.

Fortunately, this functionality is built into the PictureFrame image viewer.

This article describes the pi3d PictureFrame way of doing it. If you run picframe3 on a Pi 4 or Pi 5, the idea is the same but the topic names and the automation are different — there is a section on that at the end.

How it works

In my Raspberry Pi digital picture frame setup tutorial, I mention the Pi3D PictureFrame image viewer, which produces those wonderful crossfading image transitions.

It turns out that the same image algorithms can be used to reduce the brightness of an image.

The brightness can be controlled via MQTT or Home Assistant, which means that you can remote control it from mobile devices or simply through a time-controlled Python script.

In this article, I will show you how to do it either way.

Remote control via MQTT

The new parameter to control the brightness variable is unsurprisingly called “brightness”.

The payload value can range from “0.0” (nothing to see here) to “1.0” (full brightness).

So, if your device ID is called “picframe”, and you wanted to dim your screen to 80%, you would send a message like

mosquitto_pub -h localhost -t picframe/brightness -m "0.8"

If you want to know how this works with Apple devices, check here, for Android, here.

Control the brightness automatically with sunrise/sunset

Rather than manually changing the screen’s brightness, in most cases, an automation will be fine for most of the cases.

This is easy to do with a Python script.

First install the astral package. Since Bookworm, Raspberry Pi OS refuses a system-wide pip install — it answers with error: externally-managed-environment — so either take the packaged version:

sudo apt install python3-astral python3-paho-mqtt python3-tz

or install into a virtual environment and run the script with that environment’s Python.

Create a new file and paste this code. Save as “brightness_automation.py”.

#!/usr/bin/python3
import logging
import time
import traceback
from datetime import datetime
import paho.mqtt.client as mqtt
from astral import LocationInfo
from astral.sun import sun
from pytz import timezone

logging.basicConfig(filename="log.txt",format="%(asctime)s-%(levelname)s-%(message)s",level=logging.DEBUG)

MQTT_HOST="localhost" #enter your MQTT server here
MQTT_PORT=1883
#MQTT_USER=""
#MQTT_PASWD=""

completed=False
connected=False

def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected. %s." % (reason_code))
    global connected
    connected=True
    update_brightness()
    
def update_brightness():
    
    city = LocationInfo(name="Bad Soden am Taunus", region="Germany", timezone="Europe/Berlin", latitude=50.1420634310433, longitude=8.496851921081545) #enter your location info here
    c_data = sun(city.observer, tzinfo=city.timezone)

    dawn=str(c_data["dawn"])
    sunrise=str(c_data["sunrise"])
    sunset=str(c_data["sunset"])
    dusk=str(c_data["dusk"])
    current_local_time=datetime.now(tz=timezone(city.timezone))

    print("Current Time: ",current_local_time)
    print("Dawn: " + dawn)
    print("Sunrise: " + sunrise)
    print("Sunset: " + sunset)
    print("Dusk: " + dusk)

    dawn=datetime.fromisoformat(dawn)
    sunrise=datetime.fromisoformat(sunrise)
    sunset=datetime.fromisoformat(sunset)
    dusk=datetime.fromisoformat(dusk)

    bright_val=1.0

    if current_local_time>=dawn and current_local_time<sunrise: #this is where you adjust your brightness settings depending on sunrise and sunset
        bright_val=0.9
    elif current_local_time>=sunrise and current_local_time<sunset:
        bright_val=1.0
    elif current_local_time>=sunset and current_local_time<dusk:
        bright_val=0.9
    else:
        bright_val=0.8

    print("Brightness: ",bright_val)
    # publish screen brightness value
    client.publish("picframe/brightness", bright_val, qos=0, retain=True)
    global completed
    completed=True

if __name__ == "__main__":
    try:
        # MQTT Broker
        client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2)
        client.on_connect=on_connect
        client.username_pw_set(username=MQTT_USER,password=MQTT_PASWD)
        client.connect(host=MQTT_HOST,port=MQTT_PORT,keepalive=60)
        client.loop_start()
        while completed!=True:
            time.sleep(0.01)
            if not connected:
                print(".",end="")
    except:
        traceback.print_exc()
        logging.error("Error",exc_info=True)

Two things to watch in that script. paho-mqtt reached version 2 in 2024 and changed both of these: mqtt.Client() now has to be told which callback API you are writing against, which is the CallbackAPIVersion.VERSION2 argument, and on_connect takes five arguments instead of four. The version above is the 2.x form; on an old 1.x install it is the other way round.

The second is client.username_pw_set(username=MQTT_USER,password=MQTT_PASWD). Those two variables are commented out at the top of the script, because my broker has no password. If yours has none either, comment that line out as well, or the script stops with a NameError before it ever connects.

Adjust the values in your script as it works best with your screen and the room environment.

To start it automatically at boot and keep it running in the background, create a service with systemd.

Enter

sudo nano /etc/systemd/system/brightness_control.service

Paste the following text into the editor window

[Unit]
Description=Control display brightness automatically
After=multi-user.target

[Service]
Type=idle
User=pi
ExecStart=/usr/bin/python3 /home/pi/brightness_automation.py
Restart=always
RestartSec=300

[Install]
WantedBy=multi-user.target

Save and close.

Now change the file permissions with

sudo chmod 644 /etc/systemd/system/brightness_control.service

and update the services with

sudo systemctl daemon-reload
sudo systemctl enable brightness_control.service

Reboot.

Raspberry Pi OS has had no default pi user since 2022, so put your own user name and your own home folder in the two lines above. The script talks to a broker over the network and needs nothing privileged, so there is no reason to run it as root.

The same thing on picframe3

picframe3 also dims in software, so any monitor works, but the plumbing is different.

For a fixed evening dim there is no script at all — it is a setting:

power:
  dim_schedule:
    "20:00-23:00": 0.45

For a remote command, the topic is picframe/picframe/brightness/set and the payload is 0–255, the way Home Assistant expects a light. The frame also announces itself to Home Assistant by discovery and turns up there as a light entity with on/off and a brightness slider, which means a sunrise/sunset automation can be built on Home Assistant’s own sun triggers without this Python script, this broker call or this systemd unit.

Conclusion

Being able to soft-control your screen’s brightness through the Pi3D image viewer allows you to hide any monitor controls that you would otherwise need access to.

You could obviously also attach a brightness sensor, but I didn’t want to drill a hole in the frame. But if someone has a great idea how to do this without showing a fat sensor on the front of the frame, please contact me.

Thank You, Paddy, for a great addition to the PictureFrame script.