- C# 98%
- Dockerfile 2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .gitea | ||
| cache | ||
| doc | ||
| Entities | ||
| .gitignore | ||
| AwTrix.cs | ||
| Collector.cs | ||
| Config.cs | ||
| config.json | ||
| Dockerfile | ||
| Espn.cs | ||
| Looper.cs | ||
| Program.cs | ||
| README.md | ||
| Rest.cs | ||
| SoccerUlanzi.csproj | ||
| SoccerUlanzi.sln | ||
| TeamConfig.cs | ||
| Tracker.cs | ||
SoccerUlanzi
A tool that displays live soccer scores on external devices such as the Ulanzi TC001 or any device with AwTrix3 installed.
Data source
All fixtures and scores come from ESPN's public site API - the same source the Home Assistant TeamTracker integration uses. Two endpoints are enough:
| Purpose | Endpoint |
|---|---|
| Current or next fixture of a team, across all competitions | /soccer/all/teams/{teamId} |
| Score and clock of a known fixture | /soccer/all/summary?event={eventId} |
Because both use the all scope, league games, cup rounds and european nights are picked up automatically -
there is no list of competitions to maintain.
Note: ESPN answers
403to requests without aUser-Agentas well as to browser-like ones (Mozilla/...).Espn.cstherefore sends a plain HTTP client identifier and falls back to another one should it ever be rejected.
Configuration
Configure the application by editing the config.json file with the following options:
Basic Configuration
-
Teams: Array of teams you want to track, each with an
Idand aName. The name is only used for logging, so an emoji is fine. To find a team ID:- Go to the ESPN website and search for your team
- Select the team from the search results
- The team ID is in the URL
Example: For St. Pauli (ID: 270), the URL is:
https://www.espn.co.uk/football/club/_/id/270/st-pauli -
DeviceIp: IP address of your Ulanzi display device
-
Uninstall: Set to
trueto remove all created score apps from the display -
DisplayDelayOnGames: Duration (in seconds) the app stays visible on the display during live games
-
DisplayDelayWhenOff: Duration (in seconds) the app stays visible on the display when no live games are active (default: 7 seconds when display starts for the first time)
-
DelayOnActiveGames: How frequently game data is updated during active games (format: "HH:MM:SS")
-
DelayWhenIdle: How frequently game data is updated when no games are active (format: "HH:MM:SS")
Example Configuration
{
"Teams": [
{ "Id": "270", "Name": "St. Pauli" },
{ "Id": "137", "Name": "Werder" },
{ "Id": "127", "Name": "HSV" }
],
"DeviceIp": "192.168.178.10",
"Uninstall": false,
"DisplayDelayOnGames": 40,
"DisplayDelayWhenOff": 7,
"DelayOnActiveGames": "00:00:10",
"DelayWhenIdle": "00:15:00"
}
How a game is followed
For every team the application keeps track of exactly one fixture at a time:
- It asks ESPN which fixture is currently relevant for the team and remembers its event id in
cache/tracked.json, so a restart does not lose a result that is still being displayed. - From then on only that fixture is polled - every
DelayOnActiveGameswhile the game is running, everyDelayWhenIdleotherwise. - The result stays on the display for 24 hours after kick off, then the next fixture is picked up. A preview appears 24 hours before kick off.
Postponed and abandoned games are recognised and removed from the display instead of being shown forever.
Team Logos
Team logos are automatically downloaded from ESPN. If you want to use custom logos or if an image is missing, you can place your own logos in the ./cache directory.
Logo file naming convention:
<teamid>.png- Standard 8x8 logo, used for the score display<teamid>.6x6.png- Smaller 6x6 logo, used for the pre-match preview
Example for St. Pauli (Team ID: 270):
270.png270.6x6.png
Missing crests
Not every club has a logo at ESPN. This is not a question of the division - it simply depends on whether ESPN maintains a crest for that club at all. As of the 2026/27 season:
| Competition | Clubs with a crest |
|---|---|
| 1. Bundesliga | 18 / 18 |
| 2. Bundesliga | 17 / 18 - missing: Energie Cottbus (ID 123) |
| DFB-Pokal, 1st round | 45 / 64 - missing: 3. Liga and below, plus Cottbus |
So a regular league fixture can be affected too, not just cup ties against amateur sides. Those games are
still displayed, just without the missing crest. Dropping <teamid>.png and <teamid>.6x6.png into ./cache
fills the gap - the same mechanism used for the custom logos above.
Note: The example images below show custom logos - St. Pauli's logo has been replaced with a skull and HSV's logo with the red eye of Sauron.
Display Examples
The application shows different displays depending on the game state:
Pre-Match Display
Displayed 24 hours before a match starts. Shows the game time and team logo.
Live Match Display
Shown during active games. Features:
- Home team logo (left side)
- Guest team logo (right side)
- Current score (center)
- Progress bar at top indicating minutes played (90-minute scale)
Match Paused
Same layout as live match, but the progress bar turns yellow when the game is paused.
Overtime Display
During overtime periods (e.g., cup games), the progress bar becomes red to indicate extended play time (120-minute scale).
Match Finished
After the final whistle, the progress bar disappears, showing only the final score and team logos.
Installation & Usage
Running with Docker
-
Build the Docker image:
docker build -t soccer-ulanzi . -
Run the container:
docker run -v $(pwd)/config.json:/app/config.json -v $(pwd)/cache:/app/cache soccer-ulanziThis command:
- Mounts your local
config.jsonfile into the container - Mounts the local
cachedirectory for logo storage - Starts the application
- Mounts your local
-
Run in the background:
docker run -d --name soccer-ulanzi -v $(pwd)/config.json:/app/config.json -v $(pwd)/cache:/app/cache soccer-ulanzi
Running with .NET
If you have .NET 9.0 installed locally:
-
Restore dependencies:
dotnet restore -
Run the application:
dotnet run
Prerequisites
- Ensure your
config.jsonfile is properly configured with your team IDs and device IP - Make sure your Ulanzi device is accessible on the network




