From c50c3623337735c5ed2ced6d050c3d59cb0a3b64 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Thu, 3 Sep 2026 12:22:22 -0400 Subject: [PATCH] docs(ledmatrix-flights): document all 96 settings, with real renders The "Full Configuration Options" section pointed at config_schema.json instead of listing anything, so 84 of the 96 settings were never named -- the whole map_background block, the whole metar, proximity_alert, fonts and flightaware blocks, every unit and colour key, and the filters that decide which aircraft appear at all. All 96 are now documented by key, grouped by area, including the detail that the FlightAware and OpenSky credentials can be given either nested or flat because _normalize_flightaware_config() copies one onto the other at start-up. Adds the images it had none of: the four display modes, imperial against metric, degrees against cardinal headings, and four panel sizes. Aircraft are seeded onto the plugin instance because the live source is a local SkyAware receiver; positions and callsigns are invented and placed inside the default 10-mile radius. map_background is off in the shots, since its basemap tiles come from a live tile server and could not reproduce. One fix: _render_map_image() read aircraft['color'] with a hard subscript, while every other read of that field in this plugin uses .get with a fallback. A record that reached the map without one raised KeyError and took the whole mode to "ERR: map" while the other modes rendered the same aircraft fine. It now falls back, verified by rendering map mode with the key deliberately absent. Not affected by #397: this plugin has its own _get_font_height() that never calls display_manager.get_font_height(), and renderer.py already loads its 6x10.bdf at the face's native 10px with a fallback. check_plugin.py: 8/8 pass. The run attempts a real connection to the default skyaware_url and waits out a 5s timeout at each size -- filed as #400. Co-Authored-By: Claude Opus 5 --- README.md | 2 +- .../ledmatrix-flights/display-modes.png | Bin 0 -> 24228 bytes docs/assets/ledmatrix-flights/hero.png | Bin 0 -> 2919 bytes docs/assets/ledmatrix-flights/panel-sizes.png | Bin 0 -> 20655 bytes docs/assets/ledmatrix-flights/shots.json | 340 ++++++++++++++++++ .../assets/ledmatrix-flights/track-format.png | Bin 0 -> 14821 bytes docs/assets/ledmatrix-flights/units.png | Bin 0 -> 15080 bytes plugins.json | 4 +- plugins/ledmatrix-flights/README.md | 209 ++++++++++- plugins/ledmatrix-flights/manager.py | 7 +- plugins/ledmatrix-flights/manifest.json | 10 +- 11 files changed, 558 insertions(+), 14 deletions(-) create mode 100644 docs/assets/ledmatrix-flights/display-modes.png create mode 100644 docs/assets/ledmatrix-flights/hero.png create mode 100644 docs/assets/ledmatrix-flights/panel-sizes.png create mode 100644 docs/assets/ledmatrix-flights/shots.json create mode 100644 docs/assets/ledmatrix-flights/track-format.png create mode 100644 docs/assets/ledmatrix-flights/units.png diff --git a/README.md b/README.md index cd4e998f..8c02d2fd 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \ | Plugin | Description | Preview | |--------|-------------|---------| -| [Flight Tracker](./plugins/ledmatrix-flights/) | Real-time ADS-B aircraft tracking with map display | | +| [Flight Tracker](./plugins/ledmatrix-flights/) | Real-time ADS-B aircraft tracking with map display | ledmatrix-flights on an LED panel | | [Countdown Display](./plugins/countdown/) | Customizable countdowns for birthdays, events, holidays | countdown on an LED panel | | [Election Results](./plugins/ledmatrix-elections/) | Live election results ticker with full-screen race interrupts | | diff --git a/docs/assets/ledmatrix-flights/display-modes.png b/docs/assets/ledmatrix-flights/display-modes.png new file mode 100644 index 0000000000000000000000000000000000000000..1439965f9376cccf0cc70071378cdd008826885f GIT binary patch literal 24228 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 V1c=S3fIhEx*vk)c+w;V4{{V56#G?QJ literal 0 HcmV?d00001 diff --git a/docs/assets/ledmatrix-flights/hero.png b/docs/assets/ledmatrix-flights/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..19a6da5c5396757527d584b1354f99e97847b1c6 GIT binary patch literal 2919 zcmb7Gdpw(G8-8h3TU!0rIg}}z5)-tgh{II3o)odE&^UMEtRhPzO_K`ix|Uke6h%rq zhfLy_4AnT5wib0P2|=oj&{9MZDIvb-r~7op-skz<_j6zObzKSHLQm}2 zuDKlmfE|t}kGcWCCfO&@xK&;@+l`QB06@vq@#qov3jL*sDL#0{Q0<0{8PCLTy9t=Kt@W^gARGz_z-fyqB z1UkoGOMw=67&g;{ZH!xCWs>0rmbFi3ojCf{99(K99zu8Afi_rM4KKT`MeygiTdx@< z$JX4lXB&yda;#RE5qAwYd%?%l06=~h5C?1qbOAYFtHZxo{@ZhnX(iE0ul18Hbim7* zidrgFGgPWT=*Nc5KUoJq0*o6@|%Hu{_!7$l=6Gy-9C5rv5BZR z_dJE7!aO_{OEi8ws?8(vMKuk+D9}#Q>^;<=&{f7bO z#UPC$zjn#JMsk6pr~F;nlh}JlS4)3NO-((JNhUk<-au}J4WFl<{f>QuI-TRn%la0> zJSW8_$5y3khdURT4V%78AAetgy zP?y0MOf|Hyrc@rWvLe;{iLvUSY~eK$7t0qs@=p{#0X2Bk?l(rppS$K8H$TytSOz|v zTU!&;tbSeMgS5-Iq{f96H|Rsh80Vr|h&mvp5O7$$T)sY6-f7{AzR3X~P5?ge5o{8R z4MnFHpOj+yf=^61UD09|4qqdgpWqA9k&jqS;}C1H7x@sfHFoR_IdaLJ?6Z;y8SgBO zfDtWgjG$8My|QXW=!ciCTp%0!`z}-KMMcs32Q3B};Pxtt_TsSxs_lqiOhS(!K5O_Q zt`~f+sDP2(wp$m_UEHqxCwAA0(~CAKCj|8u)Ii{jFQFbPPgwKWMdcW_3=o&S`3Nb3 z>?75Ms;MVpN$nvkbOtv)eH6|-H%A#kG`DkMhZyF@sO&kKp$Lw^8V3pOqrZtE1~YPN zO5YK-AWz;I_#Qp+y66DzyrA2%PSQ+Lsh;t}-(O-(N` zEdHWEHG<(;l0-5uD{Lz9!>Nz|kZ=>GhcBDsLMyQThV~tgGa-AV1Tr~RpQ&}dWQYx( zZ<>vTbL742?NrmdW^EfT8bZ~ElS!-g-mEg;=eb5bw!cxux2jdgI@>5VqNi@jF)g#l z*==pkN-ZocSYBRUQQ{MLrk;w5%2n3AZ>}~WT5f1dv7e9MU^89D{t1F!@+RfSIp$t} zEQI^EYC4;vu0m}V-N=y|EI*M4S(&E+0yAQ{v#_G_ZR`wD&Jri21+ER!c^VU6M>JK9 zI^@mvCkTSzN|7rZ$fE9GljVGZ3@O4%0zJatdKai{^&oVq>`Up8w_9+C$3%el=fdM@ z6~Un{wDg48=&SYySVm9gG$JBn+sVgwmn8XbXKQRfQ=It!cja;orr#7N2G zklU`%WeaGOB$zxT3P9Q0FMdyMtIZr>td90}pDx0C)7;uG?kbs@p-wOG`!)YeC+g7;`Ht z&;B;ey?b|(HYx7RR~i0IQ%j2`AkW3ZpZi1%Ll5fWWGb|#iyNdaU%yM9xe^b_yaa}h zxvGlGXHC2o1=}?rAD(?k0DBTEs2#`h?yGxDC^JTUAbqXl#(trrFow__U7%mQ$BUF0 z6F)lE`3QesR&Dp*{euk{qLL5>!-I)1EdDI-t6VcJrq|$iX*#&@aO#yEaNH|eTk5&V zA2-%|U8WuI7N=HvUxYIGQb>Q~ASMm+>UiFx>xs$cW@eul zkVqtHft*uH!&R1yZ#GKhd%Ah(8_WHKTnX{8bsYerEqUyzOnr-|YHp=V=UJ8q^ z`03U-cy#XrG&=i+9aieBM5XWb?2{mxw!MABwrbZ^>?pqf^6?qh+ntX?57;&~{r7%;4oMcRXQ~qNttPHlG@- zQ9#+iIt753jWU;0CYv^NQRsaI_Xxzkm>2^Ph^N*#+T+MiB^hAG+k@Y=FDRmn4$Y-KOQe5AVQ(EK}3Q|7O6}tjI^>jFD7PPylbJ TN^i|s+i*MvJzDy8z>oh0=1K#% literal 0 HcmV?d00001 diff --git a/docs/assets/ledmatrix-flights/panel-sizes.png b/docs/assets/ledmatrix-flights/panel-sizes.png new file mode 100644 index 0000000000000000000000000000000000000000..73117f6213414f705ade0f313b0f0840bd2f678d GIT binary patch literal 20655 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 TvuBnXd*R0rN3--V-uQn2Dss5f literal 0 HcmV?d00001 diff --git a/docs/assets/ledmatrix-flights/shots.json b/docs/assets/ledmatrix-flights/shots.json new file mode 100644 index 00000000..c715c789 --- /dev/null +++ b/docs/assets/ledmatrix-flights/shots.json @@ -0,0 +1,340 @@ +{ + "plugin": "ledmatrix-flights", + "_comment": "Aircraft are seeded onto the plugin instance rather than fetched: the live source is a local SkyAware receiver. Positions are invented and placed within the default 10-mile radius of the schema's default centre. map_background is off in every shot because the basemap tiles come from a live tile server, so a render including them could not reproduce.", + "defaults": { + "width": 128, + "height": 32, + "scale": 6, + "freeze_time": "2026-09-03T15:30:00+00:00", + "skip_update": true, + "config": { + "enabled": true + }, + "attrs": { + "aircraft_data": { + "A1B2C3": { + "icao": "A1B2C3", + "callsign": "DAL482", + "lat": 28.01, + "lon": -82.52, + "altitude": 34000, + "speed": 452, + "heading": 78, + "distance_miles": 4.2, + "origin": "TPA", + "destination": "ATL", + "aircraft_type": "B738", + "airline_icao": "DAL", + "vertical_rate": 0, + "last_seen": 1788449400.0, + "timestamp": 1788449400.0, + "color": [ + 255, + 190, + 0 + ] + }, + "D4E5F6": { + "icao": "D4E5F6", + "callsign": "AAL119", + "lat": 27.905, + "lon": -82.398, + "altitude": 12500, + "speed": 310, + "heading": 190, + "distance_miles": 6.8, + "origin": "TPA", + "destination": "DFW", + "aircraft_type": "A321", + "airline_icao": "AAL", + "vertical_rate": 0, + "last_seen": 1788449400.0, + "timestamp": 1788449400.0, + "color": [ + 90, + 200, + 255 + ] + }, + "07A8B9": { + "icao": "07A8B9", + "callsign": "SWA2210", + "lat": 27.972, + "lon": -82.56, + "altitude": 5200, + "speed": 265, + "heading": 341, + "distance_miles": 8.4, + "origin": "TPA", + "destination": "BNA", + "aircraft_type": "B737", + "airline_icao": "SWA", + "vertical_rate": 0, + "last_seen": 1788449400.0, + "timestamp": 1788449400.0, + "color": [ + 255, + 120, + 120 + ] + }, + "C0D1E2": { + "icao": "C0D1E2", + "callsign": "JBU765", + "lat": 27.93, + "lon": -82.43, + "altitude": 2100, + "speed": 190, + "heading": 265, + "distance_miles": 2.9, + "origin": "TPA", + "destination": "JFK", + "aircraft_type": "A320", + "airline_icao": "JBU", + "vertical_rate": 0, + "last_seen": 1788449400.0, + "timestamp": 1788449400.0, + "color": [ + 140, + 255, + 140 + ] + } + } + } + }, + "shots": [ + { + "name": "hero", + "config": { + "enabled": true, + "display_mode": "overhead", + "map_background": { + "enabled": false + } + } + }, + { + "name": "mode-overhead", + "standalone": false, + "config": { + "enabled": true, + "display_mode": "overhead", + "map_background": { + "enabled": false + } + } + }, + { + "name": "mode-map", + "standalone": false, + "config": { + "enabled": true, + "display_mode": "map", + "map_background": { + "enabled": false + } + } + }, + { + "name": "mode-stats", + "standalone": false, + "config": { + "enabled": true, + "display_mode": "stats", + "map_background": { + "enabled": false + } + } + }, + { + "name": "mode-area", + "standalone": false, + "config": { + "enabled": true, + "display_mode": "area", + "map_background": { + "enabled": false + } + } + }, + { + "name": "units-imperial", + "standalone": false, + "width": 256, + "config": { + "enabled": true, + "display_mode": "overhead", + "units": "imperial", + "map_background": { + "enabled": false + } + } + }, + { + "name": "units-metric", + "standalone": false, + "width": 256, + "config": { + "enabled": true, + "display_mode": "overhead", + "units": "metric", + "map_background": { + "enabled": false + } + } + }, + { + "name": "track-deg", + "standalone": false, + "width": 256, + "config": { + "enabled": true, + "display_mode": "overhead", + "track_format": "deg", + "map_background": { + "enabled": false + } + } + }, + { + "name": "track-cardinal", + "standalone": false, + "width": 256, + "config": { + "enabled": true, + "display_mode": "overhead", + "track_format": "cardinal", + "map_background": { + "enabled": false + } + } + }, + { + "name": "size-64x32", + "width": 64, + "standalone": false, + "config": { + "enabled": true, + "display_mode": "overhead", + "map_background": { + "enabled": false + } + } + }, + { + "name": "size-128x32", + "width": 128, + "standalone": false, + "config": { + "enabled": true, + "display_mode": "overhead", + "map_background": { + "enabled": false + } + } + }, + { + "name": "size-128x64", + "width": 128, + "height": 64, + "standalone": false, + "config": { + "enabled": true, + "display_mode": "overhead", + "map_background": { + "enabled": false + } + } + }, + { + "name": "size-256x32", + "width": 256, + "standalone": false, + "config": { + "enabled": true, + "display_mode": "overhead", + "map_background": { + "enabled": false + } + } + } + ], + "composites": [ + { + "name": "display-modes", + "columns": 1, + "cells": [ + { + "shot": "mode-overhead", + "label": "display_mode: overhead", + "sublabel": "one aircraft at a time, in detail" + }, + { + "shot": "mode-map", + "label": "display_mode: map", + "sublabel": "positions plotted around you" + }, + { + "shot": "mode-stats", + "label": "display_mode: stats" + }, + { + "shot": "mode-area", + "label": "display_mode: area" + } + ] + }, + { + "name": "units", + "columns": 1, + "cells": [ + { + "shot": "units-imperial", + "label": "units: imperial", + "sublabel": "feet and knots" + }, + { + "shot": "units-metric", + "label": "units: metric" + } + ] + }, + { + "name": "track-format", + "columns": 1, + "cells": [ + { + "shot": "track-deg", + "label": "track_format: deg" + }, + { + "shot": "track-cardinal", + "label": "track_format: cardinal" + } + ] + }, + { + "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" + } + ] + } + ] +} diff --git a/docs/assets/ledmatrix-flights/track-format.png b/docs/assets/ledmatrix-flights/track-format.png new file mode 100644 index 0000000000000000000000000000000000000000..0adca08a3662763d41e6a55ba9d72c0772f89bf5 GIT binary patch literal 14821 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 DXL#4b literal 0 HcmV?d00001 diff --git a/plugins.json b/plugins.json index 2dc509a1..d2e09b71 100644 --- a/plugins.json +++ b/plugins.json @@ -436,10 +436,10 @@ "plugin_path": "plugins/ledmatrix-flights", "stars": 0, "downloads": 0, - "last_updated": "2026-08-03", + "last_updated": "2026-09-03", "verified": true, "screenshot": "", - "latest_version": "1.12.18" + "latest_version": "1.12.19" }, { "id": "march-madness", diff --git a/plugins/ledmatrix-flights/README.md b/plugins/ledmatrix-flights/README.md index 013c7be2..06f0b64b 100644 --- a/plugins/ledmatrix-flights/README.md +++ b/plugins/ledmatrix-flights/README.md @@ -13,6 +13,13 @@ # LEDMatrix Flight Tracker Plugin +![An aircraft overhead on a 128x32 panel: airline tail, callsign, altitude and +speed, route, and distance](../../docs/assets/ledmatrix-flights/hero.png) + +*Every image in this README is real plugin output, rendered at the true panel +size from seeded aircraft so it reproduces exactly. Positions and callsigns are +invented.* + Real-time aircraft tracking plugin for LEDMatrix with ADS-B data, map backgrounds, flight plans, and proximity alerts. ## Features @@ -97,17 +104,205 @@ Add FlightAware API key to `config/config_secrets.json`: **Note:** The plugin will work without an API key for basic aircraft tracking, but flight plan features (origin/destination) will be disabled. -### Full Configuration Options +### Full configuration options + +Settings live in the plugin's tab in the web UI and in `config/config.json` +under `ledmatrix-flights`. The full schema is +[`config_schema.json`](config_schema.json). + +### Source and polling + +| Key | Default | Notes | +|---|---|---| +| `enabled` | `false` | Enable or disable the flight tracker plugin. | +| `data_source` | `"skyaware"` | Where to get live aircraft position data. Choose one setup path: (1) Local receiver — 'skyaware': requires a PiAware/dump1090 ADS-B receiver on your network, most accurate data. (2) Free cloud — 'adsbfi': free global ADS-B feed from adsb.fi, no account or hardware needed. 'adsblol': same idea via adsb.lol. Both free cloud options work worldwide with no sign-up — one of `skyaware`, `adsbfi`, `adsblol`. | +| `skyaware_url` | `"http://192.168.86.30/skyaware/data/aircraf…` | URL to your local ADS-B receiver's aircraft.json endpoint. Only used when data_source is 'skyaware'. Change this to match your receiver's IP address. | +| `update_interval` | `5` | Interval in seconds between aircraft data updates (1–300). | +| `live_update_interval` | `2` | Faster fetch interval (seconds) used while a flight is locked on for the overhead view, so altitude/distance update smoothly. The ADS-B source is ~1Hz, so a low value is safe. Idle fetches use update_interval (1–60). Advanced. | +| `live_priority` | `false` | When enabled, an aircraft entering the proximity radius immediately preempts the normal rotation to show the overhead view for the proximity alert window (see proximity_alert.duration_seconds). Adds a 'flight_tracker_live' rotation slot that stays blank/skipped until a plane is overhead. Leave off for legacy behavior. Advanced. | +| `display_duration` | `30` | How long to show the flight tracker display (10-300 seconds). | +| `flight_records` | — | Track all-time closest and farthest flights ever seen, shown as extra stats rotation slots. | +| `fr24_enrichment` | `true` | Fetch origin/destination and aircraft type for free in the background using FlightRadar24 data. Works with all data sources. This is the recommended free alternative to a paid FlightAware API key — leave enabled unless you have a specific reason to turn it off. Advanced. | +| `fr24_enrichment_interval` | `60` | How often to refresh free FR24 route data in the background. Applies when fr24_enrichment is enabled (30–600). Advanced. | +| `enrichment_provider` | `"adsbnet"` | Fallback source for origin/destination data not covered by FR24 enrichment. 'adsbnet': free route lookups via adsb.lol — good coverage for major airlines, no account needed (recommended). 'flightaware': paid FlightAware AeroAPI — highest coverage but requires a paid subscription configured in the FlightAware section below — one of `adsbnet`, `flightaware`. Advanced. | +| `route_cache_ttl` | `300` | How long to cache flight route enrichment data (60–3600). Advanced. | +| `flight_records.enabled` | `true` | Track and display all-time closest and farthest flights (persisted across restarts). | + +### Where you are, and what counts as nearby + +| Key | Default | Notes | +|---|---|---| +| `center_latitude` | `27.9506` | Center latitude for the map display (-90–90). | +| `center_longitude` | `-82.4572` | Center longitude for the map display (-180–180). | +| `map_radius_miles` | `10` | Radius in miles to display around center point (1–100). | +| `zoom_factor` | `1.0` | Zoom factor to use more of the display (1.0 = normal, higher = more zoomed in) (0.5–5.0). Advanced. | +| `max_aircraft` | `5` | Maximum aircraft to display per page in area mode (1–20). Advanced. | +| `min_altitude_ft` | `0` | Minimum altitude in feet for area mode filtering. Set to 0 to disable. Advanced. | +| `max_altitude_ft` | `0` | Maximum altitude in feet for area mode filtering. Set to 0 to disable. Advanced. | +| `aircraft_categories` | *(empty)* | ICAO aircraft categories to display (e.g. A1, A2, A3). Empty list shows all. Advanced. | +| `tracked_flights` | *(empty)* | Flight numbers, callsigns, or tail numbers to track specifically (max 3). E.g. ["AA123", "N12345"]. | +| `anchor_airport` | *(blank)* | ICAO or IATA airport code to prioritize in area mode (e.g. KTPA or TPA). Arrivals/departures shown first. | + +### Display mode and layout + +| Key | Default | Notes | +|---|---|---| +| `display_mode` | `"auto"` | Single-view display mode, used only when 'rotation_views' is not set. 'area' shows multi-aircraft list with FlightWall metrics. 'flight_tracking' shows tracked flight details. 'auto' chooses based on context — one of `map`, `overhead`, `stats`, `area`, `flight_tracking`, `auto`. | +| `rotation_views` | — | Which views the display rotation cycles through. Each selected view gets its own rotation slot and an empty/no-content slot is skipped. Leave all unchecked to show nothing in the normal rotation — useful for an overhead-only board (pair with Overhead Live Priority). When this field is omitted entirely, the single 'Display Mode' setting is used instead (legacy behavior). The overhead view is not listed here; it is driven by live_priority + proximity_alert. | +| `layout` | *(blank)* | Force a specific flight detail layout. Leave empty for auto-selection based on display width — one of `""`, `flight_detail_wide`, `flight_detail_condensed`. Advanced. | +| `widescreen_threshold` | `256` | Minimum canvas width in pixels to use the widescreen flight detail layout (128–1280). Advanced. | +| `show_banner` | `false` | Show a 'FLIGHTS' banner for 2 seconds at the start of each display slot. Advanced. | +| `show_aircraft_icon` | `true` | Show airline logos in area mode (8×8 pixel sprites next to callsigns). Flight detail layouts always show logos in the logo zone when available (52 airlines included as PNG assets). Advanced. | +| `show_trails` | `true` | Show aircraft movement trails. Advanced. | +| `trail_length` | `10` | Number of trail points to display per aircraft (0–50). Advanced. | +| `scroll_speed` | `2` | Scroll speed in pixels per frame for long text (1–10). Advanced. | +| `overhead_alt_interval` | `4` | On the overhead (live-priority) card, the route (e.g. SEA>PHX) and the aircraft model share one text row and alternate. This is how many seconds each stays up before swapping. Set to 0 to disable alternation (route always wins). The model is shown as a friendly name (e.g. 'Boeing 737-900') when the type is known. Advanced. | + +### Units and formatting + +| Key | Default | Notes | +|---|---|---| +| `units` | `"imperial"` | Legacy unit system toggle. For finer control use the per-metric options below — one of `imperial`, `metric`. | +| `altitude_unit` | `"ft"` | Altitude display unit — one of `ft`, `m`, `km`, `nmi`. Advanced. | +| `speed_unit` | `"kn"` | Speed display unit — one of `kn`, `mph`, `kmh`, `ms`, `mach`. Advanced. | +| `track_format` | `"deg"` | Track/heading display format. 'deg' = numeric degrees (090deg), 'cardinal' = compass point (E) — one of `deg`, `cardinal`. Advanced. | +| `vr_unit` | `"fpm"` | Vertical rate display unit — one of `fpm`, `fts`, `ms`, `mph`, `kmh`. Advanced. | + +### Colours + +| Key | Default | Notes | +|---|---|---| +| `header_color` | `[255, 200, 0]` | RGB color for headers/callsigns in area and flight tracking modes. Advanced. | +| `metric_color` | `[255, 255, 255]` | RGB color for metric values (alt, speed, etc.) in area and flight tracking modes. Advanced. | +| `error_color` | `[255, 0, 0]` | RGB color for error/no-data messages. Advanced. | +| `airport_color` | `[0, 120, 255]` | RGB color for airport full names in widescreen layout. Advanced. | + +### Offline aircraft database + +| Key | Default | Notes | +|---|---|---| +| `use_offline_database` | `true` | Use offline aircraft database for aircraft type lookups. | +| `offline_database_auto_update` | `true` | Automatically update offline database. | +| `offline_database_update_interval_days` | `30` | Interval in days between offline database updates (1–365). | + +### Map background + +| Key | Default | Notes | +|---|---|---| +| `map_background.enabled` | `true` | Enable map background tiles. | +| `map_background.tile_provider` | `"carto_dark"` | Map tile provider — one of `osm`, `carto`, `carto_dark`, `stamen`, `esri`. | +| `map_background.tile_size` | `256` | Tile size in pixels. Advanced. | +| `map_background.cache_ttl_hours` | `8760` | Tile cache time-to-live in hours (1–8760). Advanced. | +| `map_background.fade_intensity` | `0.4` | Fade intensity for map background (0.0-1.0). Advanced. | +| `map_background.brightness` | `1.0` | Brightness adjustment for map (0.0-2.0). Advanced. | +| `map_background.contrast` | `1.0` | Contrast adjustment for map (0.0-2.0). Advanced. | +| `map_background.saturation` | `1.0` | Saturation adjustment for map (0.0-2.0). Advanced. | +| `map_background.disable_on_cache_error` | `false` | Disable map background if cache errors occur. Advanced. | +| `map_background.custom_tile_server` | `"https://maps.chuck-builds.com"` | Custom tile server URL (optional, for self-hosted OSM servers). When set, this overrides the tile_provider setting. Leave empty to use the tile_provider setting instead. Advanced. | + +### Airport weather + +| Key | Default | Notes | +|---|---|---| +| `metar.enabled` | `false` | Fetch and display METAR/TAF/PIREP/SIGMET for the airports below. | +| `metar.airports` | *(empty)* | ICAO (e.g. KTPA) or IATA (e.g. TPA) airport codes. The display rotates through each airport's weather. Example: ["KTPA", "KJFK"]. | +| `metar.show_taf` | `true` | Show the terminal aerodrome forecast (raw) on a page after each airport's METAR. | +| `metar.show_raw` | `true` | Show the raw METAR string on its own page in addition to the decoded card. | +| `metar.show_pirep` | `false` | Show recent pilot reports near each airport. Advanced. | +| `metar.show_sigmet` | `false` | Show active domestic SIGMET/AIRMET advisories (region-based, not airport-specific). Advanced. | +| `metar.pirep_distance_nm` | `200` | How far from each airport to search for pilot reports (25–500). Advanced. | +| `metar.update_interval_minutes` | `10` | How often to refresh weather from NOAA. METARs update roughly hourly, so 10 minutes is plenty (5–120). Advanced. | +| `metar.page_duration_seconds` | `8` | How long each weather page (decoded METAR, raw, TAF, etc.) is shown before advancing (3–60). Advanced. | +| `metar.altimeter_unit` | `"inhg"` | Altimeter setting display: 'inhg' shows US style (A30.01), 'hpa' shows international style (Q1016) — one of `inhg`, `hpa`. Advanced. | +| `metar.temp_unit` | `"c"` | Temperature/dewpoint unit on the decoded card — one of `c`, `f`. Advanced. | +| `metar.wind_unit` | `"kt"` | Wind speed unit. Aviation standard is knots (kt) — one of `kt`, `mph`, `kmh`, `ms`. Advanced. | +| `metar.visibility_unit` | `"sm"` | Visibility unit: statute miles (US), metres or kilometres (international) — one of `sm`, `m`, `km`. Advanced. | + +### Proximity alert + +| Key | Default | Notes | +|---|---|---| +| `proximity_alert.enabled` | `true` | Enable proximity alerts. | +| `proximity_alert.distance_miles` | `0.1` | Distance threshold in miles for proximity alert (0.01–10.0). | +| `proximity_alert.duration_seconds` | `30` | Hard cap (seconds) on how long a single overhead flight holds the screen, measured from when it is locked on. The flight shows for this long even if it leaves the radius sooner, and is released after this even if it lingers (5–300). Advanced. | +| `proximity_alert.cooldown_seconds` | `30` | After a flight's window ends, suppress the overhead preempt for this many seconds so the normal rotation (weather/clock/sports) gets guaranteed screen time, even if planes remain overhead. Set to 0 to disable the cooldown (0–600). Advanced. | + +### Fonts + +| Key | Default | Notes | +|---|---|---| +| `fonts.large_size` | `0` | Override size for the large font tier. 0 = auto (0–32). Advanced. | +| `fonts.medium_size` | `0` | Override size for the medium font tier. 0 = auto (0–32). Advanced. | +| `fonts.small_size` | `0` | Override size for the small font tier. 0 = auto (0–32). Advanced. | + +### FlightAware / OpenSky + +| Key | Default | Notes | +|---|---|---| +| `flightaware.api_key` | *(blank)* | Your FlightAware AeroAPI key. Leave blank if you are not using FlightAware — the plugin works fully without this. Secret, masked in the web UI. | +| `flightaware.enabled` | `false` | Enable paid FlightAware API calls. Must be true AND api_key must be set for FlightAware to be used. Disabled by default — do not enable unless you have a paid subscription. | +| `flightaware.max_api_calls_per_hour` | `25` | Maximum FlightAware API calls per hour (1–100). Advanced. | +| `flightaware.daily_api_budget` | `60` | Maximum FlightAware API calls per day (1–200). Advanced. | +| `flightaware.cache_ttl_hours` | `12` | Flight plan cache time-to-live in hours (1–168). Advanced. | +| `flightaware.min_callsign_length` | `4` | Minimum callsign length to fetch flight plan data (3–10). Advanced. | +| `flightaware.airline_callsign_prefixes` | `["AAL", "UAL", "DAL", "SWA", "JBU", "ASQ", …` | List of airline callsign prefixes to prioritize for flight plan fetching. Advanced. | +| `flightaware.background_service` | — | Background service configuration for fetching flight plan data without blocking the display. | + +### Background service + +| Key | Default | Notes | +|---|---|---| +| `background_service.enabled` | `true` | . | +| `background_service.fetch_interval_hours` | `4` | . Advanced. | +| `background_service.max_calls_per_run` | `10` | . Advanced. | + +The FlightAware block has its own background-service settings, separate from +the top-level `background_service`: + +| Key | Default | Notes | +|---|---|---| +| `flightaware.background_service.enabled` | `true` | Enable the background service for flight plan fetching. | +| `flightaware.background_service.fetch_interval_hours` | `4` | Interval in hours between background fetch runs (1–168). Advanced. | +| `flightaware.background_service.max_calls_per_run` | `10` | Maximum number of flight plan calls fetched per background run (1–100). Advanced. | + +### Legacy flat API keys + +The plugin accepts the FlightAware and OpenSky credentials either nested under +`flightaware` (above) or as flat top-level keys. `_normalize_flightaware_config()` +copies the nested form onto the flat names once at start-up, so both work and +you only need to set one. + + +| Key | Default | Notes | +|---|---|---| +| `opensky_username` | *(blank)* | OpenSky Network username. Only needed if you have manually set enrichment_provider to 'opensky' via direct config edit. Leave blank for the standard free setup. Secret, masked in the web UI. Advanced. | +| `opensky_password` | *(blank)* | OpenSky Network password. Only needed alongside opensky_username above. Secret, masked in the web UI. Advanced. | +| `flightaware_api_key` | *(blank)* | Deprecated — use the FlightAware section above instead. Kept only for backward compatibility with older configs. Secret, masked in the web UI. Advanced. | +| `flight_plan_enabled` | `false` | Deprecated: moved to FlightAware section. Advanced. | +| `max_api_calls_per_hour` | `25` | Deprecated: moved to FlightAware section. Advanced. | +| `daily_api_budget` | `60` | Deprecated: moved to FlightAware section. Advanced. | +| `flight_plan_cache_ttl_hours` | `12` | Deprecated: moved to FlightAware section. Advanced. | +| `min_callsign_length` | `4` | Deprecated: moved to FlightAware section. Advanced. | +| `airline_callsign_prefixes` | *(empty)* | Deprecated: moved to FlightAware section. Advanced. | -See `config_schema.json` for complete configuration options including: -- Map background settings (tile provider, brightness, contrast, saturation) -- Proximity alert configuration -- Flight plan fetching settings -- Background service configuration -- Offline database settings ## Display Modes +![The four display modes](../../docs/assets/ledmatrix-flights/display-modes.png) + +`map` is shown here with `map_background.enabled` off. With it on, the aircraft +are plotted over basemap tiles fetched from a tile server — that is why this +one screen cannot be reproduced offline the way the others are. + +Units and heading format apply across the modes: + +![imperial and metric](../../docs/assets/ledmatrix-flights/units.png) + +![degrees and cardinal headings](../../docs/assets/ledmatrix-flights/track-format.png) + +![The same aircraft on four panel sizes](../../docs/assets/ledmatrix-flights/panel-sizes.png) + + ### Map Mode (`display_mode: "map"`) Shows a geographic map with: diff --git a/plugins/ledmatrix-flights/manager.py b/plugins/ledmatrix-flights/manager.py index 237b0267..ca513d50 100644 --- a/plugins/ledmatrix-flights/manager.py +++ b/plugins/ledmatrix-flights/manager.py @@ -3200,8 +3200,11 @@ def _render_map_image(self) -> Image.Image: continue x, y = pixel - # Brighten the plane colors by boosting RGB values - base_color = aircraft['color'] + # Brighten the plane colors by boosting RGB values. + # Every other read of this field uses .get with a fallback; a record + # that reached the map without one took the whole mode to ERR: map + # while the other modes rendered it fine. + base_color = aircraft.get('color') or (255, 255, 255) color = tuple(min(255, int(c * 1.3)) for c in base_color) # Draw single pixel for each aircraft diff --git a/plugins/ledmatrix-flights/manifest.json b/plugins/ledmatrix-flights/manifest.json index b0196b80..37154315 100644 --- a/plugins/ledmatrix-flights/manifest.json +++ b/plugins/ledmatrix-flights/manifest.json @@ -1,7 +1,7 @@ { "id": "ledmatrix-flights", "name": "Flight Tracker", - "version": "1.12.18", + "version": "1.12.19", "description": "Real-time aircraft tracking with ADS-B/FlightRadar24/OpenSky/adsb.fi/adsb.lol data, map backgrounds, area mode, flight tracking, anchor airport, flight records, and optional airport weather (METAR/TAF/PIREP/SIGMET via the free NOAA Aviation Weather Center API)", "author": "ChuckBuilds", "entry_point": "manager.py", @@ -37,6 +37,12 @@ "min_ledmatrix_version": "2.0.0", "max_ledmatrix_version": "3.0.0", "versions": [ + { + "version": "1.12.19", + "released": "2026-09-03", + "notes": "Documentation, and one map-mode robustness fix. The README's 'Full Configuration Options' section pointed at the schema file instead of listing anything; 84 of the 96 settings were never named. All 96 are now documented by key, grouped by area. Adds real rendered screenshots of the four display modes, both unit systems, both heading formats and four panel sizes. Map mode read aircraft['color'] with a hard subscript where every other read of that field uses .get with a fallback, so a record arriving without one took the whole mode to 'ERR: map' while the other modes rendered it; it now falls back.", + "ledmatrix_min_version": "2.0.0" + }, { "version": "1.12.18", "released": "2026-08-26", @@ -246,5 +252,5 @@ "ledmatrix_min_version": "2.0.0" } ], - "last_updated": "2026-08-03" + "last_updated": "2026-09-03" }