This guide explains how to set up OBS Studio to launch on startup and run a background Python watchdog script that listens for Arduino serial commands to toggle projectors.
Prerequisites
OBS Studio installed.
obs-websocket enabled in OBS (Tools > obs-websocket Settings).
Python libraries:
pip install obsws-python pyserial.User Groups: Ensure your user can access serial ports:
sudo usermod -a -G dialout $USER(Log out and back in after running).
1. Create the OBS Startup Service
We use a systemd user service to ensure OBS starts after the graphical interface is ready and restarts automatically if it crashes.
File: ~/.config/systemd/user/obs-autostart.service
[Unit]
Description=OBS Startup Projector
After=graphical-session.target
[Service]
Type=simple
ExecStart=/usr/bin/obs --scene "Scene" --startprojector preview --projector-monitor 0 --minimize-to-tray
Restart=always
RestartSec=5
[Install]
WantedBy=graphical-session.target
2. Create the Python Watchdog Service
This service runs your obsarduino.py script. It includes environment variables necessary for Python to communicate with the Wayland display and KDE's shortcut system (qdbus).
File: ~/.config/systemd/user/obs-python.service
[Unit]
Description=OBS Arduino Python Script
# Wait for OBS to be up before starting the script
After=obs-autostart.service
[Service]
Type=simple
WorkingDirectory=%h/Documents
ExecStart=/usr/bin/python3 %h/Documents/obsarduino.py
# Critical for Wayland/KDE API access
Environment=DISPLAY=:0
Environment=XDG_RUNTIME_DIR=/run/user/1000
Environment=XDG_SESSION_TYPE=wayland
Restart=always
RestartSec=5
[Install]
# Using default.target avoids "ordering cycles" with the graphical session
WantedBy=default.target
Note:
%his a shortcut for your home directory. If your User ID is not1000, check it by runningid -uand update theXDG_RUNTIME_DIRaccordingly.
3. The Python Logic (obsarduino.py)
Ensure your script is saved in ~/Documents/obsarduino.py. The script handles:
Opening the Projector: Via
obsws_python.Closing the Projector: Via
qdbusby triggering the "Window Close" shortcut in KDE.
import serial
import obsws_python as obs
import time
import subprocess
SERIAL_PORT = '/dev/ttyACM0'
BAUD_RATE = 9600
cl = obs.ReqClient(host="localhost", port=4455, auth="")
def close_projector_window():
try:
# Triggers KDE global 'Window Close' shortcut
subprocess.run([
"qdbus", "org.kde.kglobalaccel", "/component/kwin",
"org.kde.kglobalaccel.Component.invokeShortcut", "Window Close"
], check=True)
except Exception as e:
print(f"Error closing window: {e}")
def handle_projector(val):
try:
if val == "1":
cl.open_video_mix_projector(
video_mix_type="OBS_WEBSOCKET_VIDEO_MIX_TYPE_PROGRAM",
monitor_index=0
)
elif val == "0":
time.sleep(0.2)
close_projector_window()
except Exception as e:
print(f"OBS Error: {e}")
try:
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
time.sleep(2)
except Exception:
exit() # Systemd will restart the script automatically
while True:
if ser.in_waiting > 0:
line = ser.readline().decode('utf-8').strip()
if line in ["1", "0"]:
handle_projector(line)
4. Activation and Commands
Run these commands to register and start your new automation:
Troubleshooting Tips
Ordering Cycle Error: If the services refuse to start due to a "cycle," ensure the Python script
[Install]section usesWantedBy=default.targetand notgraphical-session.target.Arduino Path: If your Arduino changes address (e.g., to
/dev/ttyUSB0), update theSERIAL_PORTvariable in your.pyfile and run the restart command.KDE Shortcuts: Ensure your KDE system actually has a "Window Close" shortcut defined (default is usually
Alt+F4).
