Build optimization: pre-built PM3 binaries, ARM64 CI, base image caching

Replace PM3 compile-from-source in pi-gen with pre-built tarball extraction
(saves 43-58 min). Merge stagePM3 into stageDangerousPi as 02-pm3-install
substage, renumber all subsequent substages. Switch CI PM3 build to native
ARM64 runner (ubuntu-24.04-arm64) eliminating QEMU overhead. Add weekly
base-image workflow for pre-baking stages 0-2. Support PM3_TARBALL,
BASE_IMAGE, and APT_PROXY env vars in build-image.sh.

Also includes prior Phase 5 work: theme system, design system integration,
component update system, OS updates, CI build pipeline, and test results.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
michael
2026-03-04 12:01:01 -08:00
parent 2ec89041ef
commit a9acdb85ce
163 changed files with 8124 additions and 921 deletions

View File

@@ -210,7 +210,7 @@ class BLEManager:
}
async def _check_bluetooth_adapter(self) -> bool:
"""Check if Bluetooth adapter is available.
"""Check if Bluetooth adapter is available, unblocking and powering on if needed.
Returns:
True if adapter is available, False otherwise
@@ -224,8 +224,20 @@ class BLEManager:
)
stdout, stderr = await process.communicate()
# If we get output with "Controller", we have an adapter
return b"Controller" in stdout
if b"Controller" not in stdout:
return False
# Unblock Bluetooth if soft-blocked by rfkill
await self._run_cmd("rfkill", "unblock", "bluetooth")
# Power on the adapter via bluetoothctl
out = await self._run_cmd("bluetoothctl", "power", "on")
if b"succeeded" in out.lower() or b"yes" in out.lower():
logger.info("Bluetooth adapter powered on")
else:
logger.warning("Bluetooth power on response: %s", out.decode(errors='replace').strip())
return True
except FileNotFoundError:
logger.warning("bluetoothctl not found - BlueZ not installed")
@@ -234,6 +246,19 @@ class BLEManager:
logger.error("Error checking Bluetooth adapter: %s", e)
return False
async def _run_cmd(self, *args: str) -> bytes:
"""Run a command and return stdout."""
try:
process = await asyncio.create_subprocess_exec(
*args,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE
)
stdout, _ = await process.communicate()
return stdout
except FileNotFoundError:
return b""
async def _set_device_name(self, name: str):
"""Set the Bluetooth device name.