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
|
* this line executed, so a single click on Run boots, uploads and
|
||||||
* starts the user's script (piFamily boards only). */
|
* starts the user's script (piFamily boards only). */
|
||||||
autoRun?: string;
|
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.
|
/** Canvas renderer. Receives the placed board's props; return a React node.
|
||||||
* When omitted, the canvas renders `<tag id=... style=absolute@x,y>`. */
|
* When omitted, the canvas renders `<tag id=... style=absolute@x,y>`. */
|
||||||
render?: (props: { id: string; x: number; y: number; running: boolean }) => React.ReactNode;
|
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 socket: WebSocket | null = null;
|
||||||
private _connected = false;
|
private _connected = false;
|
||||||
private _booted = 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
|
/** Rolling, escape-stripped tail of recent guest output, used to detect the
|
||||||
* boot-complete marker and shell prompts for flow-controlled sends. */
|
* boot-complete marker and shell prompts for flow-controlled sends. */
|
||||||
private _serialTail = '';
|
private _serialTail = '';
|
||||||
|
|
@ -110,6 +119,12 @@ export class RaspberryPi3Bridge {
|
||||||
this.onConnected?.();
|
this.onConnected?.();
|
||||||
// Tell the backend which Pi family member to boot.
|
// Tell the backend which Pi family member to boot.
|
||||||
this._send({ type: 'start_pi', data: { board: this.boardKind } });
|
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) => {
|
socket.onmessage = (event: MessageEvent) => {
|
||||||
|
|
@ -123,7 +138,9 @@ export class RaspberryPi3Bridge {
|
||||||
switch (msg.type) {
|
switch (msg.type) {
|
||||||
case 'serial_output': {
|
case 'serial_output': {
|
||||||
const text = (msg.data.data as string) ?? '';
|
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);
|
for (const ch of text) this.onSerialData(ch);
|
||||||
}
|
}
|
||||||
this._observeSerial(text);
|
this._observeSerial(text);
|
||||||
|
|
@ -183,11 +200,37 @@ export class RaspberryPi3Bridge {
|
||||||
private _resetBootState(): void {
|
private _resetBootState(): void {
|
||||||
this._booted = false;
|
this._booted = false;
|
||||||
this._serialTail = '';
|
this._serialTail = '';
|
||||||
|
this._quiet = false;
|
||||||
|
if (this._quietTimer) {
|
||||||
|
clearInterval(this._quietTimer);
|
||||||
|
this._quietTimer = null;
|
||||||
|
}
|
||||||
const waiters = this._promptWaiters;
|
const waiters = this._promptWaiters;
|
||||||
this._promptWaiters = [];
|
this._promptWaiters = [];
|
||||||
for (const w of waiters) w();
|
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) */
|
/** Send a byte to the Pi's ttyAMA0 (user serial) */
|
||||||
sendSerialByte(byte: number): void {
|
sendSerialByte(byte: number): void {
|
||||||
this._send({ type: 'serial_input', data: { bytes: [byte] } });
|
this._send({ type: 'serial_input', data: { bytes: [byte] } });
|
||||||
|
|
|
||||||
|
|
@ -777,8 +777,14 @@ export async function piSyncAndRunScript(boardId: string, boardKind: string): Pr
|
||||||
.getState()
|
.getState()
|
||||||
.getGroupFiles(groupId)
|
.getGroupFiles(groupId)
|
||||||
.map((f) => ({ path: `${home}/${f.name}`, content: f.content }));
|
.map((f) => ({ path: `${home}/${f.name}`, content: f.content }));
|
||||||
const { uploadFilesToPi } = await import('../utils/piUpload');
|
try {
|
||||||
await uploadFilesToPi(bridge, files);
|
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`;
|
const cmd = proDef?.autoRun ?? `python3 ${home}/script.py`;
|
||||||
bridge.sendSerialText(cmd.endsWith('\n') ? cmd : cmd + '\n');
|
bridge.sendSerialText(cmd.endsWith('\n') ? cmd : cmd + '\n');
|
||||||
}
|
}
|
||||||
|
|
@ -1259,6 +1265,11 @@ export const useSimulatorStore = create<SimulatorState>((set, get) => {
|
||||||
|
|
||||||
if (isPiBoardKind(boardKind)) {
|
if (isPiBoardKind(boardKind)) {
|
||||||
const bridge = new RaspberryPi3Bridge(id, 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) => {
|
bridge.onSerialData = (ch: string) => {
|
||||||
serialCallback(ch);
|
serialCallback(ch);
|
||||||
// Cross-board routing now handled by Interconnect (see bind below).
|
// Cross-board routing now handled by Interconnect (see bind below).
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue