Implement story 1.1: project scaffold and verified entry points

Creates the complete src/planemapper/ layout with all module stubs,
provisioning and renderer subpackages, airports.csv data bundle,
systemd unit files, and full test scaffold. All three quality gates
pass: pytest 12/12, ruff check zero violations, ruff format clean.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt Edholm
2026-04-22 22:26:53 -04:00
parent 0612e0fe02
commit 85c8acf767
71 changed files with 85541 additions and 56 deletions
View File
Binary file not shown.
Binary file not shown.
+42
View File
@@ -0,0 +1,42 @@
from pathlib import Path
DISPLAY_WIDTH = 800
DISPLAY_HEIGHT = 480
REFRESH_INTERVAL_S = 60
FETCH_TIMEOUT_S = 5
RENDER_WARN_S = 40
RENDER_ALERT_S = 50
STALE_CYCLES = 1
RESET_HOLD_S = 3
ALTITUDE_BANDS_FT = [1500, 5000, 10000, 20000, 35000, 99999]
COLOUR_BLACK = (0, 0, 0)
COLOUR_WHITE = (255, 255, 255)
COLOUR_RED = (255, 0, 0)
COLOUR_YELLOW = (255, 255, 0)
COLOUR_BLUE = (0, 0, 255)
COLOUR_GREEN = (0, 255, 0)
ALTITUDE_COLOURS = [
COLOUR_GREEN,
COLOUR_BLUE,
COLOUR_YELLOW,
COLOUR_RED,
COLOUR_BLACK,
COLOUR_WHITE,
]
COLOUR_STALE_OUTLINE = COLOUR_BLACK
COLOUR_HOME_MARKER = COLOUR_RED
COLOUR_AIRSPACE = COLOUR_BLUE
COLOUR_TRAIL = COLOUR_BLACK
TRAIL_MAX_DOTS = 5
TRAIL_DOT_SIZE_MAX = 6
TRAIL_DOT_SIZE_MIN = 2
CONFIG_PATH = Path("/etc/planemapper/config.json")
AIRSPACE_PATH = Path("/etc/planemapper/airspace.geojson")
BACKGROUND_PATH = Path("/etc/planemapper/background.png")
View File
File diff suppressed because it is too large Load Diff
+12
View File
@@ -0,0 +1,12 @@
from typing import Protocol
from PIL import Image
class DisplayInterface(Protocol):
def show(self, image: Image.Image) -> None: ...
class NullDisplay:
def show(self, image: Image.Image) -> None:
pass
+7
View File
@@ -0,0 +1,7 @@
from typing import Protocol
from planemapper.models import Aircraft
class FetcherInterface(Protocol):
def fetch(self) -> list[Aircraft]: ...
+11
View File
@@ -0,0 +1,11 @@
class ButtonHoldDetector:
def check(self) -> bool:
return False
class LEDController:
def on(self) -> None:
pass
def off(self) -> None:
pass
+8
View File
@@ -0,0 +1,8 @@
import logging
log = logging.getLogger(__name__)
def main() -> None:
logging.basicConfig(level=logging.INFO)
log.info("not implemented")
+14
View File
@@ -0,0 +1,14 @@
from dataclasses import dataclass
@dataclass
class Aircraft:
icao: str
lat: float
lon: float
heading: float = 0.0
altitude_ft: int = 0
callsign: str = ""
category: str = ""
is_mlat: bool = False
is_stale: bool = False
+8
View File
@@ -0,0 +1,8 @@
import logging
log = logging.getLogger(__name__)
def main() -> None:
logging.basicConfig(level=logging.INFO)
log.info("not implemented")
+2
View File
@@ -0,0 +1,2 @@
class ProvisioningError(Exception):
pass
+1
View File
@@ -0,0 +1 @@
# stub
+1
View File
@@ -0,0 +1 @@
# stub
+1
View File
@@ -0,0 +1 @@
# stub
+1
View File
@@ -0,0 +1 @@
# stub
+1
View File
@@ -0,0 +1 @@
# stub
+1
View File
@@ -0,0 +1 @@
# stub
+1
View File
@@ -0,0 +1 @@
# stub
+1
View File
@@ -0,0 +1 @@
# stub
+1
View File
@@ -0,0 +1 @@
# stub
+1
View File
@@ -0,0 +1 @@
# stub
+1
View File
@@ -0,0 +1 @@
# stub
+1
View File
@@ -0,0 +1 @@
# stub
+1
View File
@@ -0,0 +1 @@
# stub