Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
9a0258d
fix: Enable DietPi automation scripts for install.sh preservation
mverteuil Nov 2, 2025
2999bc3
fix: Copy install.sh to rootfs partition to survive DIETPISETUP deletion
mverteuil Nov 2, 2025
259b4d8
feat: Enable macOS ext4 rootfs mounting via anylinuxfs
mverteuil Nov 2, 2025
e5e32c7
fix: Use partition 3 for rootfs and create /root directory
mverteuil Nov 2, 2025
d7f1e65
debug: Add partition discovery output for troubleshooting
mverteuil Nov 2, 2025
1fbbce7
fix: Use partition 1 for rootfs on DietPi Orange Pi 5 Pro
mverteuil Nov 2, 2025
50c844c
feat: Use anylinuxfs to mount DietPi rootfs on macOS
mverteuil Nov 2, 2025
54828d4
fix: Detect anylinuxfs mount by comparing /Volumes before/after
mverteuil Nov 2, 2025
9caebdf
fix: Use dd instead of cp to avoid macOS extended attributes error
mverteuil Nov 3, 2025
551019f
fix: Create spi and gpio groups on DietPi/Orange Pi
mverteuil Nov 3, 2025
953b9ad
feat: Copy birdnetpi_config.txt to rootfs and add OS/device pre-confi…
mverteuil Nov 3, 2025
de9ec5f
feat: Add Orange Pi Zero 2W and remove Orange Pi 5
mverteuil Nov 3, 2025
3cfcd85
fix: Export OS and device keys as environment variables
mverteuil Nov 3, 2025
4d246ec
feat: Add SPI support for Orange Pi Zero 2W
mverteuil Nov 3, 2025
0a52dfe
fix: Export os_key and device_key in config file
mverteuil Nov 3, 2025
755b89f
refactor: Remove dead legacy code from flash_sdcard.py
mverteuil Nov 5, 2025
199071e
fix: Save WiFi and git branch settings to JSON config
mverteuil Nov 5, 2025
33ce552
fix: Update install.sh to parse JSON config instead of shell script
mverteuil Nov 5, 2025
74cdaab
fix: Update DietPi preservation scripts to use .json config files
mverteuil Nov 5, 2025
4706871
fix: Substitute repo URL and branch defaults in install.sh at flash time
mverteuil Nov 5, 2025
8c0d8fc
debug: Add logging to show config values during install.sh substitution
mverteuil Nov 5, 2025
551192f
fix: Use modified install.sh for rootfs copy instead of original
mverteuil Nov 5, 2025
d0663b7
fix: Show progress when installing Python dependencies
mverteuil Nov 5, 2025
7755eb8
fix: Copy config to install dir for birdnetpi user access
mverteuil Nov 5, 2025
ea303b7
fix: Add Orange Pi Zero 2W to supported devices
mverteuil Nov 5, 2025
e7f8264
perf: Enable uv package cache for faster retries
mverteuil Nov 5, 2025
1d0a48f
perf: Use tmpfs for uv cache and clean up after install
mverteuil Nov 5, 2025
8d3a169
fix: Add sudo access for systemd service status on DietPi/Orange Pi
mverteuil Nov 6, 2025
02e0d9e
feat: Enable SPI on DietPi/Armbian platforms for e-paper HAT support
mverteuil Nov 6, 2025
8ecfd99
fix: Check for both armbianEnv.txt and dietpiEnv.txt for SPI config
mverteuil Nov 6, 2025
8425853
fix: Respect platform-specific SPI overlays on Armbian/DietPi
mverteuil Nov 6, 2025
a01251b
fix: Only trigger reboot when boot config is modified
mverteuil Nov 6, 2025
cdf5529
fix: Handle RK3588 SPI overlay format in installer
mverteuil Nov 6, 2025
a170e91
fix: Configure correct SPI overlay at flash time for RK3588 platforms
mverteuil Nov 6, 2025
23c07d1
feat: Download Waveshare library at flash time for DietPi
mverteuil Nov 6, 2025
af17a55
refactor: Extract Waveshare download into common helper function
mverteuil Nov 6, 2025
e00d25e
fix(flasher): Copy only essential Waveshare files to boot partition
mverteuil Nov 6, 2025
35dd7db
fix: Resolve Waveshare library installation issues on DietPi
mverteuil Nov 8, 2025
8822048
fix: Use /tmp for uv cache instead of /dev/shm
mverteuil Nov 8, 2025
bec3cd7
feat: Add Orange Pi 5 + DietPi support for e-Paper HAT
mverteuil Nov 8, 2025
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
380 changes: 380 additions & 0 deletions install/devices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,380 @@
"""Device and OS configuration models for SD card flashing.

This module defines the hardware and operating system characteristics needed
for automated SD card preparation.
"""

from dataclasses import dataclass
from enum import Enum


class ChipFamily(str, Enum):
"""SoC chip families."""

BCM2711 = "bcm2711" # Raspberry Pi 4
BCM2712 = "bcm2712" # Raspberry Pi 5
RK3588 = "rk3588" # Rockchip (Orange Pi 5 series, ROCK 5B)
H618 = "h618" # Allwinner (Orange Pi Zero 2W)
S905X = "s905x" # Amlogic (Le Potato)


@dataclass(frozen=True)
class DeviceSpec:
"""Hardware device specification."""

key: str # Internal identifier (e.g., "orange_pi_5_pro")
name: str # Human-readable name (e.g., "Orange Pi 5 Pro")
chip: ChipFamily
has_wifi: bool
has_spi: bool

# OS-specific boot partition paths
# Key: os_key, Value: boot partition mount point
boot_partitions: dict[str, str]

# SPI overlay names for enabling ePaper HAT
# Key: os_key, Value: overlay name
spi_overlays: dict[str, str | None]


@dataclass(frozen=True)
class ImageSource:
"""OS image download source."""

url: str
sha256: str | None = None # Optional checksum
is_armbian: bool = False # Special handling for Armbian redirects
is_dietpi: bool = False # Special handling for DietPi


@dataclass(frozen=True)
class OSSpec:
"""Operating system specification."""

key: str # Internal identifier (e.g., "dietpi", "raspbian")
name: str # Human-readable name (e.g., "DietPi", "Raspberry Pi OS")

# Image URLs by device
# Key: device_key, Value: ImageSource
images: dict[str, ImageSource]


# Device definitions
DEVICES: dict[str, DeviceSpec] = {
# Raspberry Pi devices
"pi_zero2w": DeviceSpec(
key="pi_zero2w",
name="Raspberry Pi Zero 2 W",
chip=ChipFamily.BCM2711,
has_wifi=True,
has_spi=True,
boot_partitions={
"raspbian": "/boot/firmware",
"dietpi": "/boot/firmware",
},
spi_overlays={
"raspbian": None, # dtparam=spi=on in config.txt
"dietpi": None, # dtparam=spi=on in config.txt
},
),
"pi_3": DeviceSpec(
key="pi_3",
name="Raspberry Pi 3",
chip=ChipFamily.BCM2711,
has_wifi=True,
has_spi=True,
boot_partitions={
"raspbian": "/boot/firmware",
"dietpi": "/boot/firmware",
},
spi_overlays={
"raspbian": None,
"dietpi": None,
},
),
"pi_4": DeviceSpec(
key="pi_4",
name="Raspberry Pi 4",
chip=ChipFamily.BCM2711,
has_wifi=True,
has_spi=True,
boot_partitions={
"raspbian": "/boot/firmware",
"dietpi": "/boot/firmware",
},
spi_overlays={
"raspbian": None,
"dietpi": None,
},
),
"pi_5": DeviceSpec(
key="pi_5",
name="Raspberry Pi 5",
chip=ChipFamily.BCM2712,
has_wifi=True,
has_spi=True,
boot_partitions={
"raspbian": "/boot/firmware",
"dietpi": "/boot/firmware",
},
spi_overlays={
"raspbian": None,
"dietpi": None,
},
),
# Orange Pi devices
"orange_pi_0w2": DeviceSpec(
key="orange_pi_0w2",
name="Orange Pi Zero 2W",
chip=ChipFamily.H618,
has_wifi=True,
has_spi=True,
boot_partitions={
"armbian": "/boot",
"dietpi": "/boot",
},
spi_overlays={
"armbian": "spi-spidev", # Allwinner H618
"dietpi": "spi-spidev",
},
),
"orange_pi_5_plus": DeviceSpec(
key="orange_pi_5_plus",
name="Orange Pi 5 Plus",
chip=ChipFamily.RK3588,
has_wifi=True,
has_spi=True,
boot_partitions={
"armbian": "/boot",
"dietpi": "/boot",
},
spi_overlays={
"armbian": "rk3588-spi4-m0-cs1-spidev", # RK3588
"dietpi": "rk3588-spi4-m0-cs1-spidev",
},
),
"orange_pi_5_pro": DeviceSpec(
key="orange_pi_5_pro",
name="Orange Pi 5 Pro",
chip=ChipFamily.RK3588,
has_wifi=True,
has_spi=True,
boot_partitions={
"armbian": "/boot",
"dietpi": "/boot", # DIETPISETUP partition at /boot, not /boot/firmware
},
spi_overlays={
"armbian": "rk3588-spi4-m0-cs1-spidev",
"dietpi": "rk3588-spi4-m0-cs1-spidev",
},
),
# Other devices
"le_potato": DeviceSpec(
key="le_potato",
name="Libre Computer Le Potato",
chip=ChipFamily.S905X,
has_wifi=False,
has_spi=True,
boot_partitions={
"armbian": "/boot",
"dietpi": "/boot",
},
spi_overlays={
"armbian": None, # TODO: Verify SPI overlay for Amlogic
"dietpi": None,
},
),
"rock_5b": DeviceSpec(
key="rock_5b",
name="Radxa ROCK 5B",
chip=ChipFamily.RK3588,
has_wifi=False,
has_spi=True,
boot_partitions={
"armbian": "/boot",
"dietpi": "/boot",
},
spi_overlays={
"armbian": "rk3588-spi1-m1-cs0-spidev", # Alternative: spi3-m1
"dietpi": "rk3588-spi1-m1-cs0-spidev",
},
),
}


# Operating system definitions
OPERATING_SYSTEMS: dict[str, OSSpec] = {
"raspbian": OSSpec(
key="raspbian",
name="Raspberry Pi OS",
images={
"pi_zero2w": ImageSource(
url="https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-lite.img.xz",
sha256="3e8d1d7166aa832aded24e90484d83f4e8ad594b5a33bb4a9a1ff3ac0ac84d92",
),
"pi_3": ImageSource(
url="https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-lite.img.xz",
sha256="3e8d1d7166aa832aded24e90484d83f4e8ad594b5a33bb4a9a1ff3ac0ac84d92",
),
"pi_4": ImageSource(
url="https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-lite.img.xz",
sha256="3e8d1d7166aa832aded24e90484d83f4e8ad594b5a33bb4a9a1ff3ac0ac84d92",
),
"pi_5": ImageSource(
url="https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2024-07-04/2024-07-04-raspios-bookworm-arm64-lite.img.xz",
sha256="3e8d1d7166aa832aded24e90484d83f4e8ad594b5a33bb4a9a1ff3ac0ac84d92",
),
},
),
"armbian": OSSpec(
key="armbian",
name="Armbian",
images={
"orange_pi_0w2": ImageSource(
url="https://dl.armbian.com/orangepizero2w/Bookworm_current_minimal",
is_armbian=True,
),
"orange_pi_5_plus": ImageSource(
url="https://dl.armbian.com/orangepi5-plus/Bookworm_current_minimal",
is_armbian=True,
),
"orange_pi_5_pro": ImageSource(
url="https://dl.armbian.com/orangepi5pro/Trixie_vendor_minimal",
is_armbian=True,
),
"rock_5b": ImageSource(
url="https://dl.armbian.com/rock-5b/Bookworm_current_minimal",
is_armbian=True,
),
"le_potato": ImageSource(
url="https://dl.armbian.com/lepotato/Bookworm_current_minimal",
is_armbian=True,
),
},
),
"dietpi": OSSpec(
key="dietpi",
name="DietPi",
images={
"pi_zero2w": ImageSource(
url="https://dietpi.com/downloads/images/DietPi_RPiZero2W-ARMv8-Bookworm.img.xz",
is_dietpi=True,
),
"pi_3": ImageSource(
url="https://dietpi.com/downloads/images/DietPi_RPi3-ARMv8-Bookworm.img.xz",
is_dietpi=True,
),
"pi_4": ImageSource(
url="https://dietpi.com/downloads/images/DietPi_RPi4-ARMv8-Bookworm.img.xz",
is_dietpi=True,
),
"pi_5": ImageSource(
url="https://dietpi.com/downloads/images/DietPi_RPi5-ARMv8-Bookworm.img.xz",
is_dietpi=True,
),
"orange_pi_0w2": ImageSource(
url="https://dietpi.com/downloads/images/DietPi_OrangePiZero2W-ARMv8-Bookworm.img.xz",
is_dietpi=True,
),
"orange_pi_5_plus": ImageSource(
url="https://dietpi.com/downloads/images/DietPi_OrangePi5Plus-ARMv8-Bookworm.img.xz",
is_dietpi=True,
),
"orange_pi_5_pro": ImageSource(
url="https://dietpi.com/downloads/images/DietPi_OrangePi5Pro-ARMv8-Bookworm.img.xz",
is_dietpi=True,
),
"rock_5b": ImageSource(
url="https://dietpi.com/downloads/images/DietPi_ROCK5B-ARMv8-Bookworm.img.xz",
is_dietpi=True,
),
"le_potato": ImageSource(
url="https://dietpi.com/downloads/images/DietPi_LePotato-ARMv8-Bookworm.img.xz",
is_dietpi=True,
),
},
),
}


def get_boot_partition(device_key: str, os_key: str) -> str:
"""Get the boot partition mount point for a device/OS combination.

Args:
device_key: Device identifier (e.g., "orange_pi_5_pro")
os_key: OS identifier (e.g., "dietpi")

Returns:
Boot partition path (e.g., "/boot" or "/boot/firmware")

Raises:
KeyError: If device or OS combination is not supported
"""
device = DEVICES[device_key]
return device.boot_partitions[os_key]


def get_spi_overlay(device_key: str, os_key: str) -> str | None:
"""Get the SPI overlay name for a device/OS combination.

Args:
device_key: Device identifier
os_key: OS identifier

Returns:
SPI overlay name, or None if handled via config.txt dtparam

Raises:
KeyError: If device or OS combination is not supported
"""
device = DEVICES[device_key]
return device.spi_overlays[os_key]


def get_capabilities(device_key: str) -> dict[str, bool]:
"""Get hardware capabilities for a device.

Args:
device_key: Device identifier

Returns:
Dictionary with capability flags (has_wifi, has_spi)

Raises:
KeyError: If device is not supported
"""
device = DEVICES[device_key]
return {
"has_wifi": device.has_wifi,
"has_spi": device.has_spi,
}


def build_os_images_dict() -> dict[str, dict]:
"""Build legacy OS_IMAGES dictionary structure for TUI compatibility.

Returns:
Dictionary in format: {os_key: {"name": str, "devices": {device_key: {...}}}}
"""
os_images = {}

for os_key, os_spec in OPERATING_SYSTEMS.items():
devices_dict = {}
for device_key in os_spec.images.keys():
device_spec = DEVICES[device_key]
image_source = os_spec.images[device_key]

devices_dict[device_key] = {
"name": device_spec.name,
"url": image_source.url,
"sha256": image_source.sha256,
"is_armbian": image_source.is_armbian,
"is_dietpi": image_source.is_dietpi,
}

os_images[os_key] = {
"name": os_spec.name,
"devices": devices_dict,
}

return os_images
Loading