feat(pi3 phase 3.1+3.2): Pi 3/4/5 family via PI_CONFIGS
Backend: extract per-board config into a PI_CONFIGS dict keyed by
board_type. Pi 3/4/5 share the same arm64 image set (kernel +
initramfs + rootfs) and differ only in QEMU -cpu and -m:
raspberry-pi-3 → cortex-a53 + 1G (BCM2837, ARMv8 64-bit)
raspberry-pi-4 → cortex-a72 + 2G (BCM2711, ARMv8 64-bit)
raspberry-pi-5 → cortex-a76 + 2G (BCM2712, ARMv8 64-bit)
PiInstance now carries board_type so the per-board lookup happens
once at start_instance time. Unknown board_type falls back to
DEFAULT_PI_BOARD ('raspberry-pi-3') instead of erroring out (for
back-compat with older clients).
Pre-warm hook walks every unique image_set in PI_CONFIGS so the
provider only downloads each set once even when several Pi models
are registered.
Frontend:
- BoardKind union gains 'raspberry-pi-4' and 'raspberry-pi-5'.
- BOARD_KIND_LABELS + BOARD_KIND_FQBN entries for both new boards
(FQBN null since they use the Pi VFS + Python toolchain like Pi 3).
- ComponentRegistry inserts two new component metadata entries
cloning the Pi 3 board art with different thumbnail colours.
Tag name reused so the same velxio-raspberry-pi-3 web element
draws the board on the canvas — the 40-pin GPIO layout is
identical across Pi 3/4/5.
- boardProtocols.ts: Pi 3/4/5 share the BCM physical→GPIO table
(PI3_BCM) since the 40-pin header layout is identical.
- loadExample.ts: where 'raspberry-pi-3' is special-cased (VFS
ingest, .cpp vs .ino filename), now matches Pi 3/4/5 alike.
- Interconnect.isPi3Bridge() recognises all three Pi family members
so Arduino↔Pi serial routing keeps working.
- RaspberryPi3Bridge constructor gained a boardKind parameter
defaulting to 'raspberry-pi-3'. The WebSocket 'start_pi' message
now ships the actual board kind so the backend knows which
PI_CONFIGS entry to use.
- useSimulatorStore.addBoard wires bridge construction for all
three Pi family members.
Pi Zero/Pi 1/Pi 2 (armhf) come in Phase 3.3 — separate kernel
package + armhf rootfs build, no change here.
Smoke-tested inside the prod container:
Pi 4 (cortex-a72) → reached agetty login on hvc0
Pi 5 (cortex-a76) → reached agetty login on hvc0
Both show 'aarch64' in uname -m.
This commit is contained in:
parent
32f5b407af
commit
db5e3a8623
|
|
@ -47,14 +47,52 @@ from app.services.boot_images import (
|
|||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Image-set id (matches a key in `boot_images/manifest.json`).
|
||||
PI3_IMAGE_SET = 'raspberry-pi-3-virt'
|
||||
# Per-board configuration. Pi 3/4/5 share the same arm64 image set;
|
||||
# Pi Zero/1/2 (Phase 3 deliverable) will share an armhf image set.
|
||||
# Only -cpu, -smp, -m, qemu binary and which image-set the provider
|
||||
# fetches vary per model.
|
||||
#
|
||||
# CPU choice notes:
|
||||
# raspberry-pi-3 → Cortex-A53 (BCM2837, ARMv8 64-bit)
|
||||
# raspberry-pi-4 → Cortex-A72 (BCM2711, ARMv8 64-bit)
|
||||
# raspberry-pi-5 → Cortex-A76 (BCM2712, ARMv8 64-bit)
|
||||
PI_CONFIGS: dict[str, dict] = {
|
||||
'raspberry-pi-3': {
|
||||
'qemu': 'qemu-system-aarch64',
|
||||
'cpu': 'cortex-a53',
|
||||
'smp': '4',
|
||||
'memory': '1G',
|
||||
'image_set': 'raspberry-pi-3-virt',
|
||||
'kernel': 'velxio-kernel-arm64',
|
||||
'initramfs': 'velxio-initramfs-arm64.cpio.gz',
|
||||
'rootfs': 'velxio-pi-rootfs-arm64.ext4',
|
||||
},
|
||||
'raspberry-pi-4': {
|
||||
'qemu': 'qemu-system-aarch64',
|
||||
'cpu': 'cortex-a72',
|
||||
'smp': '4',
|
||||
'memory': '2G',
|
||||
'image_set': 'raspberry-pi-3-virt', # same arm64 image set as Pi 3
|
||||
'kernel': 'velxio-kernel-arm64',
|
||||
'initramfs': 'velxio-initramfs-arm64.cpio.gz',
|
||||
'rootfs': 'velxio-pi-rootfs-arm64.ext4',
|
||||
},
|
||||
'raspberry-pi-5': {
|
||||
'qemu': 'qemu-system-aarch64',
|
||||
'cpu': 'cortex-a76',
|
||||
'smp': '4',
|
||||
'memory': '2G',
|
||||
'image_set': 'raspberry-pi-3-virt', # same arm64 image set
|
||||
'kernel': 'velxio-kernel-arm64',
|
||||
'initramfs': 'velxio-initramfs-arm64.cpio.gz',
|
||||
'rootfs': 'velxio-pi-rootfs-arm64.ext4',
|
||||
},
|
||||
# Pi Zero/1/2 require armhf — added in Phase 3.3.
|
||||
}
|
||||
|
||||
# Filenames the provider materialises (must match `name` fields in the
|
||||
# manifest entry for PI3_IMAGE_SET).
|
||||
PI3_KERNEL_NAME = 'velxio-kernel-arm64'
|
||||
PI3_INITRAMFS_NAME = 'velxio-initramfs-arm64.cpio.gz'
|
||||
PI3_ROOTFS_NAME = 'velxio-pi-rootfs-arm64.ext4'
|
||||
# Default board if the client doesn't specify one. Kept for clients
|
||||
# that still send the legacy "start_pi" message without a board field.
|
||||
DEFAULT_PI_BOARD = 'raspberry-pi-3'
|
||||
|
||||
|
||||
def _find_free_port() -> int:
|
||||
|
|
@ -69,9 +107,11 @@ EventCallback = Callable[[str, dict], Awaitable[None]]
|
|||
class PiInstance:
|
||||
"""State for one running Pi board."""
|
||||
|
||||
def __init__(self, client_id: str, callback: EventCallback):
|
||||
self.client_id = client_id
|
||||
self.callback = callback
|
||||
def __init__(self, client_id: str, callback: EventCallback,
|
||||
board_type: str = DEFAULT_PI_BOARD):
|
||||
self.client_id = client_id
|
||||
self.callback = callback
|
||||
self.board_type = board_type
|
||||
|
||||
# Runtime state
|
||||
self.process: subprocess.Popen | None = None
|
||||
|
|
@ -105,12 +145,18 @@ class QemuManager:
|
|||
|
||||
# ── Public API ────────────────────────────────────────────────────────────
|
||||
|
||||
def start_instance(self, client_id: str, board_type: str, # noqa: ARG002
|
||||
def start_instance(self, client_id: str, board_type: str,
|
||||
callback: EventCallback) -> None:
|
||||
if client_id in self._instances:
|
||||
logger.warning('start_instance: %s already running', client_id)
|
||||
return
|
||||
inst = PiInstance(client_id, callback)
|
||||
if board_type not in PI_CONFIGS:
|
||||
logger.warning(
|
||||
'start_instance: unknown board %r, falling back to %s',
|
||||
board_type, DEFAULT_PI_BOARD,
|
||||
)
|
||||
board_type = DEFAULT_PI_BOARD
|
||||
inst = PiInstance(client_id, callback, board_type=board_type)
|
||||
self._instances[client_id] = inst
|
||||
asyncio.create_task(self._boot(inst))
|
||||
|
||||
|
|
@ -144,21 +190,29 @@ class QemuManager:
|
|||
# ── Boot sequence ─────────────────────────────────────────────────────────
|
||||
|
||||
async def _boot(self, inst: PiInstance) -> None:
|
||||
# Per-board configuration drives the QEMU command, image-set,
|
||||
# CPU type, RAM, and SMP count. See PI_CONFIGS at the top of
|
||||
# this module.
|
||||
cfg = PI_CONFIGS[inst.board_type]
|
||||
logger.info('[%s] booting %s (cpu=%s mem=%s)',
|
||||
inst.client_id, inst.board_type, cfg['cpu'], cfg['memory'])
|
||||
|
||||
# Resolve boot files via the provider (downloads + verifies on
|
||||
# first call; cache hit on subsequent calls thanks to the
|
||||
# lifespan pre-warm at module load).
|
||||
try:
|
||||
images = await self._get_provider().get(PI3_IMAGE_SET)
|
||||
images = await self._get_provider().get(cfg['image_set'])
|
||||
except BootImageError as exc:
|
||||
logger.error('[pi3] boot-image provisioning failed: %s', exc)
|
||||
logger.error('[%s] boot-image provisioning failed: %s',
|
||||
inst.client_id, exc)
|
||||
await inst.emit('error', {
|
||||
'message': f'Raspberry Pi 3 boot files unavailable: {exc}',
|
||||
'message': f'{inst.board_type} boot files unavailable: {exc}',
|
||||
})
|
||||
self._instances.pop(inst.client_id, None)
|
||||
return
|
||||
kernel_path: Path = images[PI3_KERNEL_NAME]
|
||||
initramfs_path: Path = images[PI3_INITRAMFS_NAME]
|
||||
rootfs_base: Path = images[PI3_ROOTFS_NAME]
|
||||
kernel_path: Path = images[cfg['kernel']]
|
||||
initramfs_path: Path = images[cfg['initramfs']]
|
||||
rootfs_base: Path = images[cfg['rootfs']]
|
||||
|
||||
# Allocate transport endpoints for the two chardevs.
|
||||
#
|
||||
|
|
@ -213,11 +267,11 @@ class QemuManager:
|
|||
# Why no -dtb: virt machine generates its own DTB on the fly
|
||||
# from the runtime device list, so we don't ship one.
|
||||
cmd = [
|
||||
'qemu-system-aarch64',
|
||||
cfg['qemu'],
|
||||
'-M', 'virt',
|
||||
'-cpu', 'cortex-a53',
|
||||
'-smp', '4',
|
||||
'-m', '1G',
|
||||
'-cpu', cfg['cpu'],
|
||||
'-smp', cfg['smp'],
|
||||
'-m', cfg['memory'],
|
||||
'-kernel', str(kernel_path),
|
||||
'-initrd', str(initramfs_path),
|
||||
# Root filesystem via virtio-blk over PCI. virt machine
|
||||
|
|
@ -652,29 +706,33 @@ class QemuManager:
|
|||
|
||||
|
||||
# ── Lifespan pre-warm ────────────────────────────────────────────────────────
|
||||
async def _prewarm_pi3_boot_images() -> None:
|
||||
"""Lifespan hook: download + cache the Pi 3 virt boot files in the
|
||||
async def _prewarm_pi_boot_images() -> None:
|
||||
"""Lifespan hook: download + cache the Pi virt boot files in the
|
||||
background at process start.
|
||||
|
||||
The cache check is cheap when files are already on disk (named
|
||||
docker volume), so this is a no-op for warm containers and a one-
|
||||
time pay-on-first-boot for fresh hosts. Failures are logged but
|
||||
never block startup — a missing licence key or a velxio.dev outage
|
||||
just means the first user request gets an error with a useful
|
||||
message, instead of the whole backend refusing to start.
|
||||
Pre-warms every unique ``image_set`` referenced by PI_CONFIGS
|
||||
exactly once (Pi 3/4/5 share the arm64 set; Pi Zero/1/2 will add
|
||||
an armhf set in Phase 3.3). The cache check is cheap when files
|
||||
are already on disk (named docker volume), so this is a no-op
|
||||
for warm containers. Failures are logged but never block startup.
|
||||
"""
|
||||
try:
|
||||
provider = get_default_provider()
|
||||
except Exception as exc: # noqa: BLE001 - log + continue at startup
|
||||
except Exception as exc: # noqa: BLE001
|
||||
logger.warning(
|
||||
'[pi3] cannot build boot-image provider, skipping pre-warm: %s',
|
||||
'[pi] cannot build boot-image provider, skipping pre-warm: %s',
|
||||
exc,
|
||||
)
|
||||
return
|
||||
asyncio.create_task(provider.warmup(PI3_IMAGE_SET))
|
||||
seen: set[str] = set()
|
||||
for cfg in PI_CONFIGS.values():
|
||||
if cfg['image_set'] in seen:
|
||||
continue
|
||||
seen.add(cfg['image_set'])
|
||||
asyncio.create_task(provider.warmup(cfg['image_set']))
|
||||
|
||||
|
||||
register_lifespan_startup(_prewarm_pi3_boot_images)
|
||||
register_lifespan_startup(_prewarm_pi_boot_images)
|
||||
|
||||
|
||||
qemu_manager = QemuManager()
|
||||
|
|
|
|||
|
|
@ -64,13 +64,15 @@ export class ComponentRegistry {
|
|||
|
||||
const data: ComponentMetadataCollection = await response.json();
|
||||
|
||||
// Inject Raspberry Pi 3 metadata
|
||||
// Inject Raspberry Pi 3 / 4 / 5 metadata. All three share the
|
||||
// same 40-pin GPIO header; the simulator backend picks a
|
||||
// different QEMU CPU model per board (Cortex-A53/A72/A76).
|
||||
data.components.push({
|
||||
id: 'raspberry-pi-3',
|
||||
tagName: 'velxio-raspberry-pi-3',
|
||||
name: 'Raspberry Pi 3',
|
||||
category: 'boards',
|
||||
description: 'Raspberry Pi 3 Model B with 40-pin GPIO. Connects to backend QEMU simulator.',
|
||||
description: 'Raspberry Pi 3 Model B with 40-pin GPIO. QEMU virt + Cortex-A53 backend.',
|
||||
thumbnail:
|
||||
'<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg"><rect width="64" height="64" fill="#E60049" rx="4"/><text x="50%" y="50%" text-anchor="middle" dy=".3em" font-size="10" fill="#FFF">RPi3</text></svg>',
|
||||
properties: [],
|
||||
|
|
@ -78,6 +80,32 @@ export class ComponentRegistry {
|
|||
pinCount: 40,
|
||||
tags: ['raspberry', 'pi', 'rp3', 'board', 'qemu', 'linux'],
|
||||
});
|
||||
data.components.push({
|
||||
id: 'raspberry-pi-4',
|
||||
tagName: 'velxio-raspberry-pi-3', // reuse Pi 3 board art (40-pin layout identical)
|
||||
name: 'Raspberry Pi 4',
|
||||
category: 'boards',
|
||||
description: 'Raspberry Pi 4 Model B with 40-pin GPIO. QEMU virt + Cortex-A72 backend.',
|
||||
thumbnail:
|
||||
'<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg"><rect width="64" height="64" fill="#83B81A" rx="4"/><text x="50%" y="50%" text-anchor="middle" dy=".3em" font-size="10" fill="#FFF">RPi4</text></svg>',
|
||||
properties: [],
|
||||
defaultValues: {},
|
||||
pinCount: 40,
|
||||
tags: ['raspberry', 'pi', 'rp4', 'board', 'qemu', 'linux'],
|
||||
});
|
||||
data.components.push({
|
||||
id: 'raspberry-pi-5',
|
||||
tagName: 'velxio-raspberry-pi-3', // reuse art for now (Phase 3 polish: Pi 5 PCB SVG)
|
||||
name: 'Raspberry Pi 5',
|
||||
category: 'boards',
|
||||
description: 'Raspberry Pi 5 with 40-pin GPIO. QEMU virt + Cortex-A76 backend (no raspi5 machine in QEMU yet).',
|
||||
thumbnail:
|
||||
'<svg width="64" height="64" xmlns="http://www.w3.org/2000/svg"><rect width="64" height="64" fill="#76323F" rx="4"/><text x="50%" y="50%" text-anchor="middle" dy=".3em" font-size="10" fill="#FFF">RPi5</text></svg>',
|
||||
properties: [],
|
||||
defaultValues: {},
|
||||
pinCount: 40,
|
||||
tags: ['raspberry', 'pi', 'rp5', 'board', 'qemu', 'linux'],
|
||||
});
|
||||
|
||||
// Inject SPICE probe instruments — these are Velxio-specific React
|
||||
// components (not wokwi web elements), so they have no auto-generated
|
||||
|
|
|
|||
|
|
@ -127,7 +127,12 @@ function isEsp32Bridge(boardKind: string): boolean {
|
|||
}
|
||||
|
||||
function isPi3Bridge(boardKind: string): boolean {
|
||||
return boardKind === 'raspberry-pi-3';
|
||||
// Pi 3 / 4 / 5 all use the same backend bridge (QEMU virt + virtio-serial).
|
||||
return (
|
||||
boardKind === 'raspberry-pi-3' ||
|
||||
boardKind === 'raspberry-pi-4' ||
|
||||
boardKind === 'raspberry-pi-5'
|
||||
);
|
||||
}
|
||||
|
||||
/** Resolve `(componentId, pinName)` to a `(boardId, pinNumber)` pair. */
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
* Protocol (JSON frames):
|
||||
* Frontend → Backend
|
||||
* { type: 'start_pi', data: { board: 'raspberry-pi-3' } }
|
||||
* { type: 'start_pi', data: { board: 'raspberry-pi-3'|'raspberry-pi-4'|'raspberry-pi-5' } }
|
||||
* { type: 'stop_pi' }
|
||||
* { type: 'serial_input', data: { bytes: number[] } }
|
||||
* { type: 'gpio_in', data: { pin: number, state: 0 | 1 } }
|
||||
|
|
@ -23,6 +23,10 @@ const API_BASE = (): string =>
|
|||
|
||||
export class RaspberryPi3Bridge {
|
||||
readonly boardId: string;
|
||||
/** Pi family member: 'raspberry-pi-3' | 'raspberry-pi-4' | 'raspberry-pi-5'.
|
||||
* The backend uses this to pick the QEMU -cpu / -m. Defaults to Pi 3 for
|
||||
* back-compat with code paths that don't know the kind yet. */
|
||||
readonly boardKind: string;
|
||||
|
||||
// Callbacks wired up by useSimulatorStore
|
||||
onSerialData: ((char: string) => void) | null = null;
|
||||
|
|
@ -35,8 +39,9 @@ export class RaspberryPi3Bridge {
|
|||
private socket: WebSocket | null = null;
|
||||
private _connected = false;
|
||||
|
||||
constructor(boardId: string) {
|
||||
constructor(boardId: string, boardKind: string = 'raspberry-pi-3') {
|
||||
this.boardId = boardId;
|
||||
this.boardKind = boardKind;
|
||||
}
|
||||
|
||||
get connected(): boolean {
|
||||
|
|
@ -57,8 +62,8 @@ export class RaspberryPi3Bridge {
|
|||
socket.onopen = () => {
|
||||
this._connected = true;
|
||||
this.onConnected?.();
|
||||
// Tell the backend to boot the Pi
|
||||
this._send({ type: 'start_pi', data: { board: 'raspberry-pi-3' } });
|
||||
// Tell the backend which Pi family member to boot.
|
||||
this._send({ type: 'start_pi', data: { board: this.boardKind } });
|
||||
};
|
||||
|
||||
socket.onmessage = (event: MessageEvent) => {
|
||||
|
|
|
|||
|
|
@ -954,8 +954,12 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
|
||||
const serialCallback = (ch: string) => appendSerial(id, ch);
|
||||
|
||||
if (boardKind === 'raspberry-pi-3') {
|
||||
const bridge = new RaspberryPi3Bridge(id);
|
||||
if (
|
||||
boardKind === 'raspberry-pi-3' ||
|
||||
boardKind === 'raspberry-pi-4' ||
|
||||
boardKind === 'raspberry-pi-5'
|
||||
) {
|
||||
const bridge = new RaspberryPi3Bridge(id, boardKind);
|
||||
bridge.onSerialData = (ch: string) => {
|
||||
serialCallback(ch);
|
||||
// Cross-board routing now handled by Interconnect (see bind below).
|
||||
|
|
|
|||
|
|
@ -4,7 +4,9 @@ export type BoardKind =
|
|||
| 'arduino-mega'
|
||||
| 'raspberry-pi-pico' // RP2040, browser emulation
|
||||
| 'pi-pico-w' // RP2040 + WiFi, browser emulation (WiFi ignored)
|
||||
| 'raspberry-pi-3' // QEMU ARM64, backend
|
||||
| 'raspberry-pi-3' // QEMU virt + Cortex-A53, backend
|
||||
| 'raspberry-pi-4' // QEMU virt + Cortex-A72, backend
|
||||
| 'raspberry-pi-5' // QEMU virt + Cortex-A76, backend
|
||||
| 'esp32' // Xtensa LX6, QEMU backend
|
||||
| 'esp32-devkit-c-v4' // ESP32 DevKit C V4, QEMU (esp32)
|
||||
| 'esp32-cam' // ESP32-CAM, QEMU (esp32)
|
||||
|
|
@ -71,6 +73,8 @@ export const BOARD_KIND_LABELS: Record<BoardKind, string> = {
|
|||
'raspberry-pi-pico': 'Raspberry Pi Pico',
|
||||
'pi-pico-w': 'Raspberry Pi Pico W',
|
||||
'raspberry-pi-3': 'Raspberry Pi 3B',
|
||||
'raspberry-pi-4': 'Raspberry Pi 4B',
|
||||
'raspberry-pi-5': 'Raspberry Pi 5',
|
||||
esp32: 'ESP32 DevKit V1',
|
||||
'esp32-devkit-c-v4': 'ESP32 DevKit C V4',
|
||||
'esp32-cam': 'ESP32-CAM',
|
||||
|
|
@ -91,6 +95,8 @@ export const BOARD_KIND_FQBN: Record<BoardKind, string | null> = {
|
|||
'raspberry-pi-pico': 'rp2040:rp2040:rpipico',
|
||||
'pi-pico-w': 'rp2040:rp2040:rpipicow',
|
||||
'raspberry-pi-3': null,
|
||||
'raspberry-pi-4': null,
|
||||
'raspberry-pi-5': null,
|
||||
esp32: 'esp32:esp32:esp32',
|
||||
'esp32-devkit-c-v4': 'esp32:esp32:esp32',
|
||||
'esp32-cam': 'esp32:esp32:esp32cam',
|
||||
|
|
|
|||
|
|
@ -144,7 +144,15 @@ function tableFor(boardKind: BoardKind | string): RoleTable | null {
|
|||
if (boardKind === 'raspberry-pi-pico' || boardKind === 'pi-pico-w') return RP2040_DEFAULT;
|
||||
if (boardKind === 'esp32-c3' || (boardKind as string).startsWith('esp32-c3')) return ESP32_C3_DEFAULT;
|
||||
if (boardKind === 'esp32' || (boardKind as string).startsWith('esp32')) return ESP32_DEFAULT;
|
||||
if (boardKind === 'raspberry-pi-3' || (boardKind as string).startsWith('raspberry-pi-3'))
|
||||
// Pi 3/4/5 all share the same 40-pin GPIO header → same BCM table.
|
||||
if (
|
||||
boardKind === 'raspberry-pi-3' ||
|
||||
boardKind === 'raspberry-pi-4' ||
|
||||
boardKind === 'raspberry-pi-5' ||
|
||||
(boardKind as string).startsWith('raspberry-pi-3') ||
|
||||
(boardKind as string).startsWith('raspberry-pi-4') ||
|
||||
(boardKind as string).startsWith('raspberry-pi-5')
|
||||
)
|
||||
return PI3_BCM;
|
||||
return ARDUINO_NANO; // default fallback: treat unknown as arduino-uno-like
|
||||
}
|
||||
|
|
@ -170,8 +178,15 @@ function normalizePinName(boardKind: string, pinName: string): string | null {
|
|||
return null;
|
||||
}
|
||||
|
||||
// Pi3B accepts physical pin numbers (1..40) which map to BCM
|
||||
if (boardKind === 'raspberry-pi-3' || boardKind.startsWith('raspberry-pi-3')) {
|
||||
// Pi 3/4/5 all accept physical pin numbers (1..40) which map to BCM
|
||||
if (
|
||||
boardKind === 'raspberry-pi-3' ||
|
||||
boardKind === 'raspberry-pi-4' ||
|
||||
boardKind === 'raspberry-pi-5' ||
|
||||
boardKind.startsWith('raspberry-pi-3') ||
|
||||
boardKind.startsWith('raspberry-pi-4') ||
|
||||
boardKind.startsWith('raspberry-pi-5')
|
||||
) {
|
||||
const phys = parseInt(trimmed, 10);
|
||||
if (!isNaN(phys)) {
|
||||
const bcm = PI3_PHYSICAL_TO_BCM[phys];
|
||||
|
|
|
|||
|
|
@ -129,12 +129,12 @@ export async function loadExample(
|
|||
// Arduino-style boards (AVR, RP2040, ESP32, …) all need the `.ino`
|
||||
// extension so arduino-cli auto-includes <Arduino.h>. Only the Pi 3B
|
||||
// uses a different toolchain (Python via VFS or g++ for `.cpp`).
|
||||
const filename = eb.boardKind === 'raspberry-pi-3' ? 'main.cpp' : 'sketch.ino';
|
||||
const filename = (eb.boardKind === 'raspberry-pi-3' || eb.boardKind === 'raspberry-pi-4' || eb.boardKind === 'raspberry-pi-5') ? 'main.cpp' : 'sketch.ino';
|
||||
useEditorStore.getState().setActiveGroup(board.activeFileGroupId);
|
||||
useEditorStore.getState().loadFiles([{ name: filename, content: eb.code }]);
|
||||
}
|
||||
|
||||
if (eb.vfsFiles && eb.boardKind === 'raspberry-pi-3') {
|
||||
if (eb.vfsFiles && (eb.boardKind === 'raspberry-pi-3' || eb.boardKind === 'raspberry-pi-4' || eb.boardKind === 'raspberry-pi-5')) {
|
||||
const vfsState = useVfsStore.getState();
|
||||
const tree = vfsState.getTree(boardId);
|
||||
for (const [nodeId, node] of Object.entries(tree)) {
|
||||
|
|
@ -147,7 +147,7 @@ export async function loadExample(
|
|||
|
||||
const firstArduinoIdx = example.boards.findIndex(
|
||||
(eb) =>
|
||||
eb.boardKind !== 'raspberry-pi-3' &&
|
||||
eb.boardKind !== 'raspberry-pi-3' && eb.boardKind !== 'raspberry-pi-4' && eb.boardKind !== 'raspberry-pi-5' &&
|
||||
eb.boardKind !== 'esp32' &&
|
||||
eb.boardKind !== 'esp32-s3' &&
|
||||
eb.boardKind !== 'esp32-c3',
|
||||
|
|
@ -242,7 +242,7 @@ export async function loadExample(
|
|||
// appear blank. (Regression test: load-example-transitions.test.ts.)
|
||||
const editorStore = useEditorStore.getState();
|
||||
editorStore.setActiveGroup(liveBoard.activeFileGroupId);
|
||||
const filename = liveBoard.boardKind === 'raspberry-pi-3' ? 'main.cpp' : 'sketch.ino';
|
||||
const filename = (liveBoard.boardKind === 'raspberry-pi-3' || liveBoard.boardKind === 'raspberry-pi-4' || liveBoard.boardKind === 'raspberry-pi-5') ? 'main.cpp' : 'sketch.ino';
|
||||
editorStore.loadFiles([{ name: filename, content: example.code }]);
|
||||
} else {
|
||||
// Truly board-less: write the placeholder code to whatever the editor
|
||||
|
|
|
|||
Loading…
Reference in New Issue