Thanks mate, this is brilliant - well done.
I’ve incorporated into a test dashboard and everything works great except the fonts. This may be a really dumb question, but how do you bring in the webfonts into HA?
Thanks mate, this is brilliant - well done.
I’ve incorporated into a test dashboard and everything works great except the fonts. This may be a really dumb question, but how do you bring in the webfonts into HA?
Be interested myself to see what Harry did as a few different methods on the forum…I’ve used this one and all working fine
Didn’t bother with the conversion piece, just downloaded the woff2 format direct
The fonts get added as a Dashboard resource.
/* F1 Bold */
@font-face {
font-family: 'F1Bold';
src: url('/local/fonts/formula1-bold.woff') format('woff');
font-weight: normal;
font-style: normal;
}
/* F1 Regular */
@font-face {
font-family: 'F1Regular';
src: url('/local/fonts/formula1-regular.woff') format('woff');
font-weight: normal;
font-style: normal;
}
/* F1 Wide */
@font-face {
font-family: 'F1Wide';
src: url('/local/fonts/formula1-wide.woff') format('woff');
font-weight: normal;
font-style: normal;
}
I noticed that when you want to change the time delay for the light you need to restart HA before it takes affect
I have created a helper input_number.f1_time_delay to be able to change the delay in the F1 stream.
I then created a not red flow to change my wled strip underneath the tv.
I was only able to simulate the result, but it looks very good, and you can change the delay on the fly
The strip only turns on when it goes live and goes back to the normal white when it is ended…
Now I need to wait a week before it can be tested live ![]()
use an inject node to test the flow or go to developer tools in HA and change the state
Thats what i did to test
Hi,
What the automation “f1_track_status_light_control” making please ?
Damien
I had an issue with the status details appearing at the bottom of each grid box rather than directly under the name. The following change to each of the Grid sections solved the problem:
grid:
Why is the f1 track status all in caps?..
Can it be changed???
you can always use a template
This is fantastic! Bummer that I only today found this integration, but the plus side is that we will have a race in less than 3 hours ![]()
I want to see how much delay there is by using the sensor.f1_session_status change to “live”.
Question: at what point during the race does it get status “live”? Is this at the start of the live broadcast? Or at the moment the 5 lights switch off (the actual start of the race)?
It’s the “lights out” moment AFAIK…it’s in a pre state before this
It switches to live when the race actually starts, meaning lights out, not when the formation lap begins.
A tip:
During the broadcast, they always show the moment the race clock flips to the start time, for example 15:00:00. If you look up an atomic clock online, you’ll have an exact reference. Watch the time when the broadcast clock hits the start. For me, it’s always about 30 seconds later than the atomic clock. So when the broadcast clock shows 15:00:00 and the formation lap starts, the atomic clock reads 15:00:30. I then set a 30-second delay, it’s worked perfectly ever since.
That’s a great tip, thanks!
@Stimo one last question: do you restart the integration and/or Hass after changing the delay?
That should not be neccessary if you run the latest version
@Stimo , nice work! I love it! Would have been nice to have some extra info/sensors, like team logos, cars pictures, circuits maps, but nonetheless, a very nice custom integration. I just discovered it and with some help from AI, I started creating a F1 tab and I would like to share with the community what cards I have done so far. I got inspiration from the drivers standings card and I came up with this:
First one, for Drivers Standings (scrollable)
the code (I had to hardcode some extra pictures links that I got directly from F1 website):
type: custom:button-card
entity: sensor.f1_driver_standings
show_name: false
show_state: false
show_icon: false
layout: custom
styles:
grid:
- grid-template-areas: |
"header"
"results"
- row-gap: 12px
card:
- padding: 12px
- border-radius: 8px
- background: linear-gradient(135deg, rgba(30, 30, 30, 0.8), rgba(10, 10, 10, 0.8))
- color: white
- box-shadow: 0px 4px 10px rgba(0,0,0,0.3)
custom_fields:
header:
- font-size: 20px
- font-weight: 600
- text-align: center
- padding-bottom: 6px
- border-bottom: 1px solid rgba(255,255,255,0.2)
- cursor: default
- font-family: f1regular
results:
- font-size: 14px
- line-height: 1.6
- display: flex
- flex-direction: column
- gap: 6px
- font-family: f1regular
- overflow-y: auto
- max-height: 600px
custom_fields:
header: |
[[[
// SIMPLE, NON-INTERACTIVE HEADER
return `
<div style="width: 100%; text-align: center;">
<div style="display: inline-flex; align-items: center; gap: 8px;">
<span style="font-weight: 600; font-family: 'f1regular';">🏁 DRIVERS STANDINGS</span>
</div>
</div>
`;
]]]
results: |
[[[
const results = entity.attributes?.driver_standings || [];
const displayResults = results.slice(0, 25);
const getDriverData = () => {
const driverList = states['sensor.f1_driver_list']?.attributes?.drivers || [];
const dataMap = {};
driverList.forEach(d => {
if (d.tla) {
dataMap[d.tla] = {
color: d.team_color,
headshot: d.headshot_small
};
}
});
return dataMap;
};
const teamLogos = {
"Red Bull": "https://media.formula1.com/image/upload/c_lfill,w_48/q_auto/v1740000000/common/f1/2025/redbullracing/2025redbullracinglogo.webp",
"Ferrari": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/ferrari/2025ferrarilogo.webp",
"Mercedes": "https://media.formula1.com/image/upload/c_lfill,w_48/q_auto/v1740000000/common/f1/2025/mercedes/2025mercedeslogowhite.webp",
"McLaren": "https://media.formula1.com/image/upload/c_lfill,w_48/q_auto/v1740000000/common/f1/2025/mclaren/2025mclarenlogo.webp",
"Aston Martin": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/astonmartin/2025astonmartinlogowhite.webp",
"Alpine F1 Team": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/alpine/2025alpinelogo.webp",
"RB F1 Team": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/racingbulls/2025racingbullslogowhite.webp",
"Haas F1 Team": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/haas/2025haaslogo.webp",
"Williams": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/williams/2025williamslogo.webp",
"Sauber": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/kicksauber/2025kicksauberlogo.webp",
};
const driverData = getDriverData();
const FALLBACK_COLOR = '#888';
const FALLBACK_ICON = 'mdi:racing-helmet';
const FONT_STYLE = "font-family: 'f1regular';";
return displayResults.map((r, idx) => {
const pos = r.position;
const code = r.Driver?.code;
const name = `${r.Driver?.givenName || ''} ${r.Driver?.familyName || ''}`.trim();
const team = r.Constructors?.[0]?.name || '';
const points = r.points || '0';
const driverSpecificData = driverData[code] || {};
const color = driverSpecificData.color || FALLBACK_COLOR;
const teamLogoUrl = teamLogos[team] || '';
const headshotUrl = driverSpecificData.headshot;
const bgColor = color.length === 7 ? color + '1A' : color;
const posTextColor = color === FALLBACK_COLOR ? 'white' : color;
const IMG_SIZE = '45px';
const ICON_SIZE = '45px';
let headshotTag;
if (headshotUrl) {
headshotTag = `<img src="${headshotUrl}" style="width: ${IMG_SIZE}; height: ${IMG_SIZE}; border-radius: 50%; object-fit: cover;">`;
} else {
// FIX: Wrapped ha-icon in a flex container to ensure perfect vertical and horizontal centering.
headshotTag = `
<div style="
display: flex;
align-items: center;
justify-content: center;
width: ${IMG_SIZE};
height: ${IMG_SIZE};
border-radius: 50%;
background: rgba(255, 255, 255, 0);
line-height: 1;
">
<ha-icon icon="${FALLBACK_ICON}" style="--mdc-icon-size: ${ICON_SIZE}; color: ${posTextColor};"></ha-icon>
</div>
`;
}
const logoTag = teamLogoUrl
? `<img src="${teamLogoUrl}" style="width: ${IMG_SIZE}; height: auto; max-height: ${ICON_SIZE};">`
: '';
return `
<div style="
display: grid;
grid-template-columns: 40px 1fr auto;
column-gap: 2px;
align-items: center;
background: ${bgColor};
padding: 1px 6px;
border-radius: 6px;
${FONT_STYLE}
">
<div style="
font-size: 30px;
font-weight: 900;
color: ${posTextColor};
text-align: center;
${FONT_STYLE}
">
${pos}
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<div style="
display: flex;
align-items: center;
height: 30px;
gap: 2px;
padding-right: 2px;
">
${headshotTag}
${logoTag}
</div>
<div style="display: flex; flex-direction: column; line-height: 1.2;">
<div style="color: var(--primary-text-color); font-weight: 600; font-size: 13px;">${code} - ${name}</div>
<div style="font-size: 10px; color: rgba(255, 255, 255, 0.8);">${team}</div>
</div>
</div>
<div style="
font-size: 18px;
font-weight: 900;
color: var(--primary-text-color);
text-align: right;
${FONT_STYLE}
">
${points} <span style="font-size: 10px; font-weight: 600;">PTS</span>
</div>
</div>
`;
}).join('');
]]]
And the second card, for Constructors Standings:
and the code (also with some other extra hardcoded info)
type: custom:button-card
entity: sensor.f1_constructor_standings
show_name: false
show_state: false
show_icon: false
layout: custom
styles:
grid:
- grid-template-areas: |
"header"
"results"
- row-gap: 12px
card:
- padding: 12px
- border-radius: 8px
- background: linear-gradient(135deg, rgba(30, 30, 30, 0.8), rgba(10, 10, 10, 0.8))
- color: white
- box-shadow: 0px 4px 10px rgba(0,0,0,0.3)
custom_fields:
header:
- font-size: 20px
- font-weight: 600
- text-align: center
- padding-bottom: 6px
- border-bottom: 1px solid rgba(255,255,255,0.2)
- cursor: default
- font-family: f1regular
results:
- font-size: 14px
- line-height: 1.6
- display: flex
- flex-direction: column
- gap: 6px
- font-family: f1regular
- overflow-y: auto
- max-height: 650px
custom_fields:
header: |
[[[
return `
<div style="width: 100%; text-align: center;">
<div style="display: inline-flex; align-items: center; gap: 8px;">
<span style="font-weight: 600; font-family: 'f1regular';">🔧 CONSTRUCTORS STANDINGS</span>
</div>
</div>
`;
]]]
results: |
[[[
const results = entity.attributes?.constructor_standings || [];
const displayResults = results.slice(0, 25);
const FALLBACK_COLOR = '#888';
const FONT_STYLE = "font-family: 'f1regular';";
const constructorColors = {
"McLaren": "#FF8700",
"Mercedes": "#00D2BE",
"Red Bull": "#1E41FF",
"Ferrari": "#DC0000",
"Williams": "#005AFF",
"RB F1 Team": "#6699FF",
"Aston Martin": "#006F62",
"Haas F1 Team": "#B6BABD",
"Sauber": "#52E252",
"Alpine F1 Team": "#0090FF"
};
// Car Images (Using Formula 1 website links)
const carImages = {
"McLaren": "https://media.formula1.com/image/upload/c_lfill,w_3392/q_auto/v1740000000/common/f1/2025/mclaren/2025mclarencarright.webp",
"Mercedes": "https://media.formula1.com/image/upload/c_lfill,w_3392/q_auto/v1740000000/common/f1/2025/mercedes/2025mercedescarright.webp",
"Red Bull": "https://media.formula1.com/image/upload/c_lfill,w_3392/q_auto/v1740000000/common/f1/2025/redbullracing/2025redbullracingcarright.webp",
"Ferrari": "https://media.formula1.com/image/upload/c_lfill,w_3392/q_auto/v1740000000/common/f1/2025/ferrari/2025ferraricarright.webp",
"Williams": "https://media.formula1.com/image/upload/c_lfill,w_3392/q_auto/v1740000000/common/f1/2025/williams/2025williamscarright.webp",
"RB F1 Team": "https://media.formula1.com/image/upload/c_lfill,w_3392/q_auto/v1740000000/common/f1/2025/racingbulls/2025racingbullscarright.webp",
"Aston Martin": "https://media.formula1.com/image/upload/c_lfill,w_3392/q_auto/v1740000000/common/f1/2025/astonmartin/2025astonmartincarright.webp",
"Haas F1 Team": "https://media.formula1.com/image/upload/c_lfill,w_3392/q_auto/v1740000000/common/f1/2025/haas/2025haascarright.webp",
"Sauber": "https://media.formula1.com/image/upload/c_lfill,w_3392/q_auto/v1740000000/common/f1/2025/kicksauber/2025kicksaubercarright.webp",
"Alpine F1 Team": "https://media.formula1.com/image/upload/c_lfill,w_3392/q_auto/v1740000000/common/f1/2025/alpine/2025alpinecarright.webp"
};
const teamLogos = {
"Red Bull": "https://media.formula1.com/image/upload/c_lfill,w_48/q_auto/v1740000000/common/f1/2025/redbullracing/2025redbullracinglogo.webp",
"Ferrari": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/ferrari/2025ferrarilogo.webp",
"Mercedes": "https://media.formula1.com/image/upload/c_lfill,w_48/q_auto/v1740000000/common/f1/2025/mercedes/2025mercedeslogowhite.webp",
"McLaren": "https://media.formula1.com/image/upload/c_lfill,w_48/q_auto/v1740000000/common/f1/2025/mclaren/2025mclarenlogo.webp",
"Aston Martin": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/astonmartin/2025astonmartinlogowhite.webp",
"Alpine F1 Team": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/alpine/2025alpinelogo.webp",
"RB F1 Team": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/racingbulls/2025racingbullslogowhite.webp",
"Haas F1 Team": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/haas/2025haaslogo.webp",
"Williams": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/williams/2025williamslogo.webp",
"Sauber": "https://media.formula1.com/image/upload/c_fit,h_64/q_auto/v1740000000/common/f1/2025/kicksauber/2025kicksauberlogo.webp",
};
// Sizing remains the same for consistency
const LOGO_SIZE = '45px';
const CAR_IMG_WIDTH = '130px'; // New width for the car image
return displayResults.map((r) => {
const pos = r.position;
const name = r.Constructor?.name || 'Unknown';
const points = r.points || '0';
const color = constructorColors[name] || FALLBACK_COLOR;
const carImageUrl = carImages[name] || '';
const teamLogoUrl = teamLogos[name] || '';
const bgColor = color.length === 7 ? color + '1A' : color;
const posTextColor = color === FALLBACK_COLOR ? 'white' : color;
// Car Image Tag (New location, needs specific styling to fit)
const carImageTag = carImageUrl
? `<img src="${carImageUrl}" style="width: ${CAR_IMG_WIDTH}; height: auto; max-height: ${LOGO_SIZE}; object-fit: cover;">`
: '';
// Logo Tag
const logoTag = teamLogoUrl
? `<img src="${teamLogoUrl}" style="width: ${LOGO_SIZE}; height: auto; max-height: ${LOGO_SIZE};">`
: '';
return `
<div style="
display: grid;
grid-template-columns: 40px 1fr auto;
column-gap: 2px;
align-items: center;
background: ${bgColor};
padding: 1px 6px;
border-radius: 6px;
${FONT_STYLE}
">
<div style="
font-size: 30px;
font-weight: 900;
color: ${posTextColor};
text-align: center;
${FONT_STYLE}
">
${pos}
</div>
<div style="display: flex; align-items: center; gap: 8px;">
<div style="
display: flex;
align-items: center;
height: 30px;
gap: 2px;
padding-right: 2px;
">
${logoTag}
</div>
<div style="
display: flex;
flex-direction: column;
line-height: 1.2;
align-items: center; /* ADDED: Centers content horizontally in this column */
flex-grow: 1; /* Allows this div to take up available space */
">
<div style="color: var(--primary-text-color); font-weight: 600; font-size: 13px;">${name}</div>
<div style="font-size: 10px; color: rgba(255, 255, 255, 0.8);">Wins: ${r.wins}</div>
</div>
</div>
<div style="
display: flex;
align-items: center;
justify-content: flex-end; /* Pushes content to the right edge */
gap: 8px; /* Space between car and points */
font-size: 20px;
font-weight: 900;
color: var(--primary-text-color);
${FONT_STYLE}
">
${carImageTag}
<div>
${points} <span style="font-size: 10px; font-weight: 600;">PTS</span>
</div>
</div>
</div>
`;
}).join('');
]]]
For me they work great on a 10 inch Samsung tablet.
LE: noticed an error in the Drivers list… Tsunoda’s and Lawson’s teams are wrong. Swapped. ![]()
I’ve thought about that. It’s not directly available in any of the APIs, I think, but it’s on my to-do list to figure out, maybe during the winter break ![]()
Thanks for sharing your cards, they look awesome! Really cool what you’ve done with the provided sensors ![]()
Really really nice!! I have copied your code, but the pictures of the drivers don’t show and the font type seems off. Anything else you did?
that’ll be because you don’t have the font used and its used a fall back font