Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions plugins.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": "1.0.0",
"last_updated": "2026-08-20",
"last_updated": "2026-08-19",
"plugins": [
{
"id": "cricket-scoreboard",
Expand Down Expand Up @@ -439,7 +439,7 @@
"last_updated": "2026-08-03",
"verified": true,
"screenshot": "",
"latest_version": "1.12.13"
"latest_version": "1.12.14"
},
{
"id": "march-madness",
Expand Down
34 changes: 23 additions & 11 deletions plugins/ledmatrix-flights/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2406,17 +2406,29 @@ def _get_map_background(self, center_lat: float, center_lon: float,
self.logger.debug(f"[Flight Tracker] Map displays {desired_miles_wide:.1f} miles wide x {desired_miles_high:.1f} miles high (no stretching)")
self.logger.debug(f"[Flight Tracker] Native tile scale: {pixels_per_mile_at_zoom:.3f} pixels/mile, cropped {crop_width_needed}x{crop_height_needed} pixels, scaled to {self.display_width}x{self.display_height}")

# Debug: Save composite image to see what's happening
try:
debug_composite = Path("debug_composite.png")
composite.save(debug_composite)
self.logger.debug(f"[Flight Tracker] Saved composite to: {debug_composite}")

debug_cropped = Path("debug_cropped.png")
cropped.save(debug_cropped)
self.logger.debug(f"[Flight Tracker] Saved cropped to: {debug_cropped}")
except Exception as e:
self.logger.debug(f"[Flight Tracker] Could not save debug images: {e}")
# Debug aid, off unless debug logging is on. This used to run
# unconditionally, writing both PNGs on every composite: measured on a
# live rig at 5.36 MB for the composite and 0.04 MB for the crop, five
# composites in six hours -- about 108 MB a day written to an SD card
# for files nothing reads. They also landed in the process's working
# directory, which is the install root, where a pre-commit hook has
# previously swept them into a commit.
if self.logger.isEnabledFor(logging.DEBUG):
debug_dir = getattr(self, "tile_cache_dir", None)
if debug_dir is None:
self.logger.debug(
"[Flight Tracker] No cache directory; skipping debug images")
else:
try:
composite_path = Path(debug_dir) / "debug_composite.png"
composite.save(composite_path)
cropped_path = Path(debug_dir) / "debug_cropped.png"
cropped.save(cropped_path)
self.logger.debug(
"[Flight Tracker] Saved debug images to: %s", debug_dir)
except OSError as e:
self.logger.debug(
f"[Flight Tracker] Could not save debug images: {e}")

return cropped

Expand Down
8 changes: 7 additions & 1 deletion plugins/ledmatrix-flights/manifest.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "ledmatrix-flights",
"name": "Flight Tracker",
"version": "1.12.13",
"version": "1.12.14",
"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",
Expand Down Expand Up @@ -37,6 +37,12 @@
"min_ledmatrix_version": "2.0.0",
"max_ledmatrix_version": "3.0.0",
"versions": [
{
"version": "1.12.14",
"released": "2026-08-19",
"ledmatrix_min_version": "2.0.0",
"notes": "Stop writing debug images on every map composite. Two PNGs were saved unconditionally -- no debug flag, no config check, under a comment reading \"Debug: Save composite image to see what's happening\". Measured on a live rig they are 5.36 MB and 0.04 MB, written five times in six hours: about 108 MB a day onto an SD card for files nothing reads. They also landed in the process working directory, which is the install root. They are now written only when debug logging is enabled, and into the plugin's own tile cache directory."
},
{
"version": "1.12.13",
"released": "2026-08-19",
Expand Down
83 changes: 83 additions & 0 deletions plugins/ledmatrix-flights/test_no_debug_image_writes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""Composing the map must not write debug PNGs unless debug logging is on.

_get_map_background() saved two images on every composite, ungated -- no debug
flag, no config check, and the comment above it read "Debug: Save composite
image to see what's happening". Left-over debugging shipped as production
behaviour.

Measured on a live rig: debug_composite.png is 5.36 MB and debug_cropped.png
0.04 MB, with five composites in six hours. That is roughly 108 MB a day
written to an SD card for files nothing reads, on a device whose cards have
already failed twice. Honouring the ticker's narrower render request makes the
plugin compose at two widths rather than one, so the count roughly doubles.

They also landed in the process's working directory -- the install root --
where a pre-commit hook has previously swept them into a commit.

Run: <core-venv>/bin/python plugins/ledmatrix-flights/test_no_debug_image_writes.py
"""

import ast
import sys
from pathlib import Path

PLUGIN = Path(__file__).resolve().parent
MANAGER = PLUGIN / "manager.py"
failures = []


def check(label, ok):
print((" PASS " if ok else " FAIL ") + label)
if not ok:
failures.append(label)


def _save_calls(tree):
"""Every `<something>.save(...)` in the module, with its line number."""
return [n for n in ast.walk(tree)
if isinstance(n, ast.Call)
and getattr(n.func, "attr", None) == "save"]


def _guarding_debug_check(tree, lineno):
"""True when the statement at `lineno` sits under an isEnabledFor guard."""
for node in ast.walk(tree):
if not isinstance(node, ast.If):
continue
test = ast.dump(node.test)
if "isEnabledFor" not in test:
continue
for child in ast.walk(node):
if getattr(child, "lineno", None) == lineno:
return True
return False


def main():
tree = ast.parse(MANAGER.read_text(encoding="utf-8"))

print("debug images are written only when debug logging is enabled")
saves = _save_calls(tree)
check(f"{len(saves)} save() call(s) found in manager.py", bool(saves))
for call in saves:
guarded = _guarding_debug_check(tree, call.lineno)
check(f"the save at line {call.lineno} is behind a debug-level guard",
guarded)

print("\nand not into the process working directory")
source = MANAGER.read_text(encoding="utf-8")
check('no bare Path("debug_composite.png")',
'Path("debug_composite.png")' not in source)
check('no bare Path("debug_cropped.png")',
'Path("debug_cropped.png")' not in source)
check("debug images go under the tile cache directory",
"tile_cache_dir" in source.split("debug_composite.png")[0][-600:])

print("\n%s" % ("FAILED: %d" % len(failures) if failures
else "All checks passed"))
return 1 if failures else 0


if __name__ == "__main__":
sys.exit(main())
Loading