diff --git a/README.md b/README.md index 3905ae7d..88a1e7f2 100644 --- a/README.md +++ b/README.md @@ -105,7 +105,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \ | Plugin | Description | Preview | |--------|-------------|---------| -| [Stocks Ticker](./plugins/ledmatrix-stocks/) | Real-time stock & crypto prices with charts | | +| [Stocks Ticker](./plugins/ledmatrix-stocks/) | Real-time stock & crypto prices with charts | ledmatrix-stocks on an LED panel | | [Stock News](./plugins/stock-news/) | Financial headlines from RSS feeds | | ### Time & Calendar (4) diff --git a/docs/assets/ledmatrix-stocks/display-mode.png b/docs/assets/ledmatrix-stocks/display-mode.png new file mode 100644 index 00000000..3e800fc2 Binary files /dev/null and b/docs/assets/ledmatrix-stocks/display-mode.png differ diff --git a/docs/assets/ledmatrix-stocks/gain-loss.png b/docs/assets/ledmatrix-stocks/gain-loss.png new file mode 100644 index 00000000..0b995880 Binary files /dev/null and b/docs/assets/ledmatrix-stocks/gain-loss.png differ diff --git a/docs/assets/ledmatrix-stocks/hero.png b/docs/assets/ledmatrix-stocks/hero.png new file mode 100644 index 00000000..3db0e895 Binary files /dev/null and b/docs/assets/ledmatrix-stocks/hero.png differ diff --git a/docs/assets/ledmatrix-stocks/mock.json b/docs/assets/ledmatrix-stocks/mock.json new file mode 100644 index 00000000..2fdb536d --- /dev/null +++ b/docs/assets/ledmatrix-stocks/mock.json @@ -0,0 +1,686 @@ +{ + "stock_data_AAPL": { + "symbol": "AAPL", + "name": "AAPL", + "price": 231.44, + "change": 1.82, + "change_percent": 0.79, + "open": 229.62, + "price_history": [ + { + "timestamp": "2026-09-02T09:00:00", + "price": 229.62 + }, + { + "timestamp": "2026-09-02T09:10:00", + "price": 229.89 + }, + { + "timestamp": "2026-09-02T09:20:00", + "price": 230.15 + }, + { + "timestamp": "2026-09-02T09:30:00", + "price": 230.38 + }, + { + "timestamp": "2026-09-02T09:40:00", + "price": 230.57 + }, + { + "timestamp": "2026-09-02T09:50:00", + "price": 230.72 + }, + { + "timestamp": "2026-09-02T10:00:00", + "price": 230.81 + }, + { + "timestamp": "2026-09-02T10:10:00", + "price": 230.84 + }, + { + "timestamp": "2026-09-02T10:20:00", + "price": 230.82 + }, + { + "timestamp": "2026-09-02T10:30:00", + "price": 230.74 + }, + { + "timestamp": "2026-09-02T10:40:00", + "price": 230.62 + }, + { + "timestamp": "2026-09-02T10:50:00", + "price": 230.47 + }, + { + "timestamp": "2026-09-02T11:00:00", + "price": 230.3 + }, + { + "timestamp": "2026-09-02T11:10:00", + "price": 230.11 + }, + { + "timestamp": "2026-09-02T11:20:00", + "price": 229.93 + }, + { + "timestamp": "2026-09-02T11:30:00", + "price": 229.78 + }, + { + "timestamp": "2026-09-02T11:40:00", + "price": 229.65 + }, + { + "timestamp": "2026-09-02T11:50:00", + "price": 229.57 + }, + { + "timestamp": "2026-09-02T12:00:00", + "price": 229.54 + }, + { + "timestamp": "2026-09-02T12:10:00", + "price": 229.57 + }, + { + "timestamp": "2026-09-02T12:20:00", + "price": 229.65 + }, + { + "timestamp": "2026-09-02T12:30:00", + "price": 229.79 + }, + { + "timestamp": "2026-09-02T12:40:00", + "price": 229.97 + }, + { + "timestamp": "2026-09-02T12:50:00", + "price": 230.2 + }, + { + "timestamp": "2026-09-02T13:00:00", + "price": 230.46 + }, + { + "timestamp": "2026-09-02T13:10:00", + "price": 230.73 + }, + { + "timestamp": "2026-09-02T13:20:00", + "price": 231.0 + }, + { + "timestamp": "2026-09-02T13:30:00", + "price": 231.26 + }, + { + "timestamp": "2026-09-02T13:40:00", + "price": 231.5 + }, + { + "timestamp": "2026-09-02T13:50:00", + "price": 231.7 + }, + { + "timestamp": "2026-09-02T14:00:00", + "price": 231.85 + }, + { + "timestamp": "2026-09-02T14:10:00", + "price": 231.94 + }, + { + "timestamp": "2026-09-02T14:20:00", + "price": 231.98 + }, + { + "timestamp": "2026-09-02T14:30:00", + "price": 231.97 + }, + { + "timestamp": "2026-09-02T14:40:00", + "price": 231.9 + }, + { + "timestamp": "2026-09-02T14:50:00", + "price": 231.79 + }, + { + "timestamp": "2026-09-02T15:00:00", + "price": 231.64 + }, + { + "timestamp": "2026-09-02T15:10:00", + "price": 231.46 + }, + { + "timestamp": "2026-09-02T15:20:00", + "price": 231.28 + }, + { + "timestamp": "2026-09-02T15:30:00", + "price": 231.1 + } + ], + "is_crypto": false + }, + "stock_data_MSFT": { + "symbol": "MSFT", + "name": "MSFT", + "price": 418.9, + "change": -3.55, + "change_percent": -0.84, + "open": 422.45, + "price_history": [ + { + "timestamp": "2026-09-02T09:00:00", + "price": 422.45 + }, + { + "timestamp": "2026-09-02T09:10:00", + "price": 422.78 + }, + { + "timestamp": "2026-09-02T09:20:00", + "price": 423.08 + }, + { + "timestamp": "2026-09-02T09:30:00", + "price": 423.34 + }, + { + "timestamp": "2026-09-02T09:40:00", + "price": 423.52 + }, + { + "timestamp": "2026-09-02T09:50:00", + "price": 423.61 + }, + { + "timestamp": "2026-09-02T10:00:00", + "price": 423.6 + }, + { + "timestamp": "2026-09-02T10:10:00", + "price": 423.49 + }, + { + "timestamp": "2026-09-02T10:20:00", + "price": 423.28 + }, + { + "timestamp": "2026-09-02T10:30:00", + "price": 422.97 + }, + { + "timestamp": "2026-09-02T10:40:00", + "price": 422.57 + }, + { + "timestamp": "2026-09-02T10:50:00", + "price": 422.12 + }, + { + "timestamp": "2026-09-02T11:00:00", + "price": 421.62 + }, + { + "timestamp": "2026-09-02T11:10:00", + "price": 421.11 + }, + { + "timestamp": "2026-09-02T11:20:00", + "price": 420.61 + }, + { + "timestamp": "2026-09-02T11:30:00", + "price": 420.15 + }, + { + "timestamp": "2026-09-02T11:40:00", + "price": 419.75 + }, + { + "timestamp": "2026-09-02T11:50:00", + "price": 419.43 + }, + { + "timestamp": "2026-09-02T12:00:00", + "price": 419.2 + }, + { + "timestamp": "2026-09-02T12:10:00", + "price": 419.08 + }, + { + "timestamp": "2026-09-02T12:20:00", + "price": 419.05 + }, + { + "timestamp": "2026-09-02T12:30:00", + "price": 419.13 + }, + { + "timestamp": "2026-09-02T12:40:00", + "price": 419.31 + }, + { + "timestamp": "2026-09-02T12:50:00", + "price": 419.55 + }, + { + "timestamp": "2026-09-02T13:00:00", + "price": 419.85 + }, + { + "timestamp": "2026-09-02T13:10:00", + "price": 420.18 + }, + { + "timestamp": "2026-09-02T13:20:00", + "price": 420.51 + }, + { + "timestamp": "2026-09-02T13:30:00", + "price": 420.81 + }, + { + "timestamp": "2026-09-02T13:40:00", + "price": 421.08 + }, + { + "timestamp": "2026-09-02T13:50:00", + "price": 421.27 + }, + { + "timestamp": "2026-09-02T14:00:00", + "price": 421.37 + }, + { + "timestamp": "2026-09-02T14:10:00", + "price": 421.38 + }, + { + "timestamp": "2026-09-02T14:20:00", + "price": 421.28 + }, + { + "timestamp": "2026-09-02T14:30:00", + "price": 421.08 + }, + { + "timestamp": "2026-09-02T14:40:00", + "price": 420.78 + }, + { + "timestamp": "2026-09-02T14:50:00", + "price": 420.4 + }, + { + "timestamp": "2026-09-02T15:00:00", + "price": 419.95 + }, + { + "timestamp": "2026-09-02T15:10:00", + "price": 419.46 + }, + { + "timestamp": "2026-09-02T15:20:00", + "price": 418.95 + }, + { + "timestamp": "2026-09-02T15:30:00", + "price": 418.45 + } + ], + "is_crypto": false + }, + "stock_data_NVDA": { + "symbol": "NVDA", + "name": "NVDA", + "price": 176.12, + "change": 4.31, + "change_percent": 2.51, + "open": 171.81, + "price_history": [ + { + "timestamp": "2026-09-02T09:00:00", + "price": 171.81 + }, + { + "timestamp": "2026-09-02T09:10:00", + "price": 172.09 + }, + { + "timestamp": "2026-09-02T09:20:00", + "price": 172.35 + }, + { + "timestamp": "2026-09-02T09:30:00", + "price": 172.6 + }, + { + "timestamp": "2026-09-02T09:40:00", + "price": 172.82 + }, + { + "timestamp": "2026-09-02T09:50:00", + "price": 173.0 + }, + { + "timestamp": "2026-09-02T10:00:00", + "price": 173.14 + }, + { + "timestamp": "2026-09-02T10:10:00", + "price": 173.24 + }, + { + "timestamp": "2026-09-02T10:20:00", + "price": 173.3 + }, + { + "timestamp": "2026-09-02T10:30:00", + "price": 173.31 + }, + { + "timestamp": "2026-09-02T10:40:00", + "price": 173.3 + }, + { + "timestamp": "2026-09-02T10:50:00", + "price": 173.26 + }, + { + "timestamp": "2026-09-02T11:00:00", + "price": 173.2 + }, + { + "timestamp": "2026-09-02T11:10:00", + "price": 173.14 + }, + { + "timestamp": "2026-09-02T11:20:00", + "price": 173.08 + }, + { + "timestamp": "2026-09-02T11:30:00", + "price": 173.03 + }, + { + "timestamp": "2026-09-02T11:40:00", + "price": 173.01 + }, + { + "timestamp": "2026-09-02T11:50:00", + "price": 173.03 + }, + { + "timestamp": "2026-09-02T12:00:00", + "price": 173.08 + }, + { + "timestamp": "2026-09-02T12:10:00", + "price": 173.17 + }, + { + "timestamp": "2026-09-02T12:20:00", + "price": 173.31 + }, + { + "timestamp": "2026-09-02T12:30:00", + "price": 173.48 + }, + { + "timestamp": "2026-09-02T12:40:00", + "price": 173.7 + }, + { + "timestamp": "2026-09-02T12:50:00", + "price": 173.94 + }, + { + "timestamp": "2026-09-02T13:00:00", + "price": 174.2 + }, + { + "timestamp": "2026-09-02T13:10:00", + "price": 174.48 + }, + { + "timestamp": "2026-09-02T13:20:00", + "price": 174.76 + }, + { + "timestamp": "2026-09-02T13:30:00", + "price": 175.03 + }, + { + "timestamp": "2026-09-02T13:40:00", + "price": 175.28 + }, + { + "timestamp": "2026-09-02T13:50:00", + "price": 175.5 + }, + { + "timestamp": "2026-09-02T14:00:00", + "price": 175.69 + }, + { + "timestamp": "2026-09-02T14:10:00", + "price": 175.83 + }, + { + "timestamp": "2026-09-02T14:20:00", + "price": 175.94 + }, + { + "timestamp": "2026-09-02T14:30:00", + "price": 176.0 + }, + { + "timestamp": "2026-09-02T14:40:00", + "price": 176.02 + }, + { + "timestamp": "2026-09-02T14:50:00", + "price": 176.01 + }, + { + "timestamp": "2026-09-02T15:00:00", + "price": 175.97 + }, + { + "timestamp": "2026-09-02T15:10:00", + "price": 175.92 + }, + { + "timestamp": "2026-09-02T15:20:00", + "price": 175.85 + }, + { + "timestamp": "2026-09-02T15:30:00", + "price": 175.79 + } + ], + "is_crypto": false + }, + "stock_data_BTC": { + "symbol": "BTC", + "name": "BTC", + "price": 63142.0, + "change": -812.0, + "change_percent": -1.27, + "open": 63954.0, + "price_history": [ + { + "timestamp": "2026-09-02T09:00:00", + "price": 63954.0 + }, + { + "timestamp": "2026-09-02T09:10:00", + "price": 63996.99 + }, + { + "timestamp": "2026-09-02T09:20:00", + "price": 64036.04 + }, + { + "timestamp": "2026-09-02T09:30:00", + "price": 64067.47 + }, + { + "timestamp": "2026-09-02T09:40:00", + "price": 64088.06 + }, + { + "timestamp": "2026-09-02T09:50:00", + "price": 64095.27 + }, + { + "timestamp": "2026-09-02T10:00:00", + "price": 64087.38 + }, + { + "timestamp": "2026-09-02T10:10:00", + "price": 64063.62 + }, + { + "timestamp": "2026-09-02T10:20:00", + "price": 64024.21 + }, + { + "timestamp": "2026-09-02T10:30:00", + "price": 63970.34 + }, + { + "timestamp": "2026-09-02T10:40:00", + "price": 63904.1 + }, + { + "timestamp": "2026-09-02T10:50:00", + "price": 63828.33 + }, + { + "timestamp": "2026-09-02T11:00:00", + "price": 63746.5 + }, + { + "timestamp": "2026-09-02T11:10:00", + "price": 63662.42 + }, + { + "timestamp": "2026-09-02T11:20:00", + "price": 63580.06 + }, + { + "timestamp": "2026-09-02T11:30:00", + "price": 63503.29 + }, + { + "timestamp": "2026-09-02T11:40:00", + "price": 63435.6 + }, + { + "timestamp": "2026-09-02T11:50:00", + "price": 63379.95 + }, + { + "timestamp": "2026-09-02T12:00:00", + "price": 63338.53 + }, + { + "timestamp": "2026-09-02T12:10:00", + "price": 63312.66 + }, + { + "timestamp": "2026-09-02T12:20:00", + "price": 63302.69 + }, + { + "timestamp": "2026-09-02T12:30:00", + "price": 63307.97 + }, + { + "timestamp": "2026-09-02T12:40:00", + "price": 63326.91 + }, + { + "timestamp": "2026-09-02T12:50:00", + "price": 63357.07 + }, + { + "timestamp": "2026-09-02T13:00:00", + "price": 63395.32 + }, + { + "timestamp": "2026-09-02T13:10:00", + "price": 63438.01 + }, + { + "timestamp": "2026-09-02T13:20:00", + "price": 63481.23 + }, + { + "timestamp": "2026-09-02T13:30:00", + "price": 63521.03 + }, + { + "timestamp": "2026-09-02T13:40:00", + "price": 63553.67 + }, + { + "timestamp": "2026-09-02T13:50:00", + "price": 63575.86 + }, + { + "timestamp": "2026-09-02T14:00:00", + "price": 63584.96 + }, + { + "timestamp": "2026-09-02T14:10:00", + "price": 63579.13 + }, + { + "timestamp": "2026-09-02T14:20:00", + "price": 63557.49 + }, + { + "timestamp": "2026-09-02T14:30:00", + "price": 63520.12 + }, + { + "timestamp": "2026-09-02T14:40:00", + "price": 63468.07 + }, + { + "timestamp": "2026-09-02T14:50:00", + "price": 63403.31 + }, + { + "timestamp": "2026-09-02T15:00:00", + "price": 63328.63 + }, + { + "timestamp": "2026-09-02T15:10:00", + "price": 63247.38 + }, + { + "timestamp": "2026-09-02T15:20:00", + "price": 63163.38 + }, + { + "timestamp": "2026-09-02T15:30:00", + "price": 63080.56 + } + ], + "is_crypto": true + } +} diff --git a/docs/assets/ledmatrix-stocks/panel-sizes.png b/docs/assets/ledmatrix-stocks/panel-sizes.png new file mode 100644 index 00000000..4afab696 Binary files /dev/null and b/docs/assets/ledmatrix-stocks/panel-sizes.png differ diff --git a/docs/assets/ledmatrix-stocks/shots.json b/docs/assets/ledmatrix-stocks/shots.json new file mode 100644 index 00000000..ab8d6068 --- /dev/null +++ b/docs/assets/ledmatrix-stocks/shots.json @@ -0,0 +1,314 @@ +{ + "plugin": "ledmatrix-stocks", + "defaults": { + "width": 128, + "height": 32, + "scale": 6, + "freeze_time": "2026-09-02T15:30:00", + "mock_data": "mock.json", + "config": { + "enabled": true, + "stocks": { + "enabled": true, + "symbols": [ + "AAPL", + "MSFT", + "NVDA" + ] + }, + "crypto": { + "enabled": false + } + }, + "frames": 60 + }, + "shots": [ + { + "name": "hero", + "config": { + "stocks": { + "enabled": true, + "symbols": [ + "AAPL" + ] + }, + "crypto": { + "enabled": false, + "symbols": [] + } + } + }, + { + "name": "up", + "standalone": false, + "config": { + "stocks": { + "enabled": true, + "symbols": [ + "AAPL" + ] + }, + "crypto": { + "enabled": false, + "symbols": [] + } + } + }, + { + "name": "down", + "standalone": false, + "config": { + "stocks": { + "enabled": true, + "symbols": [ + "MSFT" + ] + }, + "crypto": { + "enabled": false, + "symbols": [] + } + } + }, + { + "name": "crypto", + "standalone": false, + "config": { + "stocks": { + "enabled": false, + "symbols": [] + }, + "crypto": { + "enabled": true, + "symbols": [ + "BTC-USD" + ] + } + }, + "frames": 60, + "width": 256 + }, + { + "name": "chart-on", + "standalone": false, + "config": { + "stocks": { + "enabled": true, + "symbols": [ + "NVDA" + ] + }, + "crypto": { + "enabled": false, + "symbols": [] + }, + "display": { + "toggle_chart": true + } + } + }, + { + "name": "chart-off", + "standalone": false, + "config": { + "stocks": { + "enabled": true, + "symbols": [ + "NVDA" + ] + }, + "crypto": { + "enabled": false, + "symbols": [] + }, + "display": { + "toggle_chart": false + } + } + }, + { + "name": "mode-scroll", + "standalone": false, + "config": { + "stocks": { + "enabled": true, + "symbols": [ + "AAPL" + ] + }, + "crypto": { + "enabled": false, + "symbols": [] + }, + "display": { + "display_mode": "scroll" + } + } + }, + { + "name": "mode-switch", + "standalone": false, + "frames": 5, + "config": { + "stocks": { + "enabled": true, + "symbols": [ + "AAPL" + ] + }, + "crypto": { + "enabled": false, + "symbols": [] + }, + "display": { + "display_mode": "switch" + } + } + }, + { + "name": "size-64x32", + "width": 64, + "standalone": false, + "config": { + "stocks": { + "enabled": true, + "symbols": [ + "AAPL" + ] + }, + "crypto": { + "enabled": false, + "symbols": [] + } + } + }, + { + "name": "size-128x32", + "width": 128, + "standalone": false, + "config": { + "stocks": { + "enabled": true, + "symbols": [ + "AAPL" + ] + }, + "crypto": { + "enabled": false, + "symbols": [] + } + } + }, + { + "name": "size-128x64", + "width": 128, + "height": 64, + "standalone": false, + "config": { + "stocks": { + "enabled": true, + "symbols": [ + "AAPL" + ] + }, + "crypto": { + "enabled": false, + "symbols": [] + } + } + }, + { + "name": "size-256x32", + "width": 256, + "standalone": false, + "config": { + "stocks": { + "enabled": true, + "symbols": [ + "AAPL" + ] + }, + "crypto": { + "enabled": false, + "symbols": [] + } + } + } + ], + "composites": [ + { + "name": "gain-loss", + "columns": 1, + "cells": [ + { + "shot": "up", + "label": "A gain", + "sublabel": "positive_color, green by default" + }, + { + "shot": "down", + "label": "A loss", + "sublabel": "negative_color, red by default" + }, + { + "shot": "crypto", + "label": "Crypto", + "sublabel": "its own colour block, gold by default (256 wide, where the whole line fits)" + } + ] + }, + { + "name": "toggle-chart", + "columns": 1, + "cells": [ + { + "shot": "chart-on", + "label": "toggle_chart: true" + }, + { + "shot": "chart-off", + "label": "toggle_chart: false" + } + ] + }, + { + "name": "display-mode", + "columns": 1, + "cells": [ + { + "shot": "mode-scroll", + "label": "display_mode: scroll", + "sublabel": "one continuous ribbon of every symbol" + }, + { + "shot": "mode-switch", + "label": "display_mode: switch", + "sublabel": "one symbol at a time, held for switch_duration" + } + ] + }, + { + "name": "panel-sizes", + "columns": 2, + "cells": [ + { + "shot": "size-64x32", + "label": "64 x 32" + }, + { + "shot": "size-128x32", + "label": "128 x 32" + }, + { + "shot": "size-128x64", + "label": "128 x 64" + }, + { + "shot": "size-256x32", + "label": "256 x 32" + } + ] + } + ], + "_comment": "Scroll shots use frames: the ticker starts off-panel, so frame 0 is blank. Company logos are downloaded by the plugin and cached on disk, so the very first render on a fresh machine needs network access; later runs are offline and byte-identical." +} diff --git a/docs/assets/ledmatrix-stocks/toggle-chart.png b/docs/assets/ledmatrix-stocks/toggle-chart.png new file mode 100644 index 00000000..71c159d7 Binary files /dev/null and b/docs/assets/ledmatrix-stocks/toggle-chart.png differ diff --git a/plugins.json b/plugins.json index faa6c100..bc360374 100644 --- a/plugins.json +++ b/plugins.json @@ -1,6 +1,6 @@ { "version": "1.0.0", - "last_updated": "2026-09-02", + "last_updated": "2026-09-03", "plugins": [ { "id": "cricket-scoreboard", @@ -829,10 +829,10 @@ "plugin_path": "plugins/ledmatrix-stocks", "stars": 0, "downloads": 0, - "last_updated": "2026-08-05", + "last_updated": "2026-09-03", "verified": true, "screenshot": "", - "latest_version": "2.8.0", + "latest_version": "2.9.0", "icon": "fas fa-chart-line" }, { diff --git a/plugins/ledmatrix-stocks/README.md b/plugins/ledmatrix-stocks/README.md index 3b99de03..a41046d7 100644 --- a/plugins/ledmatrix-stocks/README.md +++ b/plugins/ledmatrix-stocks/README.md @@ -4,6 +4,12 @@ A scrolling ticker for the LEDMatrix display showing live stock and cryptocurrency prices, percent changes, and optional inline price charts. Data comes from Yahoo Finance — no API key required. +![AAPL scrolling across a 128x32 panel: the Apple logo, the symbol, the price, +and a green gain](../../docs/assets/ledmatrix-stocks/hero.png) + +*Every image in this README is real plugin output, rendered at the true panel +size from a recorded quote so it reproduces exactly.* + ## Features - Live stock and crypto prices via Yahoo Finance (no API key) @@ -70,10 +76,38 @@ generated from it. The most-used keys, with their actual nesting: ### `customization.*` -Per-element font, size, and color overrides for stocks and crypto. Each -of `symbol`, `price`, and `price_delta` has its own `font`, `font_size`, -and color settings. Defaults use `PressStart2P-Regular.ttf` at size 8, -with green for positive deltas and red for negative. +Per-element font, size, and colour overrides, kept separately for stocks and +crypto so the two are distinguishable at a glance. Each of `symbol`, `price` +and `price_delta` has its own `font` and `font_size`; the colour keys differ +by element: + +| Key | Default | Notes | +|---|---|---| +| `customization.stocks.symbol.text_color` | `[255, 255, 255]` | Ticker symbol, stocks | +| `customization.stocks.price.text_color` | `[255, 255, 255]` | Price line, stocks | +| `customization.stocks.price_delta.positive_color` | `[0, 255, 0]` | Change when up, stocks | +| `customization.stocks.price_delta.negative_color` | `[255, 0, 0]` | Change when down, stocks | +| `customization.crypto.symbol.text_color` | `[255, 215, 0]` | Ticker symbol, crypto | +| `customization.crypto.price.text_color` | `[255, 215, 0]` | Price line, crypto | +| `customization.crypto.price_delta.positive_color` | `[0, 255, 0]` | Change when up, crypto | +| `customization.crypto.price_delta.negative_color` | `[255, 0, 0]` | Change when down, crypto | + +`price_delta` has no `text_color` — its colour is chosen by the sign of the +change, from the two keys above. + +![A gain, a loss, and a crypto entry](../../docs/assets/ledmatrix-stocks/gain-loss.png) + +Five fonts are offered per element. Three are TrueType and work at every size +in the 4–16 range; two are `.bdf` bitmap faces, which exist at exactly one +size each and are loaded at that size whatever `font_size` says: + +| Font | Kind | Sizes | +|---|---|---| +| `PressStart2P-Regular.ttf` | TrueType | any | +| `4x6-font.ttf` | TrueType | any | +| `5by7.regular.ttf` | TrueType | any | +| `5x7.bdf` | bitmap | 7 only | +| `4x6.bdf` | bitmap | 6 only | ## Symbol format @@ -119,6 +153,25 @@ Symbols are upper-case only (`aapl` is rejected — use `AAPL`), and cannot contain spaces. If the field refuses what you typed, the web UI names the value it rejected. +### The two display modes + +![scroll versus switch](../../docs/assets/ledmatrix-stocks/display-mode.png) + +`scroll` renders every symbol into one continuous ribbon and slides it past; +`switch` shows a single symbol at a time and cuts to the next after +`switch_duration` seconds. + +### The inline chart + +`display.toggle_chart` draws the day's price history behind the text, sized by +`chart_width_px` and `chart_height_px`: + +![toggle_chart on and off](../../docs/assets/ledmatrix-stocks/toggle-chart.png) + +### Panel sizes + +![The same symbol on four panel sizes](../../docs/assets/ledmatrix-stocks/panel-sizes.png) + ### A note on display width Longer symbols take more room on the panel. `^GSPC` and `GC=F` are five and diff --git a/plugins/ledmatrix-stocks/config_schema.json b/plugins/ledmatrix-stocks/config_schema.json index 03562804..1263fc9c 100644 --- a/plugins/ledmatrix-stocks/config_schema.json +++ b/plugins/ledmatrix-stocks/config_schema.json @@ -214,8 +214,7 @@ "4x6-font.ttf", "5by7.regular.ttf", "5x7.bdf", - "4x6.bdf", - "cozette.bdf" + "4x6.bdf" ], "default": "PressStart2P-Regular.ttf" }, @@ -257,8 +256,7 @@ "4x6-font.ttf", "5by7.regular.ttf", "5x7.bdf", - "4x6.bdf", - "cozette.bdf" + "4x6.bdf" ], "default": "PressStart2P-Regular.ttf" }, @@ -300,8 +298,7 @@ "4x6-font.ttf", "5by7.regular.ttf", "5x7.bdf", - "4x6.bdf", - "cozette.bdf" + "4x6.bdf" ], "default": "PressStart2P-Regular.ttf" }, @@ -366,8 +363,7 @@ "4x6-font.ttf", "5by7.regular.ttf", "5x7.bdf", - "4x6.bdf", - "cozette.bdf" + "4x6.bdf" ], "default": "PressStart2P-Regular.ttf" }, @@ -409,8 +405,7 @@ "4x6-font.ttf", "5by7.regular.ttf", "5x7.bdf", - "4x6.bdf", - "cozette.bdf" + "4x6.bdf" ], "default": "PressStart2P-Regular.ttf" }, @@ -452,8 +447,7 @@ "4x6-font.ttf", "5by7.regular.ttf", "5x7.bdf", - "4x6.bdf", - "cozette.bdf" + "4x6.bdf" ], "default": "PressStart2P-Regular.ttf" }, diff --git a/plugins/ledmatrix-stocks/display_renderer.py b/plugins/ledmatrix-stocks/display_renderer.py index 56708af1..943e3f38 100644 --- a/plugins/ledmatrix-stocks/display_renderer.py +++ b/plugins/ledmatrix-stocks/display_renderer.py @@ -138,6 +138,20 @@ def __init__(self, config: Dict[str, Any], display_width: int, display_height: i self.price_font = self._load_custom_font_from_element_config(stocks_custom.get('price', {})) self.price_delta_font = self._load_custom_font_from_element_config(stocks_custom.get('price_delta', {})) + @staticmethod + def _bdf_pixel_size(path): + """The pixel size a .bdf font declares, or None if it does not.""" + try: + with open(path, "r", encoding="latin-1") as handle: + for line in handle: + if line.startswith("PIXEL_SIZE"): + return int(line.split()[1]) + if line.startswith("CHARS"): + break # past the header; no point reading the glyphs + except (OSError, ValueError, IndexError): + return None + return None + def _load_custom_font_from_element_config(self, element_config: Dict[str, Any]) -> ImageFont.FreeTypeFont: """ Load a custom font from an element configuration dictionary. @@ -165,13 +179,25 @@ def _load_custom_font_from_element_config(self, element_config: Dict[str, Any]) self.logger.debug(f"Loaded font: {font_name} at size {font_size}") return font elif font_path.lower().endswith('.bdf'): - # PIL's ImageFont.truetype() can sometimes handle BDF files - # If it fails, we'll fall through to the default font + # A .bdf is a bitmap face that exists at exactly one pixel + # size; FreeType refuses every other. Retry at the size the + # file declares rather than dropping to the default font -- + # otherwise picking 5x7.bdf works at size 7 and nowhere else. try: font = ImageFont.truetype(font_path, font_size) self.logger.debug(f"Loaded BDF font: {font_name} at size {font_size}") return font - except Exception: + except OSError: + native = self._bdf_pixel_size(font_path) + if native is not None and native != font_size: + try: + font = ImageFont.truetype(font_path, native) + self.logger.debug( + "Loaded bitmap font %s at its native size %d " + "(requested %d)", font_name, native, font_size) + return font + except OSError: + pass self.logger.warning(f"Could not load BDF font {font_name} with PIL, using default") # Fall through to default else: diff --git a/plugins/ledmatrix-stocks/manifest.json b/plugins/ledmatrix-stocks/manifest.json index 8f8621d3..ad2cb2c2 100644 --- a/plugins/ledmatrix-stocks/manifest.json +++ b/plugins/ledmatrix-stocks/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-stocks", "name": "Stock & Crypto Ticker", - "version": "2.8.0", + "version": "2.9.0", "description": "Displays stock tickers with prices, changes, and optional charts for stocks and cryptocurrencies. Supports scroll and switch display modes.", "author": "LEDMatrix Team", "homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/ledmatrix-stocks", @@ -39,6 +39,12 @@ } ], "versions": [ + { + "version": "2.9.0", + "released": "2026-09-03", + "notes": "Bitmap fonts and documentation. A .bdf face exists at exactly one pixel size, so choosing 5x7.bdf or 4x6.bdf at any other size raised inside FreeType and the renderer silently substituted PressStart2P; they now load at their own declared size. cozette.bdf is removed from all six font menus because the core ships no such file, so it could never load (#387). The README gains real rendered screenshots -- a gain, a loss, a crypto entry, both display modes, the inline chart on and off, and four panel sizes -- and documents the eight per-element colour keys that were previously summarised in a sentence.", + "ledmatrix_min_version": "2.0.0" + }, { "version": "2.8.0", "released": "2026-08-19", @@ -71,7 +77,7 @@ { "released": "2026-07-10", "version": "2.3.2", - "notes": "Bump logo_x (2px→4px) and logo_gap (4px→8px) so the ~26px-tall logo isn't cramped against the display edge and the symbol/price text on the 32px-tall display.", + "notes": "Bump logo_x (2px\u21924px) and logo_gap (4px\u21928px) so the ~26px-tall logo isn't cramped against the display edge and the symbol/price text on the 32px-tall display.", "ledmatrix_min_version": "2.0.0" }, { @@ -116,7 +122,7 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-08-05", + "last_updated": "2026-09-03", "stars": 0, "downloads": 0, "verified": true,