A comprehensive Home Assistant integration to track match results, schedules, and standings for basketball teams competing in FFBB (French Basketball Federation) championships, with no user account or private API key required. ๐ก๏ธ
โน๏ธ Good to know: This integration queries the public Directus API used by the official
competitions.ffbb.comweb app, reusing the same public, read-only access token and browser User-Agent as the official site. No personal account or credentials are required. It fetches fixtures, results, and pool standings in a single optimized request.
If you find this project useful, you can support its development ๐
- ๐ Full tracking of your favorite teams (Departmental, Regional, National)
- ๐ Complete match schedule natively synchronized in Home Assistant
- โฑ๏ธ Live next match details: date, opponent, home or away status
- ๐ Ready-to-use "Game day" and "Match in progress" binary sensors for simple automations, no templating needed
- ๐ One-click directions: direct Google Maps and Waze gym links generated in entity attributes
- ๐ Last played match: final score, opponent, and outcome (win, loss, draw)
- ๐ Pool standings and dynamic rank evolution tracking (+1, -2, 0) with persistence across restarts
- ๐ Recent form sensor summarizing the last 5 results (e.g.
W-W-L-W-D) with current streak - โก "Match finished" and "Rank change" event entities to trigger automations the instant something happens, not on every polling cycle
- ๐งฉ Ready-to-import automation blueprints for Telegram notifications, no templating required
- ๐ ๏ธ Three action services ready for automations and notification scripts (Telegram, pre-game alerts)
- ๐ง Automatic repair notification if a team can no longer be found (season rollover), pointing you straight to Reconfigure
- ๐ Fast setup: search by club name, official club code (e.g. NAQ0040141), or direct team URL copy-paste
- โ๏ธ Simple 2-minute installation via HACS
๐ Entities automatically created for each tracked team
๐ Match fixtures, venue address, and results directly in your calendar
Designed for basketball players, parents, coaches, and supporters wishing to integrate team schedules and results into their smart home:
- ๐ก๏ธ Zero account needed: No personal credentials or private tokens required; the integration utilizes the public token provided for official FFBB web apps.
- ๐ Instant GPS navigation: No more searching for gym addresses on game days; complete street addresses, postal codes, and direct GPS launch links are available in your dashboard and notification engines.
- ๐ Reliable rank tracking: Position changes (+1, -2, 0) are saved to Home Assistant permanent storage and persist across system restarts without losing track of previous standings.
- โก Lightweight and polite: Polling is centralized per pool to minimize requests and prevent unnecessary load on FFBB servers.
- ๐ท๏ธ Supported competitions: All teams and championships available on
competitions.ffbb.com(seniors, youth, departmental, regional, and national divisions). - โ๏ธ Required Home Assistant version: Version 2026.3.0 or higher.
- ๐ Internet connection: Required to fetch data from FFBB servers.
- ๐ Search methods:
- By club or city name (e.g., Basket Landes, Paris).
- By official club code (e.g., NAQ0040141).
- By full team URL copied from
competitions.ffbb.comor raw numeric engagement ID.
- ๐ Native Home Assistant Calendar: Browse the entire season in your calendar dashboard with tip-off times, round numbers, final scores, and gym details.
- ๐ Integrated GPS Navigation: Ready-to-use Waze and Google Maps deep links in sensor attributes to start navigation in one tap.
- ๐ Rank Evolution Sensor: Detects rank shifts between updates, featuring an adaptive dynamic icon (arrow up, arrow down, or neutral dash).
- ๐ Recent Form Sensor: Compact string of the last 5 results (e.g.
W-W-L-W-D), with win/loss/draw counts and the current streak in its attributes. - โก Event Entities for Instant Automations:
event.*_match_finishedfires exactly once per new result (win/loss/draw), andevent.*_rank_changedfires exactly once when the pool position moves โ both survive Home Assistant restarts without re-firing on old data, unlike triggering on a sensor's state. - ๐ Seamless Reconfiguration: Switch team or pool directly using the "Reconfigure" button without removing the entry or leaving orphaned devices behind.
- ๐ ๏ธ Dedicated Actions (Services):
ffbb_tracker.refresh: Triggers an immediate manual data update.ffbb_tracker.get_next_matches: Returns upcoming fixtures as a structured dictionary for automations.ffbb_tracker.get_standings: Returns full pool standings (points, wins, losses, played games).
- ๐ One-Click Manual Refresh: An explicit
buttonentity to trigger an on-demand update anytime without waiting for the next polling cycle. - โ๏ธ Dynamic Polling & Live Match Options: Adjust the regular polling interval (15 to 1440 minutes), enable fast polling on match days (2 to 15 minutes), and set the score-wait window.
While waiting for inclusion in the default HACS repository list, you can easily add this as a custom repository:
- Open HACS in your Home Assistant web interface.
- Click the 3 dots in the top-right corner and select Custom repositories.
- In the Repository field, paste:
https://github.com/Adrien40/ha-ffbb-tracker - In the Type dropdown, select Integration, then click Add.
- Once added, click Download on the integration card that appears (select the latest release).
- Restart Home Assistant completely.
- Go to Settings > Devices & services > Add integration, and search for FFBB Tracker.
- Download the latest release zip archive from the Releases page.
- Copy the
custom_components/ffbb_trackerfolder into your Home Assistantcustom_componentsdirectory. - Restart Home Assistant.
Each configured team creates a dedicated device exposing 12 sensors, 2 binary sensors, 2 event entities, 1 button, and 1 calendar:
| Entity | Class / Unit | Description |
|---|---|---|
| ๐ Refresh | Button | Manually triggers an immediate coordinator update. |
| ๐๏ธ Schedule Calendar | Calendar | Official calendar containing all scheduled and completed fixtures for the pool. |
| ๐ Game Day | Binary sensor | On whenever the tracked team has a match scheduled for today (local date). |
| ๐๏ธ Match in Progress | Binary sensor | On from shortly before kickoff until the result is published (or the waiting window expires). |
| โก Match Finished | Event | Fires once when a new match result is published, with opponent, score, point difference, venue (home/away), round, and gym details in event data. |
| โก Rank Change | Event | Fires once when the pool position actually moves, with the old and new position in event data (event.<team>_rank_changed). |
| ๐ Rank | Integer | Current position of the team in its pool (sensor.<team>_rank). (Includes full standings table in attributes) |
| ๐ Rank Evolution | Text | Position difference (+1, -2, 0) with dynamic icon (sensor.<team>_rank_evolution). |
| ๐ Recent Form | Text | Last 5 results as a compact string (e.g. W-W-L-W-D). (Includes win/loss/draw counts and current streak in attributes) |
| ๐ Last Match: Date | Timestamp | Date and time of the last played game. |
| ๐ฅ Last Match: Opponent | Text | Name of the last opponent faced. |
| ๐ Last Match: Result | Status | Outcome of the last match: Win (win), Loss (loss), or Draw (draw). |
| ๐ข Last Match: Score | Text | Formatted final score (e.g., 78 - 65). |
| ๐ Pool | Text | Official name of the pool and current championship phase. |
| ๐ Next Match: Date | Timestamp | Scheduled tip-off date and time of the upcoming match. |
| ๐ฅ Next Match: Opponent | Text | Name of the upcoming opponent. (Contains gym details, Maps and Waze URLs in attributes) |
| ๐ Next Match: Location | Text | Full formatted address of the gym where the game will take place. |
| ๐๏ธ Next Match: Venue | Status | Indicates whether the game is played at Home (home) or Away (away). |
The next_match_location and next_match_opponent entities expose attributes designed for travel planning:
gym_name: venue or sports facility namegym_address: street addressgym_city: municipalitygoogle_maps_url: direct Google Maps navigation URLwaze_url: direct Waze navigation launch link
next_match_opponent, next_match_venue_type, next_match_location, last_match_date, and last_match_opponent all expose:
team_logo_url: your tracked club's logo, as registered with the FFBBopponent_logo_url: the opposing club's logo
Both are None when a club has no logo on file with the FFBB (common for smaller clubs) โ always check for a value before using either in a template. The two *_opponent sensors also set entity_picture to the opponent's logo, so it shows up natively in the logbook, history, and most entity cards without any extra configuration.
These are plain image URLs pointing at the FFBB's own asset server โ there's no bundled Lovelace card to render them, since a generic card (picture-elements, button-card, mushroom, โฆ) driven by a short template does the job without adding a dependency to this integration. If you want a broken/missing logo to fall back to something branded instead of a blank image, copy this repo's custom_components/ffbb_tracker/brand/icon.png into your /config/www/ folder (making it available at /local/ffbb_tracker_icon.png) and point your card's onerror/fallback image at that path โ it's served locally by your own Home Assistant instance, so it keeps working even if the FFBB's servers are unreachable.
- Go to Settings > Devices & services.
- Click Add integration and search for FFBB Tracker.
- Fill in the search form:
- Club search: enter a few characters of the club or city name (e.g.,
BordeauxorBasket Landes). Select your club, then pick the team from the retrieved list. - Direct search: paste the team page URL copied from
competitions.ffbb.com(e.g.,https://competitions.ffbb.com/equipes/123456789) or provide the raw numeric engagement ID.
- Club search: enter a few characters of the club or city name (e.g.,
- Pool and championship details are detected and configured automatically.
๐ฑ View full mobile app automation example
alias: Basketball - Team Tracker (Companion)
description: >-
Eve reminder, schedule alerts, and interactive refresh via the mobile app
triggers:
- trigger: time
at: "19:00:00"
id: eve_reminder
- trigger: state
entity_id:
- sensor.my_team_next_match_date
- sensor.my_team_next_match_location
- sensor.my_team_next_match_opponent
id: schedule_alert
- trigger: event
event_type: mobile_app_notification_action
event_data:
action: refresh_match
id: callback_refresh
conditions: []
actions:
- if:
- condition: trigger
id: callback_refresh
then:
- action: button.press
target:
entity_id: button.my_team_refresh
- delay: "00:00:02"
- if:
- condition: trigger
id: schedule_alert
then:
- delay: "00:00:02"
- condition: template
value_template: |-
{% if trigger is not defined or trigger.id is not defined %}
true
{% elif trigger.id == 'callback_refresh' %}
true
{% elif trigger.id == 'eve_reminder' %}
{% set match_dt = as_datetime(states('sensor.my_team_next_match_date')) %}
{{ match_dt is not none and match_dt.astimezone().date() == (now().date() + timedelta(days=1)) }}
{% elif trigger.id == 'schedule_alert' %}
{{ trigger.from_state is not none and
trigger.to_state is not none and
trigger.to_state.state not in ['unknown', 'unavailable', ''] and
trigger.from_state.state != trigger.to_state.state }}
{% else %}
false
{% endif %}
- action: notify.mobile_app_smartphone
data:
title: ๐ My Team
message: >-
{%- set dt =
as_datetime(states('sensor.my_team_next_match_date')) -%}
{%- set days = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday',
'Saturday', 'Sunday'] -%}
{%- set months = ['January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December'] -%}
{%- set location = states('sensor.my_team_next_match_location') -%}
{%- set is_home = state_attr('sensor.my_team_next_match_location',
'is_home') -%}
{%- if trigger is defined and trigger.id is defined and trigger.id ==
'schedule_alert' -%}
{%- if trigger.from_state is not none and trigger.from_state.state not in ['unknown', 'unavailable', ''] -%}
{%- set status = 'โ ๏ธ Match updated' -%}
{%- else -%}
{%- set status = '๐ข New match scheduled' -%}
{%- endif -%}
{%- else -%}
{%- if dt is not none -%}
{%- set delta = (dt.astimezone().date() - now().date()).days -%}
{%- set status = '๐ฅ Match today!' if delta == 0 else ('๐ฅ Match tomorrow!' if delta == 1 else ('โณ Match in ' ~ delta ~ ' days!' if delta > 1 else '๐ Next match')) -%}
{%- else -%}
{%- set status = '๐ Next match' -%}
{%- endif -%}
{%- endif -%}
{{ status }}
๐ฅ Opponent: {{ states('sensor.my_team_next_match_opponent')
| title }}
๐
Date: {% if dt is not none %}{{ days[dt.weekday()] }}, {{ months[dt.month - 1] }}
{{ dt.day }}{% else %}Unknown date{% endif %}
โฐ Tip-off: {% if dt is not none %}{{ dt.strftime('%H:%M') }}{%
else %}Unknown{% endif %}
๐๏ธ Court: {% if is_home %}๐ Home{% else %}๐ Away{% endif
%}
๐ Location: {{ location | title if
has_value('sensor.my_team_next_match_location') else 'Not specified'
}}
data:
tag: basketball_match_notif
group: basketball_match
notification_icon: mdi:basketball
channel: Basketball
importance: high
persistent: true
sticky: true
clickAction: noAction
actions: >-
{% set gmaps = state_attr('sensor.my_team_next_match_location',
'google_maps_url') | default('', true) %} {% set waze_clean =
state_attr('sensor.my_team_next_match_location', 'waze_url') |
default('', true) | replace('+', '%20') %} {% set buttons =
[{'action': 'refresh_match', 'title': '๐
Refresh'}] %} {% if gmaps.startswith('http') %}
{% set buttons = buttons + [{'action': 'URI', 'title': '๐บ๏ธ Maps', 'uri': gmaps}] %}
{% endif %} {% if waze_clean.startswith('http') %}
{% set buttons = buttons + [{'action': 'URI', 'title': '๐ Waze', 'uri': waze_clean}] %}
{% endif %} {{ buttons }}
mode: restart
max_exceeded: silentPrefer clicking over writing YAML? Three automation blueprints ship in blueprints/automation/ffbb_tracker, built on the event entities above so they fire once per result/rank change and never replay on restart:
- Full Telegram notification pack: Matchday-eve reminder plus interactive buttons (Refresh, Maps, Waze) for one team.
- Match result notification: Telegram message with opponent, score, and venue as soon as a new result comes in.
- Rank change notification: Telegram message with the old and new pool position as soon as it moves.
- Engagement ID changes between seasons: The FFBB re-assigns a new internal engagement ID to each team every season. If a tracked team's entities stop updating and stay unavailable for several days while the season is clearly still active, the most likely cause is that the team's engagement ID has changed. When this persists for 3 days, the integration now raises a repair notification automatically (Settings > System > Repairs) pointing you to the fix. Either way, use Settings > Devices & services > FFBB Tracker > Reconfigure to search for the team again and pick it up under its new ID โ this keeps your existing automations and dashboard cards working, since the device and entity IDs are not affected by this operation.
- No official API: This integration relies on the public Directus endpoints used by the official web app rather than a documented, stable API. Breaking changes on the FFBB's side (schema changes, stricter bot filtering) can affect the integration without notice; see the Troubleshooting section and open an issue if something stops working.
- Go to Settings > Devices & services, open the FFBB Tracker integration, and remove each configured team (three-dot menu > Delete). This also removes the associated device and all its entities from the entity registry.
- If installed via HACS: open HACS > FFBB Tracker, and select Remove.
- If installed manually: delete the
custom_components/ffbb_trackerfolder from your Home Assistant configuration directory. - Restart Home Assistant.
No credentials, tokens, or external accounts are created by this integration, so there is nothing to revoke elsewhere.
โ ๏ธ Frequently Asked Questions
- No teams found when searching for a club: Some sports associations have not yet registered their rosters for the next phase, or the pool schedule has not been officially released by the committee or league.
- The GPS link directs to the town center instead of the gym: In smaller facilities, the municipality may not have provided a precise street address to the federation. In this case, the link combines the gym name and town name to optimize routing.
- The rank evolution sensor shows 0 after a restart: This is expected during initial setup; as soon as the next standings update occurs, the actual shift (+1, -1, etc.) will be computed and preserved.
๐ Home Assistant Lovelace Card: ha-ffbb-tracker-card
The integration is fully available in French and English
(configuration flow, entities, and calendar).
If you would like to see the integration translated into another language or contribute to a translation, feel free to open an issue or contact me directly on GitHub.
For bug reports or feature requests, please open an Issue on this repository.
This project is licensed under the GPLv3. It is an independent, open-source project and is not officially affiliated with the French Basketball Federation (FFBB). Use of this software is at your own discretion.
Developed with โค๏ธ by @Adrien40
