Skip to main content

Lemongrass

Open source car telemetry for 24 Hours of Lemons.

Requirements

  • Raspberry Pi running Raspberry Pi OS
  • PiSugar 3 UPS
  • USB OBD-II adapter
  • An InfluxDB instance running v2.x
  • Grafana to visualize the data

Services

This repo provides two long-running services that run on the pi:

Service Description
telem Monitors car data via OBD-II USB adapter
pisugar-monitor Monitors PiSugar 3 UPS

Deployment and orchestration (docker-compose, telegraf for OS metrics, etc.) should be managed in a separate IaC repository, not here.

Lap Data

Setup

  1. Get a Race Monitor API token https://www.race-monitor.com/Home/API

  2. Add your token to a .env file (see .env.sample) and source it:

source .env
  1. Get your race ID

We need a Race ID to get information for. Head to https://www.race-monitor.com/Live/Race while your race is live to get this easily from the URL.

Image of Race ID in URL bar

  1. Run the tool

Pull the latest image:

docker pull ghcr.io/wot-lemons/lemongrass:latest

Interactive TUI

Run lemongrass laps with no arguments in a terminal to open an interactive UI: search for a race by name or paste its numeric ID, then either watch it live (lap table, field leaderboard, and a log pane) or import a completed race's full field into InfluxDB. A race that isn't live yet offers a third choice — enter your car number and wait for the green flag; the UI checks every 10 seconds, starts monitoring by itself once the race goes live and your car appears in the timing feed, and keeps waiting until then. Once the race is live, press c to pick a different car from the live field — there is no field to choose from before the green flag. When a live race ends, the UI offers to run the authoritative final import.

The scripted forms are unchanged: lemongrass laps <race_id> [car_number] [-m] [-n] … behave exactly as before and are what cron and race-backfill use.

This Laps TUI is also reachable from the Home menu opened by running bare lemongrass (see Race Management).

Live Race

Docker — pass your credentials via an env file (see .env.sample). To pin to a specific version instead of latest, replace the tag (e.g. 1.2.3). Available tags are listed at ghcr.io/wot-lemons/lemongrass.

Note: CAR_NUMBER is required for live/monitor mode. Omit it for completed races to write laps for all competitors (fieldwide backfill).

docker run --rm -it --env-file .env ghcr.io/wot-lemons/lemongrass:latest lemongrass laps RACE_ID CAR_NUMBER -m -n

pip — install from PyPI and source your .env first (step 2 above):

pip install lemongrass
lemongrass laps RACE_ID CAR_NUMBER -m -n

uv — install from PyPI as a tool and source your .env first (step 2 above):

uv tool install lemongrass
lemongrass laps RACE_ID CAR_NUMBER -m -n

Or run ephemerally without installing:

uvx lemongrass laps RACE_ID CAR_NUMBER -m -n

Graceful exit: Press Ctrl-C at any time to stop monitoring cleanly (exits 130). The monitor also exits automatically when the race ends.

Starting before the green flag: add --wait-for-live to poll the race every 10 seconds and begin monitoring as soon as it goes live — useful when you set the capture up and then leave for the grid. It implies -m, requires CAR_NUMBER, and never times out; if the car isn't in the timing feed yet, it keeps waiting for it, unless the race ends before the car ever appears, which stops the wait and exits nonzero. Ctrl-C stops the wait (exits 130).

lemongrass laps RACE_ID CAR_NUMBER -n --wait-for-live

Real example:

docker run --rm -it --env-file .env ghcr.io/wot-lemons/lemongrass:latest lemongrass laps 166811 13 -m -n
2026-06-19 20:52:41,057 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Race/RaceDetails "HTTP/1.1 200 OK"
--------------------------------------------------------------------------------
Race 166811
Fast Friday Started: 2026-06-19 16:00:00
Seekonk Speedway   Ends: 2026-06-20 01:00:00
--------------------------------------------------------------------------------
2026-06-19 20:52:41,151 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Race/IsLive "HTTP/1.1 200 OK"
2026-06-19 20:52:41,152 - INFO - Race 166811 is currently live.
2026-06-19 20:52:41,251 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Live/GetSession "HTTP/1.1 200 OK"
--------------------------------------------------------------------------------
2026-06-19 20:52:41,252 - INFO - Current overall rankings.
--------------------------------------------------------------------------------
Empty DataFrame
Columns: [Pos., #, Class, Class Pos., Name, Laps, Transponder]
Index: []
--------------------------------------------------------------------------------
2026-06-19 20:52:41,365 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Live/GetRacer "HTTP/1.1 200 OK"
--------------------------------------------------------------------------------
Team: Jacob Burns Car Number: 13   Class: Legends Transponder: 13775638
Best Position: 2
Final Position: 1
Final Class Position: 1
Total Laps: 16
Best Lap: 6
Best Lap Time: 00:00:14.165
Total Time: 00:05:43.405
--------------------------------------------------------------------------------
Lap Position      LapTime FlagStatus    TotalTime
  1        8 00:00:14.572      Green 00:00:23.097
  2        5 00:00:14.458      Green 00:00:37.555
  3        4 00:00:14.389      Green 00:00:51.944
  4        3 00:00:14.258      Green 00:01:06.202
  5        2 00:00:14.313      Green 00:01:20.515
  6        2 00:00:14.165      Green 00:01:34.680
  7        2 00:00:14.258      Green 00:01:48.938
  8        1 00:00:14.391      Green 00:02:03.329
  9        1 00:00:14.437      Green 00:02:17.766
 10        1 00:00:14.299      Green 00:02:32.065
 11        1 00:00:14.248      Green 00:02:46.313
 12        1 00:00:14.260      Green 00:04:46.328
 13        1 00:00:14.194      Green 00:05:00.522
 14        1 00:00:14.251      Green 00:05:14.773
 15        1 00:00:14.322      Green 00:05:29.095
 16        1 00:00:14.310      Green 00:05:43.405
 17        1 00:00:14.291      Green 00:05:57.696
--------------------------------------------------------------------------------
2026-06-19 20:52:41,369 - INFO - Monitoring car 13...
--------------------------------------------------------------------------------
Lap Position      LapTime FlagStatus    TotalTime
  1        8 00:00:14.572      Green 00:00:23.097
  2        5 00:00:14.458      Green 00:00:37.555
  3        4 00:00:14.389      Green 00:00:51.944
  4        3 00:00:14.258      Green 00:01:06.202
  5        2 00:00:14.313      Green 00:01:20.515
  6        2 00:00:14.165      Green 00:01:34.680
  7        2 00:00:14.258      Green 00:01:48.938
  8        1 00:00:14.391      Green 00:02:03.329
  9        1 00:00:14.437      Green 00:02:17.766
 10        1 00:00:14.299      Green 00:02:32.065
 11        1 00:00:14.248      Green 00:02:46.313
 12        1 00:00:14.260      Green 00:04:46.328
 13        1 00:00:14.194      Green 00:05:00.522
 14        1 00:00:14.251      Green 00:05:14.773
 15        1 00:00:14.322      Green 00:05:29.095
 16        1 00:00:14.310      Green 00:05:43.405
 17        1 00:00:14.291      Green 00:05:57.696

Completed Race

You can retrieve info for a completed race too. Omit CAR_NUMBER to write laps for all competitors in the field (fieldwide backfill mode).

Docker:

# Single car
docker run --rm -it \
  --env-file .env \
  ghcr.io/wot-lemons/lemongrass:latest \
  lemongrass laps RACE_ID CAR_NUMBER

# Full field
docker run --rm -it \
  --env-file .env \
  ghcr.io/wot-lemons/lemongrass:latest \
  lemongrass laps RACE_ID

pip / uv:

lemongrass laps RACE_ID CAR_NUMBER   # single car
lemongrass laps RACE_ID              # full field

Persisting CSV output: lemongrass laps -o writes a .csv to the container's working directory (/data). The container runs as a non-root user, so that write stays inside the container and is lost on exit unless you mount a writable directory at /data. Bind mounts keep their host ownership, so pass --user to run as your host user — the CSV then lands in ./out owned by you:

mkdir -p out
docker run --rm -it --env-file .env \
  --user "$(id -u):$(id -g)" \
  -v "$(pwd)/out:/data" \
  ghcr.io/wot-lemons/lemongrass:latest \
  lemongrass laps RACE_ID CAR_NUMBER -o

Real example:

docker run --rm -it --env-file .env ghcr.io/wot-lemons/lemongrass:latest lemongrass laps 166429 852
2026-06-19 20:46:17,391 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Race/RaceDetails "HTTP/1.1 200 OK"
--------------------------------------------------------------------------------
Race 166429
The B.F.E. GP 2026 Started: 2026-06-12 10:00:00
High Plains Raceway   Ends: 2026-06-14 19:30:00
--------------------------------------------------------------------------------
2026-06-19 20:46:17,487 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Race/IsLive "HTTP/1.1 200 OK"
2026-06-19 20:46:17,488 - INFO - Race 166429 is not live. Monitor mode disabled.
2026-06-19 20:46:17,587 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Results/SessionsForRace "HTTP/1.1 200 OK"
2026-06-19 20:46:17,692 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Results/SessionDetails "HTTP/1.1 200 OK"
2026-06-19 20:46:17,797 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Results/SessionDetails "HTTP/1.1 200 OK"
2026-06-19 20:46:17,945 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Results/SessionDetails "HTTP/1.1 200 OK"
2026-06-19 20:46:17,948 - INFO - Rate limited: sleeping 59.34s [6/6 slots used over 60s window; oldest request 0.66s ago]
2026-06-19 20:47:17,422 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Results/SessionDetails "HTTP/1.1 200 OK"
2026-06-19 20:47:17,570 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Results/SessionDetails "HTTP/1.1 200 OK"
2026-06-19 20:47:17,794 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Results/SessionDetails "HTTP/1.1 200 OK"
2026-06-19 20:47:18,090 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Results/SessionDetails "HTTP/1.1 200 OK"
2026-06-19 20:47:18,314 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Results/SessionDetails "HTTP/1.1 200 OK"
2026-06-19 20:47:18,626 - INFO - HTTP Request: POST https://api.race-monitor.com/v2/Results/SessionDetails "HTTP/1.1 200 OK"
--------------------------------------------------------------------------------
2026-06-19 20:47:18,913 - INFO - Current overall rankings.
--------------------------------------------------------------------------------
Pos.   # Class  Class Pos.                             Name Laps Transponder
   1 852     A           1              Rusty Bottom Racing  351        D-34
   2   4     A           2         Sew So Fast - GC edition  349         C-2
   3 380     A           3                 Vice City Racing  349        D-20
   4   6     A           4               Stay Classy Racing  337         C-3
   5 177     B           1                     DadBodCarMod  336        D-11
   6  49     B           2             Broken Spokes Racing  334        C-20
   7 101     A           5            Smokey and the Bandit  333         D-1
   8  11     A           6          Whiskey + Doughnuts =É.  333      165002
   9 300     A           7                     World War Zx  331        D-14
  10 501     A           8            Vistabeam Racing Team  330        D-26
...
  75 779     B          33     Poorsche Club of America (R)   59        D-31
  76 333     B          34           Green Beret RacingÉ.II   53        D-19
  77  73     B          35                      Team HonDuh   23     5632016
  78  28     A          27             Liquid Mechanics (R)   11     6201404
  79 150     A          28                       NTD Racing    9         D-8
  80  35     C          17                         Passhark    8        C-13
  81  17     C          18                     Haiku Racing              C-6
  82  72                 1      Enforcement Motorsports (R)             C-25
--------------------------------------------------------------------------------
Team:        Car Number: 852  Class: A Transponder: D-34
Best Position: 7
Final Position: 1
Final Class Position: 1
Total Laps: 351
Best Lap: 214
Best Lap Time: 00:02:12.483
Total Time: 14:36:02.587
--------------------------------------------------------------------------------
Lap      LapTime Position FlagStatus    TotalTime
  1 00:02:25.450        6      Green 06:28:04.193
  2 00:02:24.449        5      Green 06:30:28.642
  3 00:02:24.332        5      Green 06:32:52.974
  4 00:02:26.745        5      Green 06:35:19.719
  5 00:02:23.508        5      Green 06:37:43.227
  6 00:02:23.444        4      Green 06:40:06.671
  7 00:02:24.004        3      Green 06:42:30.675
  8 00:02:43.345        3      Green 06:45:14.020
  9 00:36:18.582        7      Green 07:21:32.602
 10 00:02:23.425        7      Green 07:23:56.027
...
340 00:02:21.490        1      Green 14:09:37.216
341 00:02:26.332        1      Green 14:12:03.478
342 00:02:26.332        1      Green 14:14:29.810
343 00:02:24.485        1      Green 14:16:54.224
344 00:02:24.068        1      Green 14:19:18.222
345 00:02:21.403        1      Green 14:21:39.553
346 00:02:27.229        1      Green 14:24:06.782
347 00:02:28.195        1      Green 14:26:34.906
348 00:02:24.841        1      Green 14:28:59.674
349 00:02:20.867        1      Green 14:31:20.472
350 00:02:21.190        1      Green 14:33:41.592
351 00:02:21.065        1     Finish 14:36:02.587
--------------------------------------------------------------------------------

Race Management

The races subcommand provides tools for inspecting and managing race data stored in InfluxDB.

lemongrass races <subcommand> [args]

Interactive TUI

Run bare lemongrass with no arguments in a terminal to open a Home menu that routes to the Laps and Races tools. Run lemongrass races with no subcommand in a terminal to jump straight into the races browser: a checklist of stored races.

Key Action
a Select all races
i Invert the current selection
p Prune (delete) all checked races
d Diagnose the highlighted race — pick a car to compare RaceMonitor vs. InfluxDB lap counts
r Re-import the highlighted race
b Backfill — search RaceMonitor by terms and import a race
esc Back

Both interactive entry points require an interactive TTY (stdin/stdout attached to a terminal); non-interactively, or when a subcommand is given, lemongrass races <subcommand> behaves exactly as documented below. telem and pisugar-monitor remain CLI-only and have no TUI.

Subcommand Description
list Show all stored races with lap counts and schema status
prune RACE_ID... Delete all data for one or more races from InfluxDB
backfill Run historical backfill for all tracked races (delegates to lemongrass race-backfill; use --help for all options)
diagnose RACE_ID CAR_NUMBER Compare RaceMonitor vs InfluxDB lap counts for a specific car

Examples

# List all stored races and their schema version status
lemongrass races list

# Delete a race (prompts for confirmation)
lemongrass races prune 144185

# Delete multiple races at once, skipping confirmation
lemongrass races prune 144185 120037 --yes

# Diagnose a lap count mismatch for car 252 in race 144185
lemongrass races diagnose 144185 252

Backfill Options

The backfill subcommand delegates to lemongrass race-backfill and supports these flags:

Flag Description
--dry-run Print what would be backfilled without writing anything
--force Re-backfill every race, even those already complete and current
--upgrade-stored Re-process laps already in InfluxDB whose schema_version is older than current — faster than --force because it skips re-fetching from RaceMonitor
--validate Check that every expected race has metadata and at least one lap in InfluxDB
--start-date YYYY-MM-DD Only include races starting on/after this date (default: 2017-01-01)

Note: --upgrade-stored is mutually exclusive with --start-date and --validate; combine it with --force to also re-fetch races already at the current schema.

Session Tracking

All lap points written to InfluxDB include a session_id tag corresponding to the RaceMonitor session ID. In Flux queries you can filter by session_id to isolate specific race segments (e.g. Day 1 vs. Day 2). Session metadata is stored in the race_sessions bucket.

Configuration

lemongrass is configured by an optional TOML file named by the LEMONGRASS_CONFIG environment variable, falling back to built-in defaults. Secrets (INFLUX_TELEMETRY_TOKEN, RACEMONITOR_TOKENS) are supplied via the environment and referenced by *_env keys in the file — environment variables are not used for any non-secret setting. Copy lemongrass.toml.sample to get started. See docs/CONFIGURATION.md for the full key reference.

Contributing

For development setup, running the test suite, and testing against a local InfluxDB stack instead of prod, see CONTRIBUTING.md.

Upgrading from v1.x

As of v2.0.0, the individual entry points (laps, telem, race-backfill, pisugar-monitor, race-diagnose) were replaced by a single lemongrass command. If you have the old package installed, update and prefix commands with lemongrass:

Before After
laps RACE_ID CAR_NUMBER lemongrass laps RACE_ID CAR_NUMBER
telem lemongrass telem
race-backfill lemongrass race-backfill or lemongrass races backfill
pisugar-monitor lemongrass pisugar-monitor
race-diagnose lemongrass race-diagnose or lemongrass races diagnose

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

lemongrass-5.4.0.tar.gz (86.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

lemongrass-5.4.0-py3-none-any.whl (96.7 kB view details)

Uploaded Python 3

File details

Details for the file lemongrass-5.4.0.tar.gz.

File metadata

  • Download URL: lemongrass-5.4.0.tar.gz
  • Upload date:
  • Size: 86.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for lemongrass-5.4.0.tar.gz
Algorithm Hash digest
SHA256 faa1a11a4700db1ba5a1da8ade03312fde3a46cd72a5f9a04458c2667d735f00
MD5 c186c09ee0e0dcd8acb527cd867403da
BLAKE2b-256 c0ce69de55bf0fbd9bb960c268d366d01c40504f3f467097b53fcb285dce729c

See more details on using hashes here.

Provenance

The following attestation bundles were made for lemongrass-5.4.0.tar.gz:

Publisher: release-please.yml on WOT-Lemons/Lemongrass

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file lemongrass-5.4.0-py3-none-any.whl.

File metadata

  • Download URL: lemongrass-5.4.0-py3-none-any.whl
  • Upload date:
  • Size: 96.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for lemongrass-5.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 96bf29ebc9c4da32ec45a3a1bef8e5d75112e3aa81d351e5b208d8f4d7ad0644
MD5 cbd6bf2ae6d3ac16a1b33b949b058f74
BLAKE2b-256 2a5c2ce0119c02215c27f936d731eb1c9cbba42cdc551c8eb24fc794f1e328e0

See more details on using hashes here.

Provenance

The following attestation bundles were made for lemongrass-5.4.0-py3-none-any.whl:

Publisher: release-please.yml on WOT-Lemons/Lemongrass

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

7.1.3

2 files

7.1.2

2 files

7.1.1

2 files

7.1.0

2 files

7.0.0

2 files

6.0.0

2 files

5.4.2

2 files

5.4.1

2 files

This release

5.4.0 This release

2 files

5.3.1

2 files

5.3.0

2 files

5.2.1

2 files

5.2.0

2 files

5.1.0

2 files

5.0.1

2 files

5.0.0

2 files

4.0.0

2 files

3.1.0

2 files

3.0.2

2 files

3.0.1

2 files

3.0.0

2 files

2.1.1

2 files

2.1.0

2 files

2.0.1

2 files

2.0.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page