Adafruit_Learning_System_Gu.../Raspberry_Pi_Braincraft_Radio/setup-kiosk.sh
2021-01-07 17:13:31 -08:00

341 lines
9.8 KiB
Bash

# Inspired by https://pimylifeup.com/raspberry-pi-kiosk/
# Start with a raspberry pi "full" installation that boots to desktop and
# can run chromium browser.
#
# Follow braincraft setup instructions sections:
# - raspberry pi setup
# - blinka setup
# - audio setup
# - fan service setup (optional)
#
# Now run this script, restart, and satisfy yourself that the player works
# properly using a HDMI monitor. Troubleshooting is easier at this point!
#
# Now run the braincraft setup setps:
# - display module install
# - enable "overlay mode" in raspi-config so that it's safe to just power off
# the pi anytime
#
# Before working with the system, remember to
# - disable "overlay mode"
# - optionally disable the display module to get a regular desktop back
cd $HOME
sudo apt-get install -y xdotool unclutter sed
cat > kiosk.sh <<EOF
#!/bin/bash
set -x
DISPLAY=:0; export DISPLAY
xset s noblank
xset s off
xset -dpms
unclutter -idle 0.5 -root &
sudo python3 /home/pi/braincraftKeys.py &
while true; do
# Set speaker volume down -7dB (~40% volume)
amixer -c 2 -- sset Speaker -7dB
amixer -c 2 -- sset Headphone -7dB
# Wait for network to come up
while ! ping -c 1 youtube.com; do sleep 1; done
sed -i 's/"exited_cleanly":false/"exited_cleanly":true/' /home/pi/.config/chromium/Default/Preferences
sed -i 's/"exit_type":"Crashed"/"exit_type":"Normal"/' /home/pi/.config/chromium/Default/Preferences
/usr/bin/chromium-browser --autoplay-policy=no-user-gesture-required --noerrdialogs --disable-infobars --kiosk --remote-debugging-port=9992 file:///home/pi/kioskvideo.html &
sleep 15
xdotool keydown f; xdotool keyup f
wait %%
done
EOF
chmod +x kiosk.sh
cat > braincraftKeys.py <<"EOF"
#!/usr/bin/env python3
# Credit to Pimoroni for Picade HAT scripts as starting point.
import time
import signal
import os
import sys
from datetime import datetime
from collections import deque
import re
import fileinput
try:
from evdev import uinput, UInput, ecodes as e
except ImportError:
exit("This library requires the evdev module\nInstall with: sudo pip install evdev")
import digitalio
import board
def detect_rotation():
rotation_pattern = "^dtoverlay=drm-minipitft13,rotation=([0-9]+)"
hdmi_pattern = "^display_hdmi_rotate=([0-9])"
param_rotation = None
param_hdmi = None
for line in fileinput.FileInput("/boot/config.txt"):
rotation_match = re.search(rotation_pattern, line)
hdmi_match = re.search(hdmi_pattern, line)
if rotation_match:
param_rotation = int(rotation_match.group(1))
if hdmi_match:
param_hdmi = int(hdmi_match.group(1))
if param_rotation is not None and param_hdmi is not None:
break
if param_hdmi == 3:
return 180
if param_hdmi == 2:
return 270
if param_hdmi == 1:
return 0
return param_rotation
arrow_pins = deque((
board.D23, # up
board.D24, # right
board.D27, # down
board.D22, # left
))
arrow_pins.rotate((detect_rotation() // 90) - 1)
DEBUG = False
BOUNCE_TIME = 0.01 # Debounce time in seconds
POWEROFF_TIMEOUT = 5
KEYS= [ # EDIT KEYCODES IN THIS TABLE TO YOUR PREFERENCES:
# See /usr/include/linux/input.h for keycode names
# Keyboard Action (tuple = press together, no repeat)
(board.D17, (e.KEY_K,)), # button - play/pause
(board.D16, (e.KEY_LEFTCTRL, e.KEY_R)), # push stick - reload
(arrow_pins[3], (e.KEY_LEFTSHIFT, e.KEY_P)), # left - previous in playlist
(arrow_pins[1], (e.KEY_LEFTSHIFT, e.KEY_N)), # right - next in playlist
(arrow_pins[0], e.KEY_EQUAL), # up - volume up
(arrow_pins[2], e.KEY_MINUS), # down - volume down
]
key_values = set()
for pin, action in KEYS:
if isinstance(action, int):
key_values.add(action)
else:
key_values.update(action)
class KeyManager:
def __init__(self, pin, action):
self.pin = digitalio.DigitalInOut(pin)
self.pin.switch_to_output(digitalio.Pull.UP)
self.action = action
self.old_value = False
@property
def value(self):
return not self.pin.value # buttons are active-low
def tick(self):
value = self.value
if value != self.old_value:
if isinstance(self.action, int):
ui.write(e.EV_KEY, self.action, value)
ui.syn()
elif value:
for key in self.action:
ui.write(e.EV_KEY, key, 1)
ui.syn()
for key in self.action[::-1]:
ui.write(e.EV_KEY, key, 0)
ui.syn()
self.old_value = value
class ShutdownManager:
def __init__(self, manager):
self.manager = manager
self.old_value = False
self.press_start = 0
@property
def value(self):
return self.manager.value
def tick(self):
now = time.time()
value = self.value
if value:
if self.old_value:
if now > self.press_start + POWEROFF_TIMEOUT:
os.system("env DISPLAY=:0 XAUTHORITY=/home/pi/.Xauthority xset dpms force off")
os.system("sudo poweroff")
else:
self.press_start = now
self.old_value = value
managers = [KeyManager(k, v) for k, v in KEYS]
managers.append(ShutdownManager(managers[0]))
os.system("sudo modprobe uinput")
try:
ui = UInput({e.EV_KEY: key_values}, name="braincraft-hat", bustype=e.BUS_USB)
except uinput.UInputError as e:
sys.stdout.write(repr(e))
sys.stdout.write("Have you tried running as root? sudo {}".format(sys.argv[0]))
sys.exit(0)
def log(msg):
sys.stdout.write(str(datetime.now()))
sys.stdout.write(": ")
sys.stdout.write(msg)
sys.stdout.write("\n")
sys.stdout.flush()
while True:
for manager in managers:
manager.tick()
time.sleep(BOUNCE_TIME)
EOF
chmod +x braincraftKeys.py
cat > kiosk.service <<EOF
[Unit]
Description=Chromium Kiosk
Wants=graphical.target
After=graphical.target
[Service]
Environment=DISPLAY=:0.0
Environment=XAUTHORITY=/home/pi/.Xauthority
Type=simple
ExecStart=/bin/bash /home/pi/kiosk.sh
Restart=on-abort
User=pi
Group=pi
[Install]
WantedBy=graphical.target
EOF
sudo mv kiosk.service /etc/systemd/system/
sudo systemctl enable kiosk.service
sudo systemctl start kiosk.service
cat > kioskvideo.html <<"EOF"
<html>
<head>
<title>Braincraft Hat - lofi hip hop radio - betas to relax/study to</title>
<!--
Embed the youtube video for best viewing on the square (virtual 480x480)
Adafruit Braincraft Hat and react to (virtual) keystrokes to control playback
https://developers.google.com/youtube/iframe_api_reference
-->
</head>
<body>
<div style="position:absolute; top:0px; left:0px; width:480px; height:480px; overflow:hidden">
<iframe id="player" type="text/html" width="854" height="480" style="margin-left: -68px" src="http://www.youtube.com/embed/5qap5aO4i9A?enablejsapi=1&autoplay=1" frameborder="0" allow="autoplay"></iframe></div>
<div style="height:480px"></div>
NOTE: Chromium must be launched with --autoplay-policy=no-user-gesture-required or the video will not autoplay and the play/pause keystrokes will not work properly. Clicking in the video will also affect keystroke functionality!
<script type="text/javascript">
// playlist[0] needs to match the initial iframe src to work right
var playlist = [
'5qap5aO4i9A', // study
'DWcJFNfaw9c', // sleep
'5yx6BWlEVcY', // jazzy
]
// in unboxed mode, pan to a different horizontal position depending on video
var pan = [
-68, -208, -187
]
var playlist_idx = 0;
var tag = document.createElement('script');
tag.id = 'iframe-demo';
tag.src = 'https://www.youtube.com/iframe_api';
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
var player;
var unboxed=true;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
events: {
'onReady': onPlayerReady,
}
});
}
function onPlayerReady(event) {
event.target.setVolume(40);
}
function fixPan() {
if(unboxed) {
document.getElementById('player').width=854
document.getElementById('player').style="margin-left:"+pan[playlist_idx]+"px"
} else {
document.getElementById('player').width=480
document.getElementById('player').style=""
}
}
document.addEventListener('keydown', (event) => {
if (event.ctrlKey || event.metaKey || event.altKey) {
return
}
switch(event.key) {
case 'k': // toggle play-pause
if(player.getPlayerState() == 1) {
console.log("pause")
player.pauseVideo()
} else {
player.playVideo()
console.log("play")
}
break;
case 'p': // unconditional pause
player.pauseVideo()
break;
case '[': // previous video in playlist
case 'P': // previous video in playlist
playlist_idx = (playlist_idx + playlist.length - 1) % playlist.length
player.loadVideoById({'videoId' : playlist[playlist_idx]});
fixPan()
break;
case ']': // previous video in playlist
case 'N': // next video in playlist
playlist_idx = (playlist_idx + 1) % playlist.length
player.loadVideoById({'videoId' : playlist[playlist_idx]});
fixPan()
break;
case '+': // volume up
case '=': // volume up
player.setVolume(Math.min(100, player.getVolume() + 10));
break;
case '-': // volume down
player.setVolume(Math.max(0, player.getVolume() - 10));
break;
case 'm': // toggle mute
if(player.isMuted()) {
player.unMute()
} else {
player.mute()
}
break;
case 'l':
unboxed = !unboxed;
fixPan()
}
}, false)
</script>
</body>
EOF