led-ticker-crypto 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- led_ticker_crypto-0.2.0/.gitignore +9 -0
- led_ticker_crypto-0.2.0/.pre-commit-config.yaml +16 -0
- led_ticker_crypto-0.2.0/CLAUDE.md +185 -0
- led_ticker_crypto-0.2.0/LICENSE +21 -0
- led_ticker_crypto-0.2.0/Makefile +18 -0
- led_ticker_crypto-0.2.0/PKG-INFO +167 -0
- led_ticker_crypto-0.2.0/README.md +140 -0
- led_ticker_crypto-0.2.0/docs/crypto-coingecko.gif +0 -0
- led_ticker_crypto-0.2.0/docs/demo.toml +20 -0
- led_ticker_crypto-0.2.0/docs/superpowers/plans/2026-06-11-coingecko-phase3-review.md +305 -0
- led_ticker_crypto-0.2.0/pyproject.toml +53 -0
- led_ticker_crypto-0.2.0/src/led_ticker_crypto/__init__.py +7 -0
- led_ticker_crypto-0.2.0/src/led_ticker_crypto/_colors.py +26 -0
- led_ticker_crypto-0.2.0/src/led_ticker_crypto/_ticker_render.py +142 -0
- led_ticker_crypto-0.2.0/src/led_ticker_crypto/coingecko.py +338 -0
- led_ticker_crypto-0.2.0/tests/conftest.py +14 -0
- led_ticker_crypto-0.2.0/tests/test_autolookup.py +95 -0
- led_ticker_crypto-0.2.0/tests/test_coingecko.py +476 -0
- led_ticker_crypto-0.2.0/tests/test_format_price.py +76 -0
- led_ticker_crypto-0.2.0/tests/test_import_purity.py +29 -0
- led_ticker_crypto-0.2.0/tests/test_smoke.py +19 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
repos:
|
|
2
|
+
- repo: https://github.com/astral-sh/ruff-pre-commit
|
|
3
|
+
rev: v0.4.0
|
|
4
|
+
hooks:
|
|
5
|
+
- id: ruff
|
|
6
|
+
args: [--fix]
|
|
7
|
+
- id: ruff-format
|
|
8
|
+
- repo: local
|
|
9
|
+
hooks:
|
|
10
|
+
- id: pyright
|
|
11
|
+
name: pyright
|
|
12
|
+
entry: uv run pyright src
|
|
13
|
+
language: system
|
|
14
|
+
pass_filenames: false
|
|
15
|
+
always_run: true
|
|
16
|
+
stages: [pre-push]
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
Guidance for Claude Code when working in **led-ticker-crypto**, an external plugin for
|
|
4
|
+
[led-ticker](https://github.com/JamesAwesome/led-ticker).
|
|
5
|
+
|
|
6
|
+
`README.md` is the source of truth for the user-facing surface (config options, install,
|
|
7
|
+
coin-spec styles, rate limits). This file keeps the **load-bearing invariants** a contributor
|
|
8
|
+
must respect, plus navigation aids. When a fact here and the README disagree about *how a
|
|
9
|
+
feature works*, the README wins; this file is the source of truth for *how to keep it working*.
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
This plugin contributes, via the `led_ticker.plugins` entry point, a single widget:
|
|
14
|
+
|
|
15
|
+
- `crypto.coingecko` — live crypto price Container from the CoinGecko v3 API. Cycles one
|
|
16
|
+
`_CoinTicker` "story" per configured coin (the engine reads `feed_stories` via
|
|
17
|
+
`_expand_sources` on every pass). Shows symbol + price (adaptive precision) + 24h change,
|
|
18
|
+
trend-colored green/red/gray. One batched `/simple/price` fetch per update interval.
|
|
19
|
+
|
|
20
|
+
The entry-point name `crypto` is the plugin namespace, so the config `type` is
|
|
21
|
+
`crypto.coingecko` (see `register()` in `__init__.py`).
|
|
22
|
+
|
|
23
|
+
## Commands
|
|
24
|
+
|
|
25
|
+
led-ticker is **not on PyPI**; it resolves from a sibling checkout via
|
|
26
|
+
`[tool.uv.sources] led-ticker = { path = "../led-ticker", editable = true }`. CI checks out
|
|
27
|
+
`led-ticker` next to this repo using a read-only deploy key (`LED_TICKER_DEPLOY_KEY`). The
|
|
28
|
+
rgbmatrix stub is vendored at the monorepo root (`tests/stubs/`) and put on the import path
|
|
29
|
+
by the **root** `pyproject.toml` (pytest `pythonpath` + pyright `extraPaths`), so the stub is
|
|
30
|
+
importable headless from the workspace root — no sibling `../led-ticker` path is involved.
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
uv sync --extra dev # install deps (needs ../led-ticker checked out)
|
|
34
|
+
uv run pytest -q # full suite (asyncio_mode = "auto")
|
|
35
|
+
make lint # lint (monorepo root); or: uv run ruff check plugins/crypto
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Python **3.14+** only.
|
|
39
|
+
|
|
40
|
+
## Package layout
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
src/led_ticker_crypto/
|
|
44
|
+
__init__.py # register(api) → api.widget("coingecko")(CoinGeckoMonitor)
|
|
45
|
+
coingecko.py # CoinGeckoMonitor (Container): start(), update(), _build_coins,
|
|
46
|
+
# _resolve_symbols; _CoinTicker: per-coin story with draw()
|
|
47
|
+
_ticker_render.py # draw_price_ticker(): shared price-ticker renderer ported from core's
|
|
48
|
+
# coinbase widget; _format_price(); _ConstantColor; make_default_font_color
|
|
49
|
+
_colors.py # UP_TREND_COLOR / DOWN_TREND_COLOR / NEUTRAL_TREND_COLOR via lazy_palette
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
`register(api)` (in `__init__.py`):
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
def register(api):
|
|
56
|
+
api.widget("coingecko")(CoinGeckoMonitor)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Load-bearing invariants
|
|
60
|
+
|
|
61
|
+
Each rule must hold when modifying the named files.
|
|
62
|
+
|
|
63
|
+
**Import only the public surface** — every `led_ticker` import MUST come from `led_ticker.plugin`,
|
|
64
|
+
never `led_ticker.<internal>`. Enforced by `tests/test_import_purity.py`, which AST-walks every
|
|
65
|
+
source file (catches `from`-imports *and* `import led_ticker.x` forms, not just a text grep).
|
|
66
|
+
Intra-package imports (`from led_ticker_crypto._colors import …`) are fine. If you need a core
|
|
67
|
+
symbol that isn't on `led_ticker.plugin.__all__`, that's a core API change — raise it upstream,
|
|
68
|
+
don't reach around the surface.
|
|
69
|
+
|
|
70
|
+
**Python 3.14 / PEP 649** — no `from __future__ import annotations` anywhere (same rule as core).
|
|
71
|
+
Bare `tuple[int, int, int]` annotations are fine.
|
|
72
|
+
|
|
73
|
+
**`CoinGeckoMonitor` is a Container** — it exposes `feed_stories: list[_CoinTicker]` (one story
|
|
74
|
+
per coin). The engine reads `feed_stories` via `_expand_sources` on every pass through the
|
|
75
|
+
section, so live updates surface within at most one cycle. A single-coin config produces a
|
|
76
|
+
one-story container — the same code path, no special case. Never snapshot `feed_stories` into a
|
|
77
|
+
cycle iterator at section build time; that was the longboi stale-display pattern.
|
|
78
|
+
|
|
79
|
+
**`update()` uses ONE batched fetch with comma-joined ids** — multiple coin ids MUST be joined
|
|
80
|
+
into a single `ids` string (`ids=bitcoin,ethereum,dogecoin`). Passing a Python list would make
|
|
81
|
+
aiohttp emit repeated `ids=bitcoin&ids=ethereum` query parameters, which CoinGecko rejects for
|
|
82
|
+
more than one id. The comment in `update()` documents this constraint explicitly; do not change
|
|
83
|
+
the join to a list.
|
|
84
|
+
|
|
85
|
+
**Non-200 responses raise** — `update()` logs a warning (including `retry-after` if present),
|
|
86
|
+
then calls `response.raise_for_status()` so `run_monitor_loop`'s exponential backoff engages.
|
|
87
|
+
A 429 or any error body must NEVER be parsed as price data. This handles the CoinGecko free-tier
|
|
88
|
+
rate limit (~5 calls/min keyless) and any transient API error.
|
|
89
|
+
|
|
90
|
+
**`_format_price` adaptive precision** — coins ≥ $1 (or exactly $0) use the historical
|
|
91
|
+
`f"{value:,.4f}"` form (4 decimals, thousands-separated). Sub-dollar coins get extra decimals
|
|
92
|
+
computed as `min(12, max(4, 3 - floor(log10(abs(value)))))` so a coin at ~$0.0000046 renders
|
|
93
|
+
as `0.0000046` rather than collapsing to `0.0000`. Do not revert this to a fixed `.4f` format.
|
|
94
|
+
|
|
95
|
+
**`symbols` auto-lookup is unique-or-error** — `_resolve_symbols` queries the full
|
|
96
|
+
`/coins/list` response. For each requested symbol: exactly one match → `(symbol.upper(), id)`;
|
|
97
|
+
zero matches → `ValueError`; multiple matches → `ValueError` listing all candidate ids and
|
|
98
|
+
telling the user to set `symbol_id`/`symbol_ids` to disambiguate. Input order is preserved.
|
|
99
|
+
The `/coins/list` fetch only happens when `symbols` is non-empty; `symbol_ids`-only configs
|
|
100
|
+
skip it entirely.
|
|
101
|
+
|
|
102
|
+
**`_build_coins` assembles and deduplicates** — order is: legacy `symbol`+`symbol_id` → each
|
|
103
|
+
entry in `symbol_ids` → `symbols` (auto-resolved). Deduplication is by coin_id, keeping first
|
|
104
|
+
occurrence. Raises if the result is empty (the same message as `validate_config`).
|
|
105
|
+
|
|
106
|
+
**Optional `x-cg-demo-api-key` header** — `api_key` is sourced from the TOML field first, then
|
|
107
|
+
falls back to the `COINGECKO_API_KEY` environment variable (`os.getenv`). When non-empty, the
|
|
108
|
+
key is sent as `x-cg-demo-api-key` in all CoinGecko requests (both the `/coins/list` startup
|
|
109
|
+
fetch and the per-update `/simple/price` fetch). When empty, no key header is sent. No key is
|
|
110
|
+
required for a single low-frequency widget.
|
|
111
|
+
|
|
112
|
+
**`font_color` plumbing in `coingecko.py`** — `_CoinTicker.__attrs_post_init__` normalises the
|
|
113
|
+
raw config value: `None` → `make_default_font_color()` (yellow `_ConstantColor`); a plain
|
|
114
|
+
`Color` (not a `ColorProvider`) → `_ConstantColor(color)`. After `__attrs_post_init__`,
|
|
115
|
+
`self.font_color` is always a `ColorProvider`. `draw()` passes `frame_for("font_color")` so
|
|
116
|
+
animated providers (rainbow, shimmer) animate across engine ticks. The 24h change segment uses
|
|
117
|
+
`_get_change_color` and ignores `font_color`; this is intentional — change is always
|
|
118
|
+
trend-colored.
|
|
119
|
+
|
|
120
|
+
**One INFO log per successful `update()`** — the Container contract: a silent log stream after
|
|
121
|
+
startup signals the background task died. `update()` emits one INFO line per call showing
|
|
122
|
+
updated/total counts and the coin ids — never the raw API response.
|
|
123
|
+
|
|
124
|
+
**`start()` accepts `update_interval`** — this parameter is consumed by `start()` before the
|
|
125
|
+
`attrs` constructor, so it does NOT appear in `attrs.fields(cls)` and is filtered out of the
|
|
126
|
+
`**kwargs` forwarding. Any future parameter that belongs to the monitor lifecycle (not the
|
|
127
|
+
widget state) should follow the same pattern: accept in `start()`, don't pass through to
|
|
128
|
+
`cls(...)`.
|
|
129
|
+
|
|
130
|
+
**This is a faithful port of core's CoinGecko renderer** — `_ticker_render.draw_price_ticker`
|
|
131
|
+
was ported from `led_ticker.widgets.crypto.coinbase._draw_price_ticker` (coinbase was removed
|
|
132
|
+
from core; the renderer traveled with this plugin). Pixel-identity to the original was proven
|
|
133
|
+
during extraction (see PR history); `tools/compare_render.py` served that one-time validation
|
|
134
|
+
purpose and was retired after core's renderer was removed in led-ticker#188. Do not change
|
|
135
|
+
rendering logic in `_ticker_render.py` without confirming the diff is intentional.
|
|
136
|
+
|
|
137
|
+
**Port adaptations in `_ticker_render.py`** — these are deliberate, documented deviations from
|
|
138
|
+
how the original code was written that must NOT be reverted:
|
|
139
|
+
|
|
140
|
+
- `draw_text` is called in the public absolute-return form:
|
|
141
|
+
`cursor_pos = draw_text(canvas, font, text, x, y, color)` — not the core-internal
|
|
142
|
+
`cursor_pos += draw_text(...)` form. The result is pixel-identical for plain text.
|
|
143
|
+
- `compute_cursor`'s `center` parameter is keyword-only at the call site.
|
|
144
|
+
- The default `font_color` is yellow `(255, 255, 0)`, matching core's `DEFAULT_COLOR`.
|
|
145
|
+
- `_ConstantColor` is a local reproduction of core's private class (which is not on the public
|
|
146
|
+
surface). It wraps a plain `Color` so a `font_color = [r,g,b]` config routes through the same
|
|
147
|
+
`color_for` interface as effect providers.
|
|
148
|
+
|
|
149
|
+
**Font constants** — `FONT_LABEL` (`7x13`) and `FONT_DELTA` (`6x10`) are resolved once at import
|
|
150
|
+
via `resolve_font`. `FONT_VALUE` and `FONT_VALUE_SMALL` alias `FONT_DEFAULT` / `FONT_SMALL` from
|
|
151
|
+
`led_ticker.plugin` — these are BDF faces (`6x12` / `5x8`). The price font auto-downgrades to
|
|
152
|
+
`FONT_VALUE_SMALL` when the price string exceeds 10 characters (long decimals and large values).
|
|
153
|
+
|
|
154
|
+
**Trend palette is lazy** — `_colors.py` uses `colors.lazy_palette` (PEP 562 `__getattr__`),
|
|
155
|
+
so importing the module is a no-op against the rgbmatrix `graphics` library. In-module code
|
|
156
|
+
(e.g. inside `_ticker_render.py`) accesses them as module-level names
|
|
157
|
+
(`UP_TREND_COLOR`, `DOWN_TREND_COLOR`, `NEUTRAL_TREND_COLOR`), which triggers the lazy resolver
|
|
158
|
+
on first access. Do not call `_trend_palette(name)` directly from outside `_colors.py`.
|
|
159
|
+
|
|
160
|
+
## Tests / CI
|
|
161
|
+
|
|
162
|
+
`uv run pytest -q` runs the suite (`tests/`):
|
|
163
|
+
|
|
164
|
+
- `test_import_purity.py` — AST tripwire (public-surface-only). Treat a failure as a contract
|
|
165
|
+
violation, not a test to relax.
|
|
166
|
+
- `test_smoke.py` — loads the plugin through led-ticker's real plugin loader and asserts
|
|
167
|
+
`crypto.coingecko` registers under the `crypto` namespace (entry-point wiring guard).
|
|
168
|
+
- `test_coingecko.py` — behavior coverage: price formatting, change coloring, `font_color`
|
|
169
|
+
normalization, `draw()` routing, `update()` logging contract, multi-coin batching, symbol
|
|
170
|
+
resolution, rate-limit error handling.
|
|
171
|
+
|
|
172
|
+
`tools/compare_render.py` — standalone comparison tool used during extraction to assert pixel
|
|
173
|
+
identity between the ported renderer and core's original. Retired after core's renderer was
|
|
174
|
+
removed in led-ticker#188; see PR history for the comparison baseline.
|
|
175
|
+
|
|
176
|
+
CI is the monorepo's single root path-filtered per-member matrix (`.github/workflows/ci.yml`):
|
|
177
|
+
it checks out led-ticker as a sibling (deploy key), Python 3.14, `uv sync --extra dev`, then
|
|
178
|
+
runs ruff check, ruff format --check, pyright, and pytest for the changed member.
|
|
179
|
+
|
|
180
|
+
## Adding to the plugin
|
|
181
|
+
|
|
182
|
+
Register the class in `register()` in `__init__.py` (`api.widget`); it becomes `crypto.<name>`.
|
|
183
|
+
Import any core dependency from `led_ticker.plugin` only, and keep the import-purity test green.
|
|
184
|
+
If the new widget shares the price-ticker layout (symbol + price + change), reuse
|
|
185
|
+
`_ticker_render.draw_price_ticker` rather than duplicating the draw logic.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 James Awesome
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
.PHONY: dev test lint format typecheck
|
|
2
|
+
|
|
3
|
+
dev: ## Install dev deps + pre-commit hooks
|
|
4
|
+
uv sync --extra dev
|
|
5
|
+
uv run pre-commit install
|
|
6
|
+
uv run pre-commit install --hook-type pre-push
|
|
7
|
+
|
|
8
|
+
test: ## Run tests with coverage
|
|
9
|
+
uv run pytest --cov=src --cov-report=term-missing
|
|
10
|
+
|
|
11
|
+
lint: ## Ruff lint
|
|
12
|
+
uv run ruff check src tests
|
|
13
|
+
|
|
14
|
+
format: ## Ruff format
|
|
15
|
+
uv run ruff format src tests
|
|
16
|
+
|
|
17
|
+
typecheck: ## Pyright
|
|
18
|
+
uv run pyright src
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: led-ticker-crypto
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Crypto-price widgets for led-ticker (CoinGecko).
|
|
5
|
+
Project-URL: Homepage, https://docs.ledticker.dev
|
|
6
|
+
Project-URL: Repository, https://github.com/JamesAwesome/led-ticker-plugins
|
|
7
|
+
Project-URL: Issues, https://github.com/JamesAwesome/led-ticker-plugins/issues
|
|
8
|
+
Author-email: James Awesome <james@morelli.nyc>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
15
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
16
|
+
Requires-Python: >=3.14
|
|
17
|
+
Requires-Dist: aiohttp
|
|
18
|
+
Requires-Dist: led-ticker-core>=2.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pre-commit>=4.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: pyright>=1.1; extra == 'dev'
|
|
22
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
23
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
24
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
25
|
+
Requires-Dist: ruff>=0.4; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# led-ticker-crypto
|
|
29
|
+
|
|
30
|
+
A cryptocurrency price ticker **plugin** for [led-ticker](https://github.com/JamesAwesome/led-ticker), backed by the free CoinGecko v3 API. It contributes a `crypto.coingecko` **Container widget** that cycles one scrolling price line per configured coin — configure one coin for a single ticker, or a list to cycle through several.
|
|
31
|
+
|
|
32
|
+
Each line shows the coin symbol, current price (adaptive precision — sub-dollar tokens never collapse to `0.0000`), and 24-hour percent change. The change value is trend-colored: green for positive, red for negative, gray for neutral — readable at a glance on any panel.
|
|
33
|
+
|
|
34
|
+

|
|
35
|
+
|
|
36
|
+
## Prerequisites
|
|
37
|
+
|
|
38
|
+
- A working [led-ticker](https://github.com/JamesAwesome/led-ticker) install.
|
|
39
|
+
- Internet access on the Pi (the widget calls the CoinGecko public API).
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
The widget auto-registers via the `led_ticker.plugins` entry point — once the package is installed, no `[plugins]` config change is needed.
|
|
44
|
+
|
|
45
|
+
**Into a containerized led-ticker (recommended):** add this package to `config/requirements-plugins.txt` (copy it from `config/requirements-plugins.example.txt`, which already lists it), then rebuild:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# in your led-ticker checkout
|
|
49
|
+
cp config/requirements-plugins.example.txt config/requirements-plugins.txt
|
|
50
|
+
docker compose up -d --build
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
That example file lists every first-party plugin — trim the live copy to just the ones you want. The crypto line is:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
git+https://github.com/JamesAwesome/led-ticker-crypto.git@main
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Standalone (a venv that already has led-ticker):**
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
pip install "git+https://github.com/JamesAwesome/led-ticker-crypto.git@main"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
led-ticker isn't on PyPI, so this path only works where led-ticker is already installed. See the led-ticker [Plugins docs](https://docs.ledticker.dev/plugins/) for the constraint-based install the Docker image uses.
|
|
66
|
+
|
|
67
|
+
## Configuration
|
|
68
|
+
|
|
69
|
+
Reference the widget in a playlist section by `type = "crypto.coingecko"`. Three coin-spec styles are supported — choose the one that fits your workflow:
|
|
70
|
+
|
|
71
|
+
### Single coin (legacy style)
|
|
72
|
+
|
|
73
|
+
```toml
|
|
74
|
+
[[playlist.section.widget]]
|
|
75
|
+
type = "crypto.coingecko"
|
|
76
|
+
symbol = "BTC"
|
|
77
|
+
symbol_id = "bitcoin"
|
|
78
|
+
currency = "USD"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Multiple coins by explicit CoinGecko id
|
|
82
|
+
|
|
83
|
+
```toml
|
|
84
|
+
[[playlist.section.widget]]
|
|
85
|
+
type = "crypto.coingecko"
|
|
86
|
+
symbol_ids = ["bitcoin", "ethereum", "dogecoin"]
|
|
87
|
+
currency = "USD"
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Each id in `symbol_ids` is also used as the on-panel label (uppercased). Use `symbol_ids` when you know the exact CoinGecko ids and want to skip the startup lookup.
|
|
91
|
+
|
|
92
|
+
### Multiple coins by ticker symbol (auto-resolved)
|
|
93
|
+
|
|
94
|
+
```toml
|
|
95
|
+
[[playlist.section.widget]]
|
|
96
|
+
type = "crypto.coingecko"
|
|
97
|
+
symbols = ["BTC", "ETH", "SOL"]
|
|
98
|
+
currency = "USD"
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`symbols` are resolved at startup against CoinGecko's `/coins/list` endpoint. Resolution is **unique-or-error**: if a symbol matches more than one CoinGecko id, the widget fails at startup and lists the candidate ids — use `symbol_id` or `symbol_ids` to disambiguate.
|
|
102
|
+
|
|
103
|
+
You can combine all three styles in one widget; duplicates (by coin id) are silently dropped, keeping the first occurrence.
|
|
104
|
+
|
|
105
|
+
New to led-ticker configs? The [first-config tutorial](https://docs.ledticker.dev/tutorial/02-first-config/) walks through the overall structure — the blocks above show just the crypto-specific keys.
|
|
106
|
+
|
|
107
|
+
### Finding `symbol_id`
|
|
108
|
+
|
|
109
|
+
`symbol_id` is CoinGecko's internal coin identifier (e.g. `"bitcoin"`, `"ethereum"`, `"solana"`, `"dogecoin"`). It's the `id` field in CoinGecko's `/coins/list` endpoint and appears in a coin's page URL: `coingecko.com/en/coins/<id>`.
|
|
110
|
+
|
|
111
|
+
Use `symbol_id` (or `symbol_ids`) when:
|
|
112
|
+
- You want to skip the startup `/coins/list` lookup that `symbols` triggers.
|
|
113
|
+
- A symbol is ambiguous — the startup error lists the candidates and tells you which ids to use.
|
|
114
|
+
|
|
115
|
+
### Options
|
|
116
|
+
|
|
117
|
+
| Option | Type | Default | Description |
|
|
118
|
+
|--------|------|---------|-------------|
|
|
119
|
+
| `symbol` | string | — | Single-coin label shown on the panel (e.g. `"BTC"`). Requires `symbol_id`. |
|
|
120
|
+
| `symbol_id` | string | — | Single-coin CoinGecko id (e.g. `"bitcoin"`). Requires `symbol`. |
|
|
121
|
+
| `symbols` | list of strings | — | Ticker symbols auto-resolved to CoinGecko ids at startup (e.g. `["BTC", "ETH"]`). Unique-or-error. |
|
|
122
|
+
| `symbol_ids` | list of strings | — | CoinGecko ids used directly, uppercased as panel labels (e.g. `["bitcoin", "ethereum"]`). |
|
|
123
|
+
| `currency` | string | `"USD"` | Fiat currency code (e.g. `"USD"`, `"EUR"`). |
|
|
124
|
+
| `update_interval` | int | `300` | Seconds between CoinGecko fetches (5 min default). |
|
|
125
|
+
| `center` | bool | `true` | Center the ticker on the canvas when it fits; scroll when it overflows. |
|
|
126
|
+
| `padding` | int | `6` | Horizontal spacing (logical px) between the symbol, price, and change segments. |
|
|
127
|
+
| `hold_time` | float | `0.0` | Seconds to hold each coin's ticker before the engine cycles to the next. |
|
|
128
|
+
| `bg_color` | `[r,g,b]` | none | Background fill behind the ticker. |
|
|
129
|
+
| `font_color` | `[r,g,b]` / string / table | yellow `(255,255,0)` | Color for the symbol and price. Accepts any led-ticker color provider (e.g. `"rainbow"`, `{style="shimmer", ...}`). The 24h change color is always trend-colored and ignores this field. |
|
|
130
|
+
|
|
131
|
+
At least one of `symbol`+`symbol_id`, `symbols`, or `symbol_ids` must be specified — the widget fails at config validation otherwise.
|
|
132
|
+
|
|
133
|
+
### Rate limits & API key
|
|
134
|
+
|
|
135
|
+
CoinGecko's keyless free tier allows roughly **5 requests per minute**. For a single low-frequency widget at the default 5-minute interval this is plenty; if you add more coins or shorten `update_interval`, you may hit HTTP 429. When that happens the widget logs the rate-limit clearly and lets its monitor loop back off — it will not show stale garbage.
|
|
136
|
+
|
|
137
|
+
To raise the limit, create a free account at [coingecko.com](https://www.coingecko.com) and get a **Demo API key** (no credit card required). Supply it via the `COINGECKO_API_KEY` environment variable (the only supported path — config-file secrets are intentionally not accepted):
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
export COINGECKO_API_KEY="CG-xxxxxxxxxxxxxxxxxxxx"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
The key is sent as the `x-cg-demo-api-key` request header.
|
|
144
|
+
|
|
145
|
+
## Development
|
|
146
|
+
|
|
147
|
+
led-ticker isn't on PyPI, so this plugin resolves it from a sibling checkout. Clone both side by side:
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
~/projects/.../led-ticker
|
|
151
|
+
~/projects/.../led-ticker-crypto
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
uv sync --extra dev # resolves led-ticker from ../led-ticker
|
|
156
|
+
uv run pytest -q
|
|
157
|
+
uv run ruff check src tests
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
> **Note:** led-ticker's `graphics` surface works headless via its bundled stub, but the full `RGBMatrix`/canvas test stub lives in led-ticker's `tests/stubs/` and isn't shipped. This repo's tests put it on the path via `pyproject.toml`'s `[tool.pytest.ini_options] pythonpath = ["../led-ticker/tests/stubs"]`.
|
|
161
|
+
|
|
162
|
+
The plugin imports only the public `led_ticker.plugin` surface — `tests/test_import_purity.py` enforces it.
|
|
163
|
+
|
|
164
|
+
## Links
|
|
165
|
+
|
|
166
|
+
- [led-ticker](https://github.com/JamesAwesome/led-ticker) — the core project
|
|
167
|
+
- [Docs site](https://docs.ledticker.dev) · [Plugin system](https://docs.ledticker.dev/plugins/)
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# led-ticker-crypto
|
|
2
|
+
|
|
3
|
+
A cryptocurrency price ticker **plugin** for [led-ticker](https://github.com/JamesAwesome/led-ticker), backed by the free CoinGecko v3 API. It contributes a `crypto.coingecko` **Container widget** that cycles one scrolling price line per configured coin — configure one coin for a single ticker, or a list to cycle through several.
|
|
4
|
+
|
|
5
|
+
Each line shows the coin symbol, current price (adaptive precision — sub-dollar tokens never collapse to `0.0000`), and 24-hour percent change. The change value is trend-colored: green for positive, red for negative, gray for neutral — readable at a glance on any panel.
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
- A working [led-ticker](https://github.com/JamesAwesome/led-ticker) install.
|
|
12
|
+
- Internet access on the Pi (the widget calls the CoinGecko public API).
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
The widget auto-registers via the `led_ticker.plugins` entry point — once the package is installed, no `[plugins]` config change is needed.
|
|
17
|
+
|
|
18
|
+
**Into a containerized led-ticker (recommended):** add this package to `config/requirements-plugins.txt` (copy it from `config/requirements-plugins.example.txt`, which already lists it), then rebuild:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# in your led-ticker checkout
|
|
22
|
+
cp config/requirements-plugins.example.txt config/requirements-plugins.txt
|
|
23
|
+
docker compose up -d --build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
That example file lists every first-party plugin — trim the live copy to just the ones you want. The crypto line is:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
git+https://github.com/JamesAwesome/led-ticker-crypto.git@main
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Standalone (a venv that already has led-ticker):**
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install "git+https://github.com/JamesAwesome/led-ticker-crypto.git@main"
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
led-ticker isn't on PyPI, so this path only works where led-ticker is already installed. See the led-ticker [Plugins docs](https://docs.ledticker.dev/plugins/) for the constraint-based install the Docker image uses.
|
|
39
|
+
|
|
40
|
+
## Configuration
|
|
41
|
+
|
|
42
|
+
Reference the widget in a playlist section by `type = "crypto.coingecko"`. Three coin-spec styles are supported — choose the one that fits your workflow:
|
|
43
|
+
|
|
44
|
+
### Single coin (legacy style)
|
|
45
|
+
|
|
46
|
+
```toml
|
|
47
|
+
[[playlist.section.widget]]
|
|
48
|
+
type = "crypto.coingecko"
|
|
49
|
+
symbol = "BTC"
|
|
50
|
+
symbol_id = "bitcoin"
|
|
51
|
+
currency = "USD"
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Multiple coins by explicit CoinGecko id
|
|
55
|
+
|
|
56
|
+
```toml
|
|
57
|
+
[[playlist.section.widget]]
|
|
58
|
+
type = "crypto.coingecko"
|
|
59
|
+
symbol_ids = ["bitcoin", "ethereum", "dogecoin"]
|
|
60
|
+
currency = "USD"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Each id in `symbol_ids` is also used as the on-panel label (uppercased). Use `symbol_ids` when you know the exact CoinGecko ids and want to skip the startup lookup.
|
|
64
|
+
|
|
65
|
+
### Multiple coins by ticker symbol (auto-resolved)
|
|
66
|
+
|
|
67
|
+
```toml
|
|
68
|
+
[[playlist.section.widget]]
|
|
69
|
+
type = "crypto.coingecko"
|
|
70
|
+
symbols = ["BTC", "ETH", "SOL"]
|
|
71
|
+
currency = "USD"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`symbols` are resolved at startup against CoinGecko's `/coins/list` endpoint. Resolution is **unique-or-error**: if a symbol matches more than one CoinGecko id, the widget fails at startup and lists the candidate ids — use `symbol_id` or `symbol_ids` to disambiguate.
|
|
75
|
+
|
|
76
|
+
You can combine all three styles in one widget; duplicates (by coin id) are silently dropped, keeping the first occurrence.
|
|
77
|
+
|
|
78
|
+
New to led-ticker configs? The [first-config tutorial](https://docs.ledticker.dev/tutorial/02-first-config/) walks through the overall structure — the blocks above show just the crypto-specific keys.
|
|
79
|
+
|
|
80
|
+
### Finding `symbol_id`
|
|
81
|
+
|
|
82
|
+
`symbol_id` is CoinGecko's internal coin identifier (e.g. `"bitcoin"`, `"ethereum"`, `"solana"`, `"dogecoin"`). It's the `id` field in CoinGecko's `/coins/list` endpoint and appears in a coin's page URL: `coingecko.com/en/coins/<id>`.
|
|
83
|
+
|
|
84
|
+
Use `symbol_id` (or `symbol_ids`) when:
|
|
85
|
+
- You want to skip the startup `/coins/list` lookup that `symbols` triggers.
|
|
86
|
+
- A symbol is ambiguous — the startup error lists the candidates and tells you which ids to use.
|
|
87
|
+
|
|
88
|
+
### Options
|
|
89
|
+
|
|
90
|
+
| Option | Type | Default | Description |
|
|
91
|
+
|--------|------|---------|-------------|
|
|
92
|
+
| `symbol` | string | — | Single-coin label shown on the panel (e.g. `"BTC"`). Requires `symbol_id`. |
|
|
93
|
+
| `symbol_id` | string | — | Single-coin CoinGecko id (e.g. `"bitcoin"`). Requires `symbol`. |
|
|
94
|
+
| `symbols` | list of strings | — | Ticker symbols auto-resolved to CoinGecko ids at startup (e.g. `["BTC", "ETH"]`). Unique-or-error. |
|
|
95
|
+
| `symbol_ids` | list of strings | — | CoinGecko ids used directly, uppercased as panel labels (e.g. `["bitcoin", "ethereum"]`). |
|
|
96
|
+
| `currency` | string | `"USD"` | Fiat currency code (e.g. `"USD"`, `"EUR"`). |
|
|
97
|
+
| `update_interval` | int | `300` | Seconds between CoinGecko fetches (5 min default). |
|
|
98
|
+
| `center` | bool | `true` | Center the ticker on the canvas when it fits; scroll when it overflows. |
|
|
99
|
+
| `padding` | int | `6` | Horizontal spacing (logical px) between the symbol, price, and change segments. |
|
|
100
|
+
| `hold_time` | float | `0.0` | Seconds to hold each coin's ticker before the engine cycles to the next. |
|
|
101
|
+
| `bg_color` | `[r,g,b]` | none | Background fill behind the ticker. |
|
|
102
|
+
| `font_color` | `[r,g,b]` / string / table | yellow `(255,255,0)` | Color for the symbol and price. Accepts any led-ticker color provider (e.g. `"rainbow"`, `{style="shimmer", ...}`). The 24h change color is always trend-colored and ignores this field. |
|
|
103
|
+
|
|
104
|
+
At least one of `symbol`+`symbol_id`, `symbols`, or `symbol_ids` must be specified — the widget fails at config validation otherwise.
|
|
105
|
+
|
|
106
|
+
### Rate limits & API key
|
|
107
|
+
|
|
108
|
+
CoinGecko's keyless free tier allows roughly **5 requests per minute**. For a single low-frequency widget at the default 5-minute interval this is plenty; if you add more coins or shorten `update_interval`, you may hit HTTP 429. When that happens the widget logs the rate-limit clearly and lets its monitor loop back off — it will not show stale garbage.
|
|
109
|
+
|
|
110
|
+
To raise the limit, create a free account at [coingecko.com](https://www.coingecko.com) and get a **Demo API key** (no credit card required). Supply it via the `COINGECKO_API_KEY` environment variable (the only supported path — config-file secrets are intentionally not accepted):
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
export COINGECKO_API_KEY="CG-xxxxxxxxxxxxxxxxxxxx"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The key is sent as the `x-cg-demo-api-key` request header.
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
led-ticker isn't on PyPI, so this plugin resolves it from a sibling checkout. Clone both side by side:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
~/projects/.../led-ticker
|
|
124
|
+
~/projects/.../led-ticker-crypto
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
uv sync --extra dev # resolves led-ticker from ../led-ticker
|
|
129
|
+
uv run pytest -q
|
|
130
|
+
uv run ruff check src tests
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
> **Note:** led-ticker's `graphics` surface works headless via its bundled stub, but the full `RGBMatrix`/canvas test stub lives in led-ticker's `tests/stubs/` and isn't shipped. This repo's tests put it on the path via `pyproject.toml`'s `[tool.pytest.ini_options] pythonpath = ["../led-ticker/tests/stubs"]`.
|
|
134
|
+
|
|
135
|
+
The plugin imports only the public `led_ticker.plugin` surface — `tests/test_import_purity.py` enforces it.
|
|
136
|
+
|
|
137
|
+
## Links
|
|
138
|
+
|
|
139
|
+
- [led-ticker](https://github.com/JamesAwesome/led-ticker) — the core project
|
|
140
|
+
- [Docs site](https://docs.ledticker.dev) · [Plugin system](https://docs.ledticker.dev/plugins/)
|
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# render-duration: 18
|
|
2
|
+
# Demo config for the crypto.coingecko widget (multi-coin ticker).
|
|
3
|
+
# Explicit symbol_ids for a deterministic render; shiba-inu showcases the
|
|
4
|
+
# adaptive sub-cent price formatting.
|
|
5
|
+
[display]
|
|
6
|
+
rows = 16
|
|
7
|
+
cols = 32
|
|
8
|
+
chain_length = 5
|
|
9
|
+
default_scale = 1
|
|
10
|
+
brightness = 60
|
|
11
|
+
|
|
12
|
+
[[playlist.section]]
|
|
13
|
+
mode = "swap"
|
|
14
|
+
loop_count = 1
|
|
15
|
+
hold_time = 3.0
|
|
16
|
+
|
|
17
|
+
[[playlist.section.widget]]
|
|
18
|
+
type = "crypto.coingecko"
|
|
19
|
+
symbol_ids = ["bitcoin", "ethereum", "shiba-inu"]
|
|
20
|
+
currency = "USD"
|