feat(pi-family): quietBoot — hide the shared rootfs' branded boot chatter
The generic arm64 image prints another product's banner/motd/login line during boot, before guestSetup can re-brand the guest. Boards with quietBoot show a neutral '[Velxio] Booting <label> (Linux guest)...' progress line (dots every 4 s) while boot detection and the prompt-gated upload still run underneath; the shell is revealed (already re-branded) right before the auto-run command, so the user's first visible output is their own program.
This commit is contained in:
parent
a766d0cde3
commit
330b9ed1b2
|
|
@ -71,6 +71,12 @@ export interface ProBoardDef {
|
|||
* this line executed, so a single click on Run boots, uploads and
|
||||
* starts the user's script (piFamily boards only). */
|
||||
autoRun?: string;
|
||||
/** Suppress the guest's boot chatter in the terminal (piFamily boards
|
||||
* only): the shared rootfs prints another product's banner/motd during
|
||||
* boot, before guestSetup can re-brand it. With quietBoot the terminal
|
||||
* shows a neutral Velxio boot-progress line instead and reveals the
|
||||
* shell right before the auto-run command executes. */
|
||||
quietBoot?: boolean;
|
||||
/** Canvas renderer. Receives the placed board's props; return a React node.
|
||||
* When omitted, the canvas renders `<tag id=... style=absolute@x,y>`. */
|
||||
render?: (props: { id: string; x: number; y: number; running: boolean }) => React.ReactNode;
|
||||
|
|
|
|||
|
|
@ -75,6 +75,15 @@ export class RaspberryPi3Bridge {
|
|||
private socket: WebSocket | null = null;
|
||||
private _connected = false;
|
||||
private _booted = false;
|
||||
/** quietBoot (ProBoardDef): while true, guest serial output is withheld
|
||||
* from onSerialData (boot detection still runs) and a neutral Velxio
|
||||
* progress line + dots show instead. The run path calls setQuiet(false)
|
||||
* to reveal the shell right before the script starts. */
|
||||
quietBootDefault = false;
|
||||
/** Board label for the quiet-boot progress line. */
|
||||
quietBootLabel = '';
|
||||
private _quiet = false;
|
||||
private _quietTimer: ReturnType<typeof setInterval> | null = null;
|
||||
/** Rolling, escape-stripped tail of recent guest output, used to detect the
|
||||
* boot-complete marker and shell prompts for flow-controlled sends. */
|
||||
private _serialTail = '';
|
||||
|
|
@ -110,6 +119,12 @@ export class RaspberryPi3Bridge {
|
|||
this.onConnected?.();
|
||||
// Tell the backend which Pi family member to boot.
|
||||
this._send({ type: 'start_pi', data: { board: this.boardKind } });
|
||||
if (this.quietBootDefault) {
|
||||
this._quiet = true;
|
||||
const label = this.quietBootLabel || this.boardKind;
|
||||
this._emitLocal(`[Velxio] Booting ${label} (Linux guest)`);
|
||||
this._quietTimer = setInterval(() => this._emitLocal('.', false), 4000);
|
||||
}
|
||||
};
|
||||
|
||||
socket.onmessage = (event: MessageEvent) => {
|
||||
|
|
@ -123,7 +138,9 @@ export class RaspberryPi3Bridge {
|
|||
switch (msg.type) {
|
||||
case 'serial_output': {
|
||||
const text = (msg.data.data as string) ?? '';
|
||||
if (this.onSerialData) {
|
||||
// quietBoot: keep observing (boot marker + prompt waiters drive
|
||||
// guestSetup and the upload) but withhold the branded chatter.
|
||||
if (!this._quiet && this.onSerialData) {
|
||||
for (const ch of text) this.onSerialData(ch);
|
||||
}
|
||||
this._observeSerial(text);
|
||||
|
|
@ -183,11 +200,37 @@ export class RaspberryPi3Bridge {
|
|||
private _resetBootState(): void {
|
||||
this._booted = false;
|
||||
this._serialTail = '';
|
||||
this._quiet = false;
|
||||
if (this._quietTimer) {
|
||||
clearInterval(this._quietTimer);
|
||||
this._quietTimer = null;
|
||||
}
|
||||
const waiters = this._promptWaiters;
|
||||
this._promptWaiters = [];
|
||||
for (const w of waiters) w();
|
||||
}
|
||||
|
||||
/** Feed locally-generated status text to the serial consumers. */
|
||||
private _emitLocal(text: string, newline = true): void {
|
||||
if (!this.onSerialData) return;
|
||||
const chunk = newline ? `\r\n${text}` : text;
|
||||
for (const ch of chunk) this.onSerialData(ch);
|
||||
}
|
||||
|
||||
/** Reveal (or re-hide) the guest's serial stream. Turning quiet off ends
|
||||
* the progress dots and prints a ready line. */
|
||||
setQuiet(on: boolean): void {
|
||||
if (this._quiet === on) return;
|
||||
this._quiet = on;
|
||||
if (!on) {
|
||||
if (this._quietTimer) {
|
||||
clearInterval(this._quietTimer);
|
||||
this._quietTimer = null;
|
||||
}
|
||||
this._emitLocal(' ready.\r\n');
|
||||
}
|
||||
}
|
||||
|
||||
/** Send a byte to the Pi's ttyAMA0 (user serial) */
|
||||
sendSerialByte(byte: number): void {
|
||||
this._send({ type: 'serial_input', data: { bytes: [byte] } });
|
||||
|
|
|
|||
|
|
@ -777,8 +777,14 @@ export async function piSyncAndRunScript(boardId: string, boardKind: string): Pr
|
|||
.getState()
|
||||
.getGroupFiles(groupId)
|
||||
.map((f) => ({ path: `${home}/${f.name}`, content: f.content }));
|
||||
const { uploadFilesToPi } = await import('../utils/piUpload');
|
||||
await uploadFilesToPi(bridge, files);
|
||||
try {
|
||||
const { uploadFilesToPi } = await import('../utils/piUpload');
|
||||
await uploadFilesToPi(bridge, files);
|
||||
} finally {
|
||||
// Reveal the shell (ends quietBoot) right before the script starts, so
|
||||
// the user's first visible output is their own program.
|
||||
bridge.setQuiet(false);
|
||||
}
|
||||
const cmd = proDef?.autoRun ?? `python3 ${home}/script.py`;
|
||||
bridge.sendSerialText(cmd.endsWith('\n') ? cmd : cmd + '\n');
|
||||
}
|
||||
|
|
@ -1259,6 +1265,11 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
|||
|
||||
if (isPiBoardKind(boardKind)) {
|
||||
const bridge = new RaspberryPi3Bridge(id, boardKind);
|
||||
const piDef = getProBoard(boardKind);
|
||||
if (piDef?.quietBoot) {
|
||||
bridge.quietBootDefault = true;
|
||||
bridge.quietBootLabel = piDef.label;
|
||||
}
|
||||
bridge.onSerialData = (ch: string) => {
|
||||
serialCallback(ch);
|
||||
// Cross-board routing now handled by Interconnect (see bind below).
|
||||
|
|
|
|||
Loading…
Reference in New Issue