Driver headshots are not hardcoded locally. They come from the official F1 live DriverList feed, not from the Jolpica/Ergast standings API. When no live DriverList is available, the integration keeps the last known driver list or falls back to standings data, and that fallback does not include headshots. That means images can be missing or outdated until the live feed updates them. Custom driver images are not supported at the moment.
[Update] F1 Live Timing API changes — what’s affected, what’s fixed, and what’s coming
Hi everyone,
I wanted to share a detailed update on what has been happening with the integration over the past few weeks, following reports of missing tyre data and Championship Prediction sensors showing unknown during the 2026 season opener.
What changed in the F1 Live Timing API
During last season, F1’s Live Timing service no longer freely delivers all data streams via its SignalR connection. A number of streams now require a valid F1 TV subscription (an authenticated JWT token) to receive live data. This went largely unnoticed at first because Replay Mode continued to work perfectly, replay uses static archive files that are stored on F1’s servers without any authentication requirement. So everything looked fine in replay, but the live connection was silently missing data for certain sensors.
What is affected
The core of the integration, timing, session status, track status, race control messages, lap counts, weather, driver lists, sector times, and tyre data, all come from streams that remain fully open without authentication. These continue to work live, no changes needed.
The streams that are now auth-gated are:
- Championship Prediction (driver and team standings predictions during a session)
- Pit Stop Series (live pit stop data)
- Driver Race Info (position changes, gap calculations used by some sensors)
- Car telemetry (CarData.z / Position.z) — compressed high-frequency telemetry
Replay-only sensors
Rather than silently show stale or unknown values, sensors that depend on auth-gated streams will be designated as Replay Only. This means they will update correctly when you play back a finished session, but will not receive live updates during a race. The Lovelace card will been updated to show a clear amber indicator when a sensor is in this state, so you’ll know the data shown is from the archive rather than live.
What will be fixed
Two significant fixes have landed in BETA based on this analysis:
Tyre data source migration. The integration previously used TyreStintSeries as its primary source for tyre compound data. That stream is unreliable live. It has been replaced with TimingAppData, which is the same approach used by others and is confirmed open and stable. Tyre data (compound, new/used, stint laps) should be reliable during live sessions.
SignalR protocol upgrade. The integration has been migrated from the legacy ASP.NET SignalR protocol to ASP.NET Core SignalR. The legacy protocol required re-subscribing every 5 minutes due to Azure connection limits, which caused 589 reconnects over a two-day period in testing. The Core protocol has built-in ping/pong keepalives and is significantly more stable. This will be verified against packet captures from the Japanese GP.
Why authentication is not being implemented right now
Several users have asked about adding F1 TV login support to restore the auth-gated streams. I want to be transparent here: I do not personally have an F1 TV Pro subscription, which means I cannot test an authenticated integration end-to-end against real live data. Shipping untested auth code risks breaking things for everyone, including the streams that currently work fine.
My priority right now is making sure the open streams, which cover the majority of what most users care about, are as stable and accurate as possible. Once the current fixes are validated across a few race weekends, authentication can be revisited. Any contributions or testing help from users who do have F1 TV Pro would be very welcome.
What’s still being monitored
A capture service is running on my homelab server recording live SignalR data across multiple parallel streams. This gives direct visibility into exactly what data F1’s servers are delivering. I’ll continue using this to validate sensor behaviour across upcoming race weekends.
Thanks for your patience and for all the bug reports, the detailed logs from issues like #427, #428, and #425 were essential to tracking this down. Feel free to reply here or open an issue on GitHub if you notice anything unexpected.
Thank you all for contributing to this little hobby project of mine.
Keep rocking Stimo!!!
And thank you for this great addon cq cards!
I think all of us will agree on that!
hi hi all, busy trying to unravel all the capabilities of the F1 Sensor integration and then think the various cards.
some immediate “desires” for this weekend as there is data coming that I can test against.
dashboard:
practice and race times, as in when ?
practice and race times per driver per session
I love this dashboard by Nicxe… would love if someone has build THIS and willing to share the dashboard yaml.
then came across the FOLLOWING dashboard.
I got the points shown next to the names, I even got the cars shown, but the back ground, the helmets, the proper text / font, does not seem to want o work.
asking for some assistance.
G
They’re the same cards that go with the sensor. They’re both by @Stimo
would really love if someone with a couple of dashboards might be willing to paste their complete dashboard yaml per dashboard.
will long term customise, create own, jsut want to see all the lights on, data flowing etc as a taster this weekend.
G
you just need to install the live data cards … sensors needed to use are all layed out on which ones to use when you pick the card
guessing allot of them are dependant on activity on track, will see later.
G
No need to guess, everything is documented here. You just need to take the time to read it ![]()
You can test the live data cards out by playing a replay session.
do really love asking for assistance/example yaml, known to be working, that basically cost the responder the simple task of a copy paste, but instead,
I get a RTFM…
G
explains why previously I had no head shots and suddenly this morning they were there, thought I did something wrong, now understand why. ![]()
G
hi hi,
Trying to add some of your cards.
I can’t seem to add the 2026 calendar ?
G
There is no YAML, it’s custom cards installed using the UI
then it gets even more interesting, will see tomorrow while qualifying is on.
see there is a qualifying card,
is there a card for each of the practice sessions, other words it should show/data for practice 1 and practice 2.
See everyone have a card showing China… which card shows the name/location of the race for the wknd.
G
Dear George,
As it is is covered in the Readme (RTFM) there is a card that covers info during the (sprint) qualification and one specific for the (sprint) race. You can easily setup all the cards on a page and see what happens while (re)playing a race of qualification. For testing of the cards I always load a session and see how the data fills the cards, that is so cool. Have fun with the F1 sensor and the cards. Of course you have made integrations (HACS) for both !!
i get where your coming from with wanting a working cut n paste altho these cards are a little different in the fact they are perdefined working card just like what your asking for
… I currently have the calendar added to a dashboard and the below is what I see.
- type: custom:atomic-calendar-revive
name: Calendar
enableModeChange: true
entities:
- entity: calendar.f1_season_calendar
icon: mdi:calendar-month
showing me:
is there a larger calendar showing all the race names/countries/dates as a table.
figured out, rough, but it’s there
type: vertical-stack
cards:
- type: custom:f1-live-session-card
show_flag: true
show_lap_progress: true
show_track_status: true
show_weather: true
show_time_remaining: true
show_time_elapsed: true
session_entity: sensor.f1_current_session
session_status_entity: sensor.f1_session_status
formation_start_entity: binary_sensor.f1_formation_start
lap_count_entity: sensor.f1_race_lap_count
track_status_entity: sensor.f1_track_status
weather_entity: sensor.f1_track_weather
next_race_entity: ""
session_time_remaining_entity: sensor.f1_session_time_remaining
session_time_elapsed_entity: sensor.f1_session_time_elapsed
overtake_mode_entity: binary_sensor.f1_overtake_mode
straight_mode_entity: sensor.f1_straight_mode
- type: custom:atomic-calendar-revive
name: Calendar
enableModeChange: true
entities:
- entity: calendar.f1_season_calendar
icon: mdi:calendar-month
- type: custom:button-card
entity: sensor.f1_current_season
icon: mdi:calendar-clock
show_name: false
show_state: false
show_icon: false
layout: custom
tap_action:
action: call-service
service: input_boolean.toggle
service_data:
entity_id: input_boolean.f1_race_toggle
styles:
grid:
- grid-template-areas: |
"header"
"races"
- row-gap: 12px
card:
- padding: 12px
- border-radius: 8px
- background: linear-gradient(135deg,
- color: white
- font-family: sans-serif
- box-shadow: 0px 4px 10px rgba(0,0,0,0.3)
custom_fields:
header:
- font-size: 17px
- font-weight: 600
- text-align: center
- padding-bottom: 6px
- border-bottom: 1px solid rgba(255,255,255,0.2)
- cursor: pointer
races:
- font-size: 14px
- line-height: 1.6
- display: flex
- flex-direction: column
- gap: 6px
custom_fields:
header: |
[[[
if (!entity?.attributes) return "Race calendar not available";
const expanded = states['input_boolean.f1_race_toggle']?.state === 'on';
const icon = expanded ? "mdi:chevron-up" : "mdi:chevron-down";
return `
<div style="width: 100%; text-align: center;">
<div style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer;">
<span style="font-weight: 600;">🏁 F1 Schedule - ${entity.attributes.season}</span>
<ha-icon icon="${icon}" style="--mdc-icon-size: 20px;"></ha-icon>
</div>
</div>
`;
]]]
races: |
[[[
if (!entity?.attributes?.races) return "No races found";
const toggle = states['input_boolean.f1_race_toggle']?.state === 'on';
const races = toggle ? entity.attributes.races : entity.attributes.races.slice(0, 10);
const now = new Date();
let nextRace = null;
for (const r of entity.attributes.races) {
const raceDate = new Date(r.date + 'T' + (r.time || '00:00:00Z'));
if (raceDate > now) {
nextRace = r;
break;
}
}
return races.map((r, idx) => {
const date = new Date(r.date + 'T' + (r.time || '00:00:00Z'));
const dateString = date.toLocaleDateString('nl-NL', { weekday: 'short', day: 'numeric', month: 'short' });
const timeString = date.toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit' });
const isNextRace = r === nextRace;
const bg = isNextRace
? 'rgba(255, 215, 0, 0.12)'
: (idx % 2 === 0 ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.08)');
return `
<div style="display: flex; flex-direction: column; background: ${bg}; padding: 6px 10px; border-radius: 4px;">
<div><strong>Ronde ${r.round}:</strong> ${r.raceName}</div>
<div style="font-size: 12px; color: var(--secondary-text-color);">
📍 ${r.Circuit.Location.locality}, ${r.Circuit.Location.country} • ${dateString} ${r.time ? '• ' + timeString : ''}
</div>
</div>`;
}).join('');
]]]
in your post below you show the calendars…
I tried to implement, but some how it’s coming up in a non english languages.
below is the yaml. any idea where the problem is coming from?
- type: custom:button-card
- row-gap: 12px
card:
- padding: 12px
- border-radius: 8px
- background: linear-gradient(135deg,
- color: white
- font-family: sans-serif
- box-shadow: 0px 4px 10px rgba(0,0,0,0.3)
custom_fields:
header:
- font-size: 17px
- font-weight: 600
- text-align: center
- padding-bottom: 6px
- border-bottom: 1px solid rgba(255,255,255,0.2)
- cursor: pointer
races:
- font-size: 14px
- line-height: 1.6
- display: flex
- flex-direction: column
- gap: 6px
custom_fields:
header: |
[[[
if (!entity?.attributes) return "Race calendar not available";
const expanded = states['input_boolean.f1_race_toggle']?.state === 'on';
const icon = expanded ? "mdi:chevron-up" : "mdi:chevron-down";
return `
<div style="width: 100%; text-align: center;">
<div style="display: inline-flex; align-items: center; gap: 8px; cursor: pointer;">
<span style="font-weight: 600;">🏁 F1 Schedule - ${entity.attributes.season}</span>
<ha-icon icon="${icon}" style="--mdc-icon-size: 20px;"></ha-icon>
</div>
</div>
`;
]]]
races: |
[[[
if (!entity?.attributes?.races) return "No races found";
const toggle = states['input_boolean.f1_race_toggle']?.state === 'on';
const races = toggle ? entity.attributes.races : entity.attributes.races.slice(0, 10);
const now = new Date();
let nextRace = null;
for (const r of entity.attributes.races) {
const raceDate = new Date(r.date + 'T' + (r.time || '00:00:00Z'));
if (raceDate > now) {
nextRace = r;
break;
}
}
return races.map((r, idx) => {
const date = new Date(r.date + 'T' + (r.time || '00:00:00Z'));
const dateString = date.toLocaleDateString('nl-NL', { weekday: 'short', day: 'numeric', month: 'short' });
const timeString = date.toLocaleTimeString('nl-NL', { hour: '2-digit', minute: '2-digit' });
const isNextRace = r === nextRace;
const bg = isNextRace
? 'rgba(255, 215, 0, 0.12)'
: (idx % 2 === 0 ? 'rgba(255,255,255,0.04)' : 'rgba(255,255,255,0.08)');
return `
<div style="display: flex; flex-direction: column; background: ${bg}; padding: 6px 10px; border-radius: 4px;">
<div><strong>Ronde ${r.round}:</strong> ${r.raceName}</div>
<div style="font-size: 12px; color: var(--secondary-text-color);">
📍 ${r.Circuit.Location.locality}, ${r.Circuit.Location.country} • ${dateString} ${r.time ? '• ' + timeString : ''}
</div>
</div>`;
}).join('');
]]]

