Professional broadcast graphics for hockey powered by real-time scoreboard data
🚀 Quick Start • ✨ Features • 📺 Overlays • 🔌 API • 📄 License
Click to expand
Release Date: January 2026
| Change | Description |
|---|---|
| 🔌 Serial Port Control | Connect/Disconnect buttons to release serial port for other applications |
| ⏱️ Manual Clock Mode | Scorebug clock only updates from actual serial data - no auto-countdown |
| 📊 Raw Serial Display | Verbose console shows ALL incoming serial data, not just MP-70 packets |
| 🎨 Modern UI | Refreshed dashboard with glass morphism, gradients, and subtle textures |
| 🔧 Simulation Fix | Simulation mode no longer auto-starts when disabled |
| 🎛️ Simplified Interface | Removed Preview/Live toggle, cleaner controls |
If upgrading from v2.1.0:
cd /path/to/SLAP
git pull
./deploy.py
slap restartSLAP captures real-time game data from Trans-Lux FairPlay MP-70 scoreboard controllers and generates professional NHL-style broadcast graphics via CasparCG or OBS Studio.
┌────────────┐ RS-232 ┌──────────────┐
│ Scorekeeper│───────────────────│ Scoreboard │
│ Console │ │ Display │
│ (MP-70) │ └──────────────┘
└─────┬──────┘
│ RS-232 (sniff)
▼
┌─────────────────────────────────────────────────────────────────────┐
│ SLAP Server │
│ ┌──────────────────┐ ┌──────────────────┐ ┌───────────────┐ │
│ │ Serial Parser │───▶│ Game State │───▶│ AMCP Client │ │
│ │ (MP-70 Protocol)│ │ scores, clock, │ │ │ │
│ └──────────────────┘ │ period, penalties│ └───────┬───────┘ │
│ └────────┬─────────┘ │ │
│ │ │ AMCP │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌───────────────┐ │
│ │ Web Dashboard │ │ CasparCG │ │
│ │ (Socket.IO) │ │ Server │ │
│ └────────┬─────────┘ └───────┬───────┘ │
└───────────────────────────────────┼──────────────────────┼──────────┘
│ │
▼ ▼
┌──────────────┐ ┌─────────────────────┐
│ OBS Studio │◀───│ HTML/CSS/JS │
│ (streaming) │ NDI│ Templates │
└──────────────┘ └─────────────────────┘
Dataflow:
- Scorekeeper operates the MP-70 console during the game
- MP-70 sends game data via RS-232 to the physical scoreboard
- SLAP passively sniffs the RS-232 line (no interference with scoreboard)
- Parser decodes the MP-70 binary protocol (scores, period, clock, penalties)
- AMCP Client sends data updates to CasparCG via AMCP protocol
- CasparCG renders HTML/CSS/JS templates with live data
- Web Dashboard provides real-time monitoring and manual override control
For best performance, run SLAP and CasparCG on the same machine:
CasparCG Machine (runs SLAP):
├── RS-232 USB adapter → MP-70 serial sniff
├── SLAP server (localhost:5000)
├── CasparCG server (localhost:5250)
└── HTML templates served locally (zero latency)
│
│ NDI (network)
▼
OBS Machine (powerful workstation):
├── Receives NDI stream from CasparCG
├── Composites overlays onto camera feeds
└── Outputs final broadcast stream
Why this works best:
- Localhost AMCP = zero network latency for graphics
- Single machine handles capture → parse → render
- OBS stays separate for compositing only
- Serial port directly connected to graphics machine
Tip
After installation, SLAP is controlled via the slap command and available in your start menu!
# Clone the repository
git clone https://github.com/sworrl/SLAP.git
cd SLAP
# Run the installer (handles everything automatically)
chmod +x deploy.py
./deploy.py
# After installation, use the 'slap' command:
slap start # Start the server
slap status # Check if running
slap stop # Stop the server
# Open in browser
# https://slap.localhostNote: The installer automatically:
- Installs all prerequisites (Python, nginx, openssl, etc.)
- Creates a global
slapcommand- Sets up HTTPS with self-signed SSL certificate
- Adds SLAP to your start menu
- Configures auto-start on boot
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
SLAP includes a full suite of NHL-style broadcast overlays, all controllable via the web dashboard or API.
| Overlay | URL | Description |
|---|---|---|
| 🏒 Scorebug | /overlay |
Main game scorebug with scores, clock, period |
| 🚨 Goal Splash | /overlay/goal |
Full-screen goal celebration with confetti |
| 🎯 Shot Counter | /overlay/shots |
Shots on goal tracker |
| ⏱️ Penalty Box | /overlay/penalty |
Detailed penalty info display |
| 👤 Player Card | /overlay/player |
Lower third player spotlight |
| 📊 Period Summary | /overlay/period |
End-of-period stats summary |
| 🎬 Game Intro | /overlay/intro |
Pre-game matchup graphic |
| 🥅 Goalie Stats | /overlay/goalie |
Goalie performance display |
| ⚡ Power Play | /overlay/powerplay |
Power play countdown graphic |
| ⭐ Three Stars | /overlay/stars |
Post-game three stars of the game |
| 🔁 Replay Bug | /overlay/replay |
Flashing replay indicator |
| 📰 Ticker | /overlay/ticker |
Scrolling scores crawl |
Add overlays as HTML templates:
PLAY 1-10 [HTML] "https://slap.localhost/overlay"
PLAY 1-11 [HTML] "https://slap.localhost/overlay/goal"
Add as Browser Source:
- URL:
https://slap.localhost/overlay(orhttp://localhost:9876/overlay) - Width: 1920
- Height: 1080
- Custom CSS: (leave empty)
All overlays respond to Socket.IO events for real-time triggering.
- Python 3.8 or higher
- Linux, macOS, or Windows
# Clone or download SLAP
git clone https://github.com/sworrl/SLAP.git
cd SLAP
# Run the installer
chmod +x deploy.py
./deploy.pyThe installer handles:
- System package installation (Python, nginx, openssl, git)
- Python virtual environment creation
- Dependency installation
- HTTPS/SSL certificate generation
- nginx reverse proxy configuration
- Start menu entry creation
- Systemd service for auto-start
| Command | Description |
|---|---|
slap start |
Start SLAP server |
slap stop |
Stop SLAP server |
slap restart |
Restart SLAP server |
slap status |
Check if running |
slap logs |
Show logs (-f to follow) |
slap config |
View/edit configuration |
slap -update |
Update from GitHub |
slap --help |
Show all commands |
Simulation mode is hidden by default in the WebUI. To enable it:
slap -simulation:enable # Show simulation controls in WebUI
slap -simulation:disable # Hide simulation controls (default)slap -https:setup # Configure HTTPS with nginx and SSL
slap -https:remove # Remove HTTPS configurationslap start --port 9876 # Custom port
slap start --debug # Debug logging
slap -serial:/dev/ttyUSB0 # Set serial port./deploy.py --uninstallNote
SLAP works in simulation mode without any hardware. Only need this for live games!
| Item | Description |
|---|---|
| MP-70 Controller | Trans-Lux FairPlay MP-70, MP-71, MP-72, or MP-73 |
| USB-Serial Adapter | Any RS-232 to USB adapter (FTDI recommended) |
| Serial Cable | DB-9 or appropriate connector for your MP-70 |
- Access the MP-70 setup menu
- Navigate to sport-specific setup
- When prompted "VIDEO CHAR?", answer NO
- This sets RS-232 to ProLine data format
- Verify RS-232 output is enabled
MP-70 RS-232 Port → Serial Cable → USB Adapter → Computer
Linux:
ls /dev/ttyUSB*
# Usually /dev/ttyUSB0macOS:
ls /dev/tty.usb*
# Usually /dev/tty.usbserial-XXXXWindows:
- Open Device Manager
- Look under "Ports (COM & LPT)"
- Usually COM3 or COM4
Edit src/config/default.json:
{
"serial": {
"port": "/dev/ttyUSB0",
"baudrate": 9600
},
"caspar": {
"host": "127.0.0.1",
"port": 5250,
"enabled": true
},
"web": {
"port": 9876
},
"simulator": {
"enabled": false
}
}| Setting | Description |
|---|---|
port |
Serial port path (e.g., /dev/ttyUSB0, COM4) |
baudrate |
Always 9600 for MP-70 |
| Setting | Description |
|---|---|
host |
CasparCG server IP address |
port |
AMCP port (default: 5250) |
enabled |
Set to false to disable CasparCG |
SLAP stores game history, events, and player statistics in a SQLite database. The database is stored outside the source directory for security:
| Platform | Location |
|---|---|
| Linux | ~/.local/share/slap/slap.db |
| macOS | ~/Library/Application Support/slap/slap.db |
| Windows | %LOCALAPPDATA%\slap\slap.db |
Note
The database is automatically created during ./deploy.py install and includes self-healing - if the database becomes corrupted, SLAP will back it up and create a fresh one automatically.
The dashboard at
https://slap.localhost(orhttp://localhost:9876without HTTPS) provides full control over SLAP.
- Live scorebug preview - See exactly what appears on broadcast
- Preview/Live toggle - Switch between simulation and real hardware
- Score controls - Manually adjust scores with +/- buttons
- Goal buttons - Trigger goal animations
- Clock controls - Set period and game time
- Penalty controls - Add 2-minute or 5-minute penalties
- 🚨 Goal Splash - Trigger home/away goal celebrations
- 🔁 Replay Bug - Show/hide replay indicator
- 👤 Player Card - Display player lower thirds with roster lookup
- 🥅 Goalie Stats - Show goalie save percentages
- 📊 Period Summary - End of period stats
- 🎬 Game Intro - Pre-game matchup graphic
- ⭐ Three Stars - Post-game honors
- ⚡ Power Play - Enhanced PP graphic
- 🎯 Shot Counter - Update SOG display
- 📰 Ticker - League scores crawl
- 🎨 Team Customization - Set team names, colors, and logos
- 📋 Roster Manager - Add/edit player names and numbers
- 🖼️ Logo Upload - Upload custom team logos (PNG, SVG, etc.)
- 📡 Serial Port - Configure MP-70 connection
- 🎬 CasparCG control - Start/stop server, connect AMCP
- 📺 OBS control - Start/stop OBS, connect WebSocket
- 🔗 Connection status - Monitor all integrations
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/state |
Get current game state |
POST |
/api/state |
Update game state |
POST |
/api/goal |
Trigger goal event |
POST |
/api/penalty |
Add penalty |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/overlays |
List all available overlays |
POST |
/api/overlay/goal |
Trigger goal splash |
POST |
/api/overlay/player |
Show player card |
POST |
/api/overlay/goalie |
Show goalie stats |
POST |
/api/overlay/period |
Show period summary |
POST |
/api/overlay/intro |
Show game intro |
POST |
/api/overlay/stars |
Show three stars |
POST |
/api/overlay/powerplay |
Show power play graphic |
POST |
/api/overlay/shots |
Update shot counter |
POST |
/api/overlay/replay |
Show replay bug |
POST |
/api/overlay/ticker |
Show scores ticker |
POST |
/api/overlay/{name}/hide |
Hide any overlay |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/roster |
Get all rosters |
GET |
/api/roster/{team} |
Get team roster (home/away) |
POST |
/api/roster/{team} |
Update team roster |
POST |
/api/roster/{team}/player |
Add player to roster |
DELETE |
/api/roster/{team}/player/{number} |
Remove player |
POST |
/api/roster/{team}/player/{number}/stats |
Update player stats |
POST |
/api/roster/reset |
Reset all game stats |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/teams |
Get team configuration |
POST |
/api/teams |
Update team configuration |
GET |
/api/teams/logos |
List available logos |
POST |
/api/teams/logo/upload |
Upload new logo |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/serial/ports |
List available serial ports |
GET |
/api/serial/status |
Get serial connection status |
POST |
/api/serial/config |
Configure serial port |
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/simulator/start |
Start simulator |
POST |
/api/simulator/stop |
Stop simulator |
POST |
/api/simulator/reset |
Reset simulator |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/games |
Get recent games (add ?limit=N) |
POST |
/api/games |
Create new game |
GET |
/api/games/current |
Get current in-progress game |
GET |
/api/games/{id} |
Get specific game |
PUT |
/api/games/{id} |
Update game details |
DELETE |
/api/games/{id} |
Delete game |
POST |
/api/games/{id}/end |
End game (status: final/cancelled) |
GET |
/api/games/{id}/summary |
Get full game summary with events |
GET |
/api/games/{id}/events |
Get game events (add ?type=goal) |
POST |
/api/games/{id}/goal |
Log a goal |
POST |
/api/games/{id}/penalty |
Log a penalty |
POST |
/api/games/{id}/shot |
Log a shot |
| Method | Endpoint | Description |
|---|---|---|
GET |
/api/stats |
Get player stats (add ?season=YYYY&team=X) |
GET |
/api/stats/leaders |
Get stat leaders (add ?stat=points&limit=N) |
GET |
/api/stats/team/{team} |
Get team win/loss record |
GET |
/api/stats/h2h |
Head-to-head record (add ?team1=X&team2=Y) |
Success:
{
"status": "ok",
"data": { ... }
}Error:
{
"error": "Error message description"
}| Status Code | Description |
|---|---|
| 200 | Success |
| 400 | Bad request (invalid parameters) |
| 404 | Not found |
| 503 | Service unavailable |
SLAP uses Socket.IO for real-time updates.
const socket = io('https://slap.localhost');
// Listen for state updates
socket.on('state_update', (state) => {
console.log('Score:', state.game.home, '-', state.game.away);
});
// Request current state
socket.emit('request_state');
// Update score
socket.emit('update_score', { home: 5, away: 3 });
// Update clock
socket.emit('update_clock', { clock: "12:30" });
// Update period
socket.emit('update_period', { period: "3" });# Get current state
curl https://slap.localhost/api/state
# Trigger home goal
curl -X POST https://slap.localhost/api/goal \
-H "Content-Type: application/json" \
-d '{"side": "HOME"}'
# Set score manually
curl -X POST https://slap.localhost/api/state \
-H "Content-Type: application/json" \
-d '{"home": 3, "away": 1}'
# Add 2-minute penalty to away team
curl -X POST https://slap.localhost/api/penalty \
-H "Content-Type: application/json" \
-d '{"side": "AWAY", "duration": 120}'
# Show player card
curl -X POST https://slap.localhost/api/overlay/player \
-H "Content-Type: application/json" \
-d '{"team": "home", "number": "87", "name": "CROSBY", "duration": 5000}'import requests
BASE_URL = "https://slap.localhost/api"
# Get state
state = requests.get(f"{BASE_URL}/state").json()
print(f"Score: {state['game']['home']} - {state['game']['away']}")
# Trigger goal
requests.post(f"{BASE_URL}/goal", json={"side": "HOME"})
# Update score
requests.post(f"{BASE_URL}/state", json={"home": 5, "away": 2})
# Show player card
requests.post(f"{BASE_URL}/overlay/player", json={
"team": "home",
"number": "87",
"name": "CROSBY",
"duration": 5000
})// Using fetch API
async function triggerGoal(side) {
const response = await fetch('/api/goal', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ side })
});
return response.json();
}
// Using Socket.IO for real-time updates
const socket = io();
socket.on('state_update', (state) => {
document.getElementById('homeScore').textContent = state.game.home;
document.getElementById('awayScore').textContent = state.game.away;
});Tip
SLAP's API works great with Stream Deck and similar control surfaces!
| Button | HTTP Request |
|---|---|
| Home Goal | POST /api/goal with {"side":"HOME"} |
| Away Goal | POST /api/goal with {"side":"AWAY"} |
| Show Bug | POST /api/bug/show |
| Hide Bug | POST /api/bug/hide |
| Replay | POST /api/overlay/replay |
| Player Card | POST /api/overlay/player with player data |
Note
The easiest way is using the built-in installer:
# Install CasparCG (downloads ~105MB)
./deploy.sh caspar-install
# Start CasparCG
./deploy.sh caspar-start
# Check status
./deploy.sh caspar-status
# Stop CasparCG
./deploy.sh caspar-stopThis installs CasparCG to ~/.local/share/casparcg/ which:
- Works on immutable Linux systems
- Doesn't require root/sudo access
- Automatically copies SLAP templates
- Creates a default config for 1080p output
You can also control CasparCG from the web dashboard.
SLAP sends these commands to CasparCG:
| Command | Description |
|---|---|
CG 1-10 UPDATE 1 "{json}" |
Update scorebug data |
CG 1-10 INVOKE 1 "goal:HOME" |
Trigger goal animation |
CG 1-10 INVOKE 1 "show" |
Show scorebug |
CG 1-10 INVOKE 1 "hide" |
Hide scorebug |
The MP-70 controller outputs game data via RS-232 serial connection using a binary protocol.
| Parameter | Value |
|---|---|
| Baud Rate | 9600 |
| Data Bits | 8 |
| Parity | None |
| Stop Bits | 1 |
| Flow Control | None |
All packets are wrapped with ASCII control characters:
| Byte | Value | Name | Description |
|---|---|---|---|
| Start | 0x02 |
STX | Start of Text |
| End | 0x03 |
ETX | End of Text |
Packets must be at least 80 bytes to be considered valid.
Clock packets contain only the game clock time.
Position Length Field Format
-------- ------ ----- ------
[0] 1 STX 0x02
[1] 1 Type 'C' (0x43)
[2:6] 4 Clock ASCII "MMSS"
[7:79] 73 Padding (unused)
[79] 1 ETX 0x03
Clock Format: 4 ASCII digits (MMSS)
"1500"= 15:00"0130"= 01:30
Score packets contain the full game state.
Position Length Field Format
-------- ------ ----- ------
[0] 1 STX 0x02
[1] 1 Type 'H' (0x48)
[13:15] 2-3 Home Score ASCII digits
[29:31] 2-3 Away Score ASCII digits
[45:46] 1 Period ASCII digit
[52:56] 4 Home Penalty 1 ASCII "MMSS"
[57:61] 4 Home Penalty 2 ASCII "MMSS"
[62:66] 4 Away Penalty 1 ASCII "MMSS"
[67:71] 4 Away Penalty 2 ASCII "MMSS"
[79] 1 ETX 0x03
For debugging or reverse-engineering the MP-70 protocol:
MP-70 Controller Scoreboard Display
| |
| TX (Pin 3) ----------+---------------> RX
| |
| v
| [Snooper RX]
| USB-Serial Adapter
| (capture only)
Key Points:
- Only connect TX from MP-70 to your snooper's RX
- Do NOT connect your snooper's TX (passive listening)
- Connect GND between all devices
Linux:
stty -F /dev/ttyUSB0 9600 cs8 -cstopb -parenb raw
cat /dev/ttyUSB0 | tee capture.bin | hexdump -CWindows:
- TeraTerm: File > Log > Start logging (binary mode)
- PuTTY: Session > Logging > All session output
# View hex dump
hexdump -C capture.bin | less
# Find packet boundaries
hexdump -C capture.bin | grep "02.*03"SLAP/
├── deploy.py # Python deploy script
├── LICENSE # GPL-3.0 License
├── README.md # This file
└── src/
├── run.py # Main entry point
├── requirements.txt # Python dependencies
├── config/ # Configuration files
│ ├── default.json # Default config
│ └── roster.json # Team rosters
├── slap/ # Python package
│ ├── config.py # Config loader
│ ├── parser/ # MP-70 protocol decoder
│ ├── core/ # Game state & logic
│ ├── output/ # CasparCG & OBS clients
│ ├── simulator/ # Fake serial for testing
│ └── web/ # Flask dashboard
│ ├── app.py # API routes & Socket.IO
│ ├── templates/ # Dashboard HTML
│ └── static/js/ # Local JS libraries
├── templates/ # Broadcast overlay templates
│ ├── scorebug.html # Main scorebug
│ ├── css/
│ │ ├── scorebug.css
│ │ └── overlays.css
│ ├── js/
│ │ ├── scorebug.js
│ │ └── socket.io.min.js
│ ├── overlays/ # Individual overlay templates
│ │ ├── goal.html
│ │ ├── player.html
│ │ ├── goalie.html
│ │ ├── period.html
│ │ ├── intro.html
│ │ ├── stars.html
│ │ ├── powerplay.html
│ │ ├── shots.html
│ │ ├── penalty.html
│ │ ├── replay.html
│ │ └── ticker.html
│ └── Logos/ # Team logo files
└── docs/ # Reference docs
└── MP-70_Manual.pdf
🔌 Serial Port Issues
Permission denied (Linux):
sudo usermod -a -G dialout $USER
# Log out and back inNo data received:
- Verify MP-70 is set to ProLine data format (not VIDEO CHAR)
- Check cable connections
- Try different USB port
- Verify baud rate is 9600
🎬 CasparCG Issues
Connection refused:
- Verify CasparCG server is running
- Check firewall settings
- Verify host and port in config
Template not updating:
- Verify template is loaded:
CG 1-10 INFO - Check channel/layer numbers match config
🖥️ Web Interface Issues
Page not loading:
- Verify SLAP is running:
./deploy.py status - Try different port:
./deploy.py start --port 8888 - Check firewall settings
🐍 Virtual Environment Issues
pip missing or broken:
./deploy.py update # Recreates venv if broken|
Core Systems
|
Web & API
|
Overlays (11 templates)
|
🔊 Audio & Media (Not Implemented)
- Goal horn audio playback
- Siren/buzzer sound effects
- PA announcement integration
- Video replay control (NDI/RTMP switching)
💾 Data & Storage (Partially Implemented)
- SQLite database backend ✅
- Game history & archive ✅
- Season player statistics ✅
- Roster import from CSV/Excel
- Historical game data CSV export
- Career statistics (multi-season)
🔐 Security & Multi-user (Not Implemented)
- User authentication system
- Role-based permissions
- API key management
📱 Extended Interfaces (Not Implemented)
- Mobile companion app
- Stream Deck native plugin
- Discord/Slack notifications
- Multi-game tournament mode
🔌 Future Hardware Support
- Daktronics All Sport 5000
- OES scoreboard protocol
- Generic protocol adapters
This project is licensed under the GNU General Public License v3.0.
See the LICENSE file for details.
Scoreboard Live Automation Platform
Built for hockey broadcast professionals 🏒