Formatter, previewer, and publisher for Vestaboard devices
Project description
vesta
A small Python formatter / previewer / publisher for Vestaboard devices.
semantic input → board layout → terminal preview → optional publish
What it does
- Formats structured data (JSON, CSV, plain text) for Vestaboard
- Supports multiple device profiles:
- flagship: 6 × 22
- note: 3 × 15
- Previews output in the terminal before sending
- Publishes via Vestaboard Cloud API or Local API
Templates
| Template | Input | Behaviour |
|---|---|---|
text |
string | Wrapped and centered text |
kv |
JSON object | Key / value rows. Applies _pct/_curr suffix formatting; values otherwise treated as strings. |
data |
JSON object or array | Label/value rows (object) or columnar table (array). Applies suffix formatting and color indicators. |
auto |
any | Picks the best renderer based on input shape (default) |
metrics and table are accepted as aliases for data.
In most cases --template can be omitted — auto selects kv, data, or text based on input shape. Specify --template kv explicitly when you want the flush-right value layout for a JSON object instead of the metrics layout.
CSV is auto-detected — no --template flag needed.
Key suffixes
Field name suffixes control formatting automatically. The suffix is stripped from the label on the board.
| Suffix | Effect |
|---|---|
_pct / _percent |
formats value as 3.2% |
_curr |
formats value as $84.2K |
{
"revenue_curr": 84210.50,
"sessions": 10823,
"growth_pct": 12.4
}
Renders as:
REVENUE $84.2K
SESSIONS 10.8K
GROWTH 12.4%
Color indicators
Color is driven by semantic tone, not raw cell placement. The trailing colored tile is added automatically when a tone can be determined.
Auto-detection: inferred when a field name contains change, delta, or diff and the value is numeric:
- positive → green
- negative → red
- zero → white
_pct / _percent control value formatting only (appends %) — they do not trigger auto-detection. Use _style to add color to percentage fields explicitly.
Explicit tone: set via _style:
{
"score": 91.2,
"_style": { "score": "good" }
}
Accepted tone names: good, bad, warn, info, neutral, muted,
or a direct color: green, red, yellow, blue, white, black, violet, orange.
Use "none" to suppress the color tile on a specific field:
{
"delta": 0,
"_style": { "delta": "none" }
}
Range-based tone: specify good and bad thresholds for a 4-step gradient. Direction is implicit — wherever good sits numerically is the green end:
{
"bounce_rate": 68.4,
"_style": { "bounce_rate": {"good": 30, "bad": 80} }
}
| Zone | Color | Position |
|---|---|---|
| 1st quarter | green | 0–25% toward bad |
| 2nd quarter | yellow | 25–50% |
| 3rd quarter | orange | 50–75% |
| 4th quarter | red | 75–100% (and beyond) |
_style and other _-prefixed keys are never shown on the board.
Use --explain to see which fields got indicators and why:
cat metrics.json | vesta render --preview-only --explain
Layout flags
--title TEXT
Adds a title row at the top with colored tile bookends. Tries 2 tiles each side, falls back to 1 if the text is long.
--title-color COLOR[,COLOR,COLOR]|none
Color of the bookend tiles. Defaults to white. Pass none for a plain centered title with no tiles. Pass up to 3 comma-separated colors to use multiple tiles — the right side mirrors the left: --title-color red,blue,orange places red blue orange on the left and orange blue red on the right. Falls back through fewer tiles if the title text is too long to fit.
--subtitle TEXT|time
Optional second row below the title, with a single tile bookend on each side (same color as title). Use the special value time to insert the current time. You can also embed the subtitle directly in the title using a newline — --title $'Weather\nSan Francisco' — and the second line becomes the subtitle automatically (explicit --subtitle takes precedence).
--separator [PATTERN]
Adds a full-width row of colored tiles below the title block (after subtitle if present). Used alone, defaults to solid white. Accepts:
| Pattern | Result |
|---|---|
| (omitted) | solid white |
white, blue, red, … |
solid named color |
rainbow |
R O Y G B V cycling |
red,black |
alternating colors |
echo '{"temp":"68F","hum_pct":42,"co2":"820","noise":"38"}' | \
vesta render --template kv --title "HOME" --separator rainbow --preview-only
┌────────────── flagship 6x22 ───────────────┐
│████ H O M E ████│
│████████████████████████████████████████████│
│T E M P 6 8 F │
│H U M 4 2 % │
│C O 2 8 2 0 │
│N O I S E 3 8 D B │
└────────────────────────────────────────────┘
vesta render --input testdata/home.json --columns 2 \
--title "HOME" --title-color white --subtitle time --preview-only
┌────────────── flagship 6x22 ───────────────┐
│████ H O M E ████│
│██ 1 2 : 0 9 A ██│
│T E M P 6 8 F H U M 4 2 % │
│C O 2 8 2 0 N O I S E 3 8 D B │
│D O O R S S H U T L I G H T O N │
│H E A T O F F F A N S O N │
└────────────────────────────────────────────┘
--columns [1|2] (kv layout)
Pack two key-value pairs per row instead of one. Each column is sized independently to its own content, which creates a natural gap between columns. Color indicators from _style or auto-detection still apply: left-column tiles appear in the gap; right-column tiles appear at the board's right edge. When using auto template (the default), passing --columns 2 with a JSON object automatically selects kv layout.
echo '{"now":"62F","rain_pct":0,"high":"66F","low":"48F"}' | \
vesta render --columns 2 --preview-only
┌────────────── flagship 6x22 ───────────────┐
│N O W 6 2 F R A I N 0 % │
│H I G H 6 6 F L O W 4 8 F │
│ │
│ │
│ │
│ │
└────────────────────────────────────────────┘
Falls back to --columns 1 with a warning if the content is too wide for the profile.
--align [left|center|right]
For metrics (JSON object): aligns the content block horizontally as a unit. Default is left.
For tables (JSON array or CSV): default is center (compact block, centered). left and right spread columns edge-to-edge with equal inter-column gaps — first column anchored to the chosen edge, last column anchored to the opposite edge.
--valign [top|center]
Vertical alignment of the content block. Default is top. Use center for breathing room when you have fewer rows than the board height.
--timestamp
Adds the current time (10:01A, 9:30P) to the bottom-right corner. Silently skipped if there isn't room. Use --force-timestamp to place it regardless, overwriting content if needed.
--tz
IANA timezone for the timestamp, e.g. America/New_York. Defaults to local system time.
--profile [flagship|note]
Board profile. Auto-detected from API grid dimensions when publishing. Defaults to flagship.
Example usage
Text:
echo '"hello world"' | vesta render --preview-only
┌────────────── flagship 6x22 ───────────────┐
│ │
│ │
│ H E L L O W O R L D │
│ │
│ │
│ │
└────────────────────────────────────────────┘
Key/value:
echo '{"temp": "72F", "wind": "12mph"}' | vesta render --template kv --preview-only
┌────────────── flagship 6x22 ───────────────┐
│T E M P 7 2 F │
│W I N D 1 2 M P H │
│ │
│ │
│ │
│ │
└────────────────────────────────────────────┘
Key/value 2-col with title:
vesta render --input testdata/home.json --columns 2 \
--title "HOME" --subtitle time --preview-only
┌────────────── flagship 6x22 ───────────────┐
│████ H O M E ████│
│██ 1 2 : 0 9 A ██│
│T E M P 6 8 F H U M 4 2 % │
│C O 2 8 2 0 N O I S E 3 8 D B │
│D O O R S S H U T L I G H T O N │
│H E A T O F F F A N S O N │
└────────────────────────────────────────────┘
CSV table (auto-detected, centered by default):
vesta render --input scores.csv --preview-only
┌────────────── flagship 6x22 ───────────────┐
│ N A M E S C O R E R A N K │
│ A L I C E 9 8 1 │
│ B O B 8 7 2 │
│ C A R O L 7 6 3 │
│ D A V E 6 1 4 │
│ │
└────────────────────────────────────────────┘
Use --align left or --align right to spread columns edge-to-edge instead.
Metrics with color indicators:
echo '{
"revenue_curr": 84210.50,
"sessions": 10823,
"conversion_pct": 3.2,
"bounce_rate_pct": 68.4,
"_style": {
"revenue_curr": "good",
"conversion_pct": {"good": 8, "bad": 2},
"bounce_rate_pct": {"good": 30, "bad": 80}
}
}' | vesta render --valign center --align center --timestamp --preview-only
┌────────────── flagship 6x22 ───────────────┐
│ │
│ R E V E N U E $ 8 4 . 2 K ██ │
│ S E S S I O N S 1 0 . 8 K │
│ C O N V E R S I O N 3 . 2 % ██ │
│ B O U N C E R A T E 6 8 . 4 % ██ │
│ 9 : 3 4 P │
└────────────────────────────────────────────┘
Note profile:
vesta render --input testdata/metrics_note.json --profile note --preview-only
┌───────── note 3x15 ──────────┐
│T E M P $ 7 2 . 0 0 │
│H U M I D I T Y 5 4 % │
│C H A N G E - 2 . 1 % ██│
└──────────────────────────────┘
Get raw character codes (for direct API use):
cat data.json | vesta render --json-only
Publishing
Cloud API:
cat data.json | vesta post-cloud --token $VESTABOARD_TOKEN
Local API:
cat data.json | vesta post-local --api-key $VESTABOARD_LOCAL_API_KEY
Preview current board state:
vesta read-cloud
VESTABOARD_TOKEN is read from the environment. Board profile is auto-detected from the grid dimensions returned by the API. Pass --profile to override.
Re-render a saved board:
cat data.json | vesta render --json-only > saved.json
cat saved.json | vesta render --preview-only
Running the examples
./run_examples.sh
Runs all bundled examples against local test data. Requires no API credentials.
Installation
pip install vestaboard-tools
Or run directly from source with uv:
uv run vesta.py render
Why this exists
Hitting the Vestaboard API directly is straightforward. The harder part is making structured data fit well on a small fixed-size grid — compacting numbers, handling suffixes, previewing locally, and reusing layouts across scripts and data sources. This project is mainly that rendering layer.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file vestaboard_tools-0.2.1.tar.gz.
File metadata
- Download URL: vestaboard_tools-0.2.1.tar.gz
- Upload date:
- Size: 26.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d135c954316d2c156ec82f97942ae4d87360ad9a60f1ad16ea8dfd22d8686017
|
|
| MD5 |
de570b3675329be867a86bac7b291b9f
|
|
| BLAKE2b-256 |
daf290af8d1c1f6acbdcf36c37be4489ec6d8b8feb7e4d4bbd69ce807a9e7cd6
|
Provenance
The following attestation bundles were made for vestaboard_tools-0.2.1.tar.gz:
Publisher:
publish.yml on q/vesta
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vestaboard_tools-0.2.1.tar.gz -
Subject digest:
d135c954316d2c156ec82f97942ae4d87360ad9a60f1ad16ea8dfd22d8686017 - Sigstore transparency entry: 1297489475
- Sigstore integration time:
-
Permalink:
q/vesta@792490da9974828fa97b5c7c8c7fe82ca23fe881 -
Branch / Tag:
refs/tags/0.2.1 - Owner: https://github.com/q
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@792490da9974828fa97b5c7c8c7fe82ca23fe881 -
Trigger Event:
release
-
Statement type:
File details
Details for the file vestaboard_tools-0.2.1-py3-none-any.whl.
File metadata
- Download URL: vestaboard_tools-0.2.1-py3-none-any.whl
- Upload date:
- Size: 22.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0eee5f74867c8b8a2bc91fa56a67a9b8647c7b802cd759cf12e0c9ef230eefd8
|
|
| MD5 |
db645a79a5fbb541b61a95541ee767e7
|
|
| BLAKE2b-256 |
f80864b95221b1fee1fb21db043047ffbdaa2d70c5c7606fc4b762fa5e2a6a7f
|
Provenance
The following attestation bundles were made for vestaboard_tools-0.2.1-py3-none-any.whl:
Publisher:
publish.yml on q/vesta
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
vestaboard_tools-0.2.1-py3-none-any.whl -
Subject digest:
0eee5f74867c8b8a2bc91fa56a67a9b8647c7b802cd759cf12e0c9ef230eefd8 - Sigstore transparency entry: 1297489550
- Sigstore integration time:
-
Permalink:
q/vesta@792490da9974828fa97b5c7c8c7fe82ca23fe881 -
Branch / Tag:
refs/tags/0.2.1 - Owner: https://github.com/q
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@792490da9974828fa97b5c7c8c7fe82ca23fe881 -
Trigger Event:
release
-
Statement type: