Automatically reconnect single ADB head unit
This commit is contained in:
+34
-7
@@ -26,7 +26,7 @@ export type AdbExecutor = (
|
||||
options: ExecuteOptions
|
||||
) => Promise<{ stdout: string; stderr: string }>;
|
||||
|
||||
export type AdbConnectionState = "disabled" | "unavailable" | "offline" | "unauthorized";
|
||||
export type AdbConnectionState = "disabled" | "unavailable" | "offline" | "unauthorized" | "ambiguous";
|
||||
|
||||
export type AdbDeviceSnapshot = {
|
||||
state: "connected";
|
||||
@@ -111,15 +111,42 @@ export async function collectAdbStatus(
|
||||
if (!config.enabled) {
|
||||
return { available: false, state: "disabled", reason: "ADB integration is not configured" };
|
||||
}
|
||||
if (!config.serial) {
|
||||
return { available: false, state: "unavailable", reason: "ADB is enabled without a configured device serial" };
|
||||
|
||||
const options = { timeout: config.timeoutMs, maxBuffer: MAX_ADB_OUTPUT_BYTES };
|
||||
let serial = config.serial;
|
||||
if (!serial) {
|
||||
try {
|
||||
const output = (await execute(config.executable, ["devices"], options)).stdout;
|
||||
const devices = output
|
||||
.split(/\r?\n/)
|
||||
.slice(1)
|
||||
.map((line) => line.trim().split(/\s+/, 3))
|
||||
.filter((parts): parts is [string, string, ...string[]] => Boolean(parts[0] && parts[1]));
|
||||
if (devices.length === 0) {
|
||||
return { available: false, state: "offline", reason: "No ADB infotainment device is connected" };
|
||||
}
|
||||
if (devices.length > 1) {
|
||||
return {
|
||||
available: false,
|
||||
state: "ambiguous",
|
||||
reason: "Multiple ADB devices are connected; configure ADB_SERIAL to select the infotainment unit"
|
||||
};
|
||||
}
|
||||
const [discoveredSerial, discoveredState] = devices[0]!;
|
||||
if (discoveredState === "unauthorized") {
|
||||
return { available: false, state: "unauthorized", reason: "Authorize this Pi on the infotainment screen" };
|
||||
}
|
||||
if (discoveredState !== "device") {
|
||||
return { available: false, state: "offline", reason: "The discovered infotainment device is offline" };
|
||||
}
|
||||
serial = discoveredSerial;
|
||||
} catch (error) {
|
||||
return connectionFailure(error);
|
||||
}
|
||||
}
|
||||
|
||||
const run = async (arguments_: readonly string[]) =>
|
||||
execute(config.executable, ["-s", config.serial!, ...arguments_], {
|
||||
timeout: config.timeoutMs,
|
||||
maxBuffer: MAX_ADB_OUTPUT_BYTES
|
||||
});
|
||||
execute(config.executable, ["-s", serial, ...arguments_], options);
|
||||
|
||||
try {
|
||||
const state = (await run(["get-state"])).stdout.trim().toLowerCase();
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
} from "./security.js";
|
||||
import { collectSystemStatus } from "./status.js";
|
||||
import { readUpdateStatus, triggerSystemUpdate } from "./update.js";
|
||||
import { collectAdbStatus } from "./adb.js";
|
||||
import "./types.js";
|
||||
|
||||
const usernameSchema = z
|
||||
@@ -98,6 +99,7 @@ export async function buildApp(config: AppConfig): Promise<FastifyInstance> {
|
||||
});
|
||||
const database = openDatabase(config.databasePath);
|
||||
const jobRunner = new JobRunner(database, undefined, config.adb);
|
||||
if (config.adb.enabled) void collectAdbStatus(config.adb);
|
||||
|
||||
app.decorate("database", database);
|
||||
app.decorate("jobRunner", jobRunner);
|
||||
|
||||
@@ -50,10 +50,6 @@ export function loadConfig(environment: NodeJS.ProcessEnv = process.env): AppCon
|
||||
}
|
||||
|
||||
const adbSerial = parsed.ADB_SERIAL.trim() || null;
|
||||
if (parsed.ADB_ENABLED && !adbSerial) {
|
||||
throw new Error("ADB_SERIAL is required when ADB_ENABLED=true");
|
||||
}
|
||||
|
||||
return {
|
||||
nodeEnv: parsed.NODE_ENV,
|
||||
host: parsed.HOST,
|
||||
|
||||
Reference in New Issue
Block a user