No description
  • Python 65.9%
  • JavaScript 19.8%
  • CSS 9.6%
  • HTML 3.9%
  • Dockerfile 0.8%
Find a file
2026-10-12 23:24:47 +02:00
public feat: platform display 2026-10-12 23:24:47 +02:00
tests feat: platform display 2026-10-12 23:24:47 +02:00
.dockerignore first commit 2026-10-12 15:02:13 +02:00
.env.example feat: enhance SNCF station board with social preview rendering and details display 2026-10-12 15:58:56 +02:00
.gitignore first commit 2026-10-12 15:02:13 +02:00
compose.yaml feat: enhance SNCF station board with social preview rendering and details display 2026-10-12 15:58:56 +02:00
Dockerfile feat: platform display 2026-10-12 23:24:47 +02:00
logos.py first commit 2026-10-12 15:02:13 +02:00
platforms.py feat: platform display 2026-10-12 23:24:47 +02:00
preview.py feat: platform display 2026-10-12 23:24:47 +02:00
README.md feat: platform display 2026-10-12 23:24:47 +02:00
requirements.txt feat: platform display 2026-10-12 23:24:47 +02:00
server.py feat: platform display 2026-10-12 23:24:47 +02:00
sncf.py feat: platform display 2026-10-12 23:24:47 +02:00
station_directory.py feat: enhance SNCF station board with social preview rendering and details display 2026-10-12 15:58:56 +02:00
stations.json first commit 2026-10-12 15:02:13 +02:00
STATIONS.md first commit 2026-10-12 15:02:13 +02:00

Fullscreen SNCF station board

A standalone departure/arrival board using the official SNCF realtime API directly. Astucesweb is no longer a data dependency.

Run

Requires Python 3.10 or newer, the Cairo shared library, and an SNCF API key. Docker installs the image-rendering dependencies automatically. For a local Python run, install Cairo (for example, sudo apt install libcairo2 on Debian/Ubuntu) and create a virtual environment:

python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt

Put the key in .env in this project directory (outside public):

SNCF_API_KEY=your_sncf_api_key

Alternatively, set the SNCF_API_KEY environment variable. The key is only sent by the Python server to api.sncf.com in an Authorization header. It is never included in browser code, public config, URLs, API responses, or logs. .env is ignored by Git.

cd /home/mopi/dev/sncf-fullscreen
.venv/bin/python server.py

Open http://localhost:8088. Click anywhere or press F to toggle fullscreen; Escape exits. The board fills the viewport outside browser fullscreen too.

Docker Compose

With Docker and Docker Compose installed, use the same .env file containing SNCF_API_KEY, then run from the project directory:

docker compose up -d --build

Open http://localhost:8088. To use another host port (for example, if the local Python server is already running):

SNCF_PORT=8089 docker compose up -d --build

The API key is passed to the container at runtime and excluded from the image build context. public/config.json is mounted read-only, so edit it on the host to change defaults. Reload the browser after presentation changes; if your editor replaces the file instead of editing it in place, recreate the container with docker compose up -d --force-recreate. After changing .env, recreate the container with that command too.

docker compose logs -f
docker compose down

The container runs as a non-root user with a read-only filesystem and includes a health check for the web server. The writable station-cache volume preserves automatic station-directory updates across container restarts and rebuilds. docker compose down retains this volume; adding --volumes deletes it.

Config defaults

Edit public/config.json:

{
  "station": "PLY",
  "mode": "departures",
  "includeSuburban": true,
  "showFooter": true,
  "text": "",
  "scrollSpeed": 50,
  "infoInterval": 2
}
  • station: SNCF trigram such as PLY, PNO, MSC, MBC, PGN, PES, or TGR. Eight-digit UIC codes and stop_area:SNCF:<UIC> also work. The station directory automatically updates from SNCF every 24 hours; new station codes become available once SNCF publishes them. See STATIONS.md for the source and cache behavior.
  • mode: departures (blue), arrivals (green), or split (blue departures on the left and green arrivals on the right). Split mode shows up to ten trains per side, with a shared clock and footer. Arrivals show the train's origin, departures its terminus.
  • includeSuburban: the bundled config enables RER/Transilien trains that share the station's stop area. Set to false to exclude them; omitted settings also fall back to false. They may only have planned times available. Replacement buses and coaches are excluded in either setting.
  • showFooter: set to false to hide the scrolling text, clock, and information strip. Train rows fill the freed space.
  • text: custom scrolling text. Empty or whitespace-only text automatically displays Bienvenue en gare de [station name]. for the selected station, including in split mode and social previews. Use showFooter: false to hide the footer.
  • scrollSpeed: pixels per second, from 10 to 300.
  • infoInterval: seconds between displaying status and service/train number, from 0.5 to 60. Defaults to 2; 0 keeps the status visible. All rows switch together.

Reload after editing presentation settings. Station, mode, and suburban filtering changes apply on the next refresh. No server restart is needed for .env file edits or config edits. Changing an environment variable requires restarting the server.

URL overrides

URL settings take precedence over config defaults for that tab. They do not modify the config file.

All public settings above are supported as URL parameters. Boolean settings accept true/false or 1/0. The API key must remain in .env or an environment variable.

Data and display

The station directory refreshes in the background without delaying board startup. Failed updates retry after one hour and keep the previous cache. The bundled directory provides an offline fallback. For local Python runs, the cache is stored in .cache/ (ignored by Git); SNCF_CACHE_DIR can override that location.

The browser subscribes to /api/live over WebSocket and receives a board snapshot immediately, then updates when the server's cached data changes. Connections reconnect automatically with a delay of 1–30 seconds; /api/departures polling remains a fallback if WebSockets are unavailable. Train rows are reused and changed fields update in place, preserving unchanged logos and scrolling text. The server calls the official SNCF/Navitia /departures or /arrivals endpoint for the selected stop area. Each station/mode/filter combination has its own 30-second cache, shared by WebSocket connections and HTTP requests, so tabs can show different boards independently without increasing SNCF requests. Failed refreshes retain only that board's last successful data and show a stale-data warning. WebSockets improve delivery and rendering; the upstream data still refreshes every 30 seconds.

Split mode fetches both sides in parallel and reuses these same caches. A failure on one side keeps its previous data and leaves the other side working. On narrow screens, each panel uses the compact layout while keeping departures on the left and arrivals on the right. Generated social previews also support split mode.

Scheduled times remain in the yellow column; delays come from the difference between the scheduled and expected API times. Trains without a reported delay display à l’heure; the API data freshness is preserved separately for planned and realtime data. Delay and cancellation status are displayed when included in the API response. Disruption titles appear in an uppercase column to the right of the destination, separated by a thick vertical line. Titles wrap onto up to two lines when they fit; longer titles scroll horizontally at the configured scrollSpeed. This adapts after resizing and font loading. With reduced motion enabled, overflowing titles can be scrolled manually. The clock and station times use Europe/Paris.

Announced platform numbers and letters appear in boxes on the right. Missing Navitia platforms are supplemented from the official SNCF SIRI Estimated Timetable feed, matched by train number, station UIC, arrival/departure mode, and scheduled date and time. The feed is shared across boards and refreshed at most every 30 seconds. Unknown platforms, hall-only announcements, unmatched services, and supplementary data older than two minutes leave the platform box blank. A feed failure does not interrupt the timetable. No additional API key is needed.

The white SVG collection includes INOUI, Lyria, OUIGO, Eurostar, and SNCF. TER services use SNCF. SNCF and OUIGO retain their white badge backgrounds with transparent lettering; OUIGO is a full circle. Sources are recorded in public/logos/SOURCES.md.

API documentation: https://doc.navitia.io/#next-departures-and-arrivals

Hosting and checks

The server adds station-specific Open Graph metadata to the HTML before serving it, so Discord does not need to run JavaScript. /og-image.png draws a 1080×720 PNG on demand from the current SNCF data, local logos, and Achemine fonts, without taking a screenshot or storing a preview file. Station, mode, suburban filtering, footer visibility, and custom footer text follow the shared URL's settings. PNGs reuse the normal train-data cache and a bounded in-memory image cache. The image URL includes a 30-second version; previously posted Discord embeds may still be cached by Discord. PNG previews show a static status view, with long titles truncated to two lines.

Set PUBLIC_BASE_URL to your public site origin (defaults to https://sncf-panel.mopigames.dev) when hosting on another domain. It is used for absolute embed URLs; an environment change requires recreating the container or restarting the local server.

HTML responses use Cache-Control: no-store, and JavaScript/CSS URLs include a version derived from each file's contents. Redeployments therefore request new assets instead of reusing older browser or CDN cache entries.

For access on your local network:

.venv/bin/python server.py --host 0.0.0.0 --port 8088

For public hosting, run the Python application behind an HTTPS reverse proxy with WebSocket upgrades enabled for /api/live (Caddy handles them automatically; Nginx needs the Upgrade and Connection headers forwarded). HTTPS pages use secure wss:// connections. Static-only hosting cannot serve the authenticated API proxy. SNCF API quotas apply; API responses are cached per board to reduce requests.

.venv/bin/python -m unittest discover -s tests -v

This is an independent viewer, not an official SNCF service. Achemine font assets came from the referenced original page; check redistribution terms before publishing them on another domain.