惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
G
Google Developers Blog
Blog — PlanetScale
Blog — PlanetScale
U
Unit 42
A
About on SuperTechFans
Vercel News
Vercel News
B
Blog
Martin Fowler
Martin Fowler
MyScale Blog
MyScale Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
腾讯CDC
D
Docker
V
Visual Studio Blog
博客园 - 叶小钗
The Cloudflare Blog
Jina AI
Jina AI
B
Blog RSS Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
WordPress大学
WordPress大学
T
Tailwind CSS Blog
MongoDB | Blog
MongoDB | Blog
D
DataBreaches.Net
月光博客
月光博客
大猫的无限游戏
大猫的无限游戏

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Building a real-time F1 dashboard on macOS with free publ...
AlexDesign420 · 2026-05-30 · via DEV Community

AlexDesign420

I wanted to see live Formula 1 data on my desktop while watching the race — not buried in an app or a browser tab, but always visible in the corner of my screen. So I built a widget that pulls from two free public APIs and renders everything directly on the macOS desktop.

Here's what went into it and what I learned along the way.

What it looks like

During a live session the widget shows:

  • Live standings — position, driver code, gap to leader, last lap time, tyre compound and age, pit stop count
  • Race Control banner — Safety Car, Virtual Safety Car, Red Flag with colour-coded flashing overlay
  • Side panel — Team radio recordings (playable), all RC messages, track weather
  • Audio streams — ARD, BBC Radio 5, talkSPORT, ORF Sport Plus and more, played via mpv

Outside of race weekends it shows the championship standings, the full calendar and a countdown to the next event.

Tech stack

Layer Tool
Widget host Übersicht — renders JSX widgets directly on the macOS desktop
Frontend JSX (React-like syntax, no build step)
Backend Python 3 + Flask on 127.0.0.1:9877
Live F1 data OpenF1 API — completely free, no key needed
Calendar & standings Jolpica API — the Ergast successor
Audio mpv via Unix socket
TTS macOS say -v Anna

The two APIs

OpenF1

OpenF1 is the standout discovery here. It exposes granular, real-time F1 telemetry for free with no authentication. The endpoints I use most:

GET /v1/position?session_key=9158&date>2024-05-18T13:00:00
GET /v1/intervals?session_key=9158&date>2024-05-18T13:00:00
GET /v1/laps?session_key=9158
GET /v1/stints?session_key=9158
GET /v1/pit?session_key=9158
GET /v1/race_control?session_key=9158
GET /v1/team_radio?session_key=9158
GET /v1/weather?session_key=9158

A few gotchas that cost me time:

  • Date filters must be ISO 8601, not Unix timestamps: date>2024-05-18T13:00:00 ✅, date>1716033600
  • rainfall is an integer (0 = dry, higher = wetter), not a boolean
  • gap_to_leader from /intervals is a float for normal gaps but a string like "+1 LAP" for lapped cars — always parseFloat() and guard before calling .toFixed()
  • compound from /stints is one of SOFT, MEDIUM, HARD, INTERMEDIATE, WET (uppercase strings)

Jolpica

Jolpica is a drop-in replacement for the now-deprecated Ergast API. The JSON structure is identical:

url = f"https://api.jolpi.ca/ergast/f1/{SEASON}/driverStandings.json"
data = requests.get(url).json()

lists = data["MRData"]["StandingsTable"]["StandingsLists"]
for ds in lists[0]["DriverStandings"]:
    team = ds["Constructors"][0]["name"]   # plural array, not Constructor.name
    driver_code = ds["Driver"]["code"]
    points = ds["points"]

The one trap: it's Constructors[0] (plural, array) — not Constructor.name as you might expect from the docs.

Architecture: why a local Flask server?

Übersicht widgets run a shell command on a timer. Shell commands can curl an API directly, but you lose:

  • Response caching (hitting OpenF1 20 times per minute per endpoint is unnecessary)
  • Background stream health checks
  • mpv process management via Unix socket
  • State persistence between widget refresh cycles

The Flask server runs as a daemon and handles all of that. The widget shell command just calls engine.py (event detection, TTS triggers) and curls the local server for the rest.

f1.jsx  (refreshes every 3 seconds)
  └── shell: python3 ~/.f1/engine.py
  └── fetch: http://127.0.0.1:9877/api/session   ← live standings + RC
  └── fetch: http://127.0.0.1:9877/api/radio      ← team radio
  └── fetch: http://127.0.0.1:9877/api/schedule   ← calendar

The HLS stream headache

Several ARD/ZDF/MDR backup renditions use -b/ path segments that return 404 in ffmpeg (which mpv uses under the hood). I wrote a resolver that parses the HLS master playlist and picks the first clean audio rendition:

def resolve_hls_audio(url):
    if ".m3u8" not in url.lower():
        return url
    resp = requests.get(url, timeout=6)
    text = resp.text

    # Prefer explicit audio renditions
    audio = re.findall(
        r'#EXT-X-MEDIA:TYPE=AUDIO[^\n]*?URI="([^"]+)"', text
    )
    for cand in audio:
        full = urljoin(url, cand)
        if "-b/" not in full and "-b." not in full:
            return full

    # Fall back to lowest-bandwidth video variant
    variants = []
    for i, line in enumerate(text.splitlines()):
        if line.startswith("#EXT-X-STREAM-INF"):
            bw = int(re.search(r'BANDWIDTH=(\d+)', line).group(1))
            variants.append((bw, urljoin(url, text.splitlines()[i+1])))
    if variants:
        return sorted(variants)[0][1]
    return url

Übersicht-specific constraints

Writing JSX for Übersicht has a few rules that differ from a normal React project:

  1. No ${VAR} in the command string — the backtick template literal is evaluated by the shell, and ${} crashes the widget. Use $(subshell) for shell substitutions.
  2. No child_process — use the built-in run() helper instead
  3. No // comments inside the bash heredoc — they cause parse errors
  4. refreshFrequency is in milliseconds

What I'd add next

  • Sprint weekend detection (Sprint Shootout sessions have a different session name)
  • Driver headshot images from OpenF1's headshot_url field
  • Push notifications for race start / chequered flag via macOS Notification Center

The full source is on GitHub — pull requests welcome.

Links