@kvell007/embed-labs-local-bridge 0.1.0-alpha.0 → 0.1.0-alpha.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/debug.js +3 -3
- package/dist/adapters/debug.js.map +1 -1
- package/dist/adapters/logo.d.ts +2 -0
- package/dist/adapters/logo.js +108 -0
- package/dist/adapters/logo.js.map +1 -0
- package/dist/adapters/taishanpi-live.d.ts +163 -0
- package/dist/adapters/taishanpi-live.js +349 -0
- package/dist/adapters/taishanpi-live.js.map +1 -0
- package/dist/hardware.d.ts +1 -0
- package/dist/hardware.js +106 -4
- package/dist/hardware.js.map +1 -1
- package/dist/process.js +59 -8
- package/dist/process.js.map +1 -1
- package/dist/tools.js +397 -1
- package/dist/tools.js.map +1 -1
- package/package.json +2 -2
package/dist/adapters/debug.js
CHANGED
|
@@ -54,8 +54,8 @@ async function scanDebugTool(spec, observedAt) {
|
|
|
54
54
|
available: resolvedPath !== undefined,
|
|
55
55
|
resolved_path: resolvedPath,
|
|
56
56
|
summary: resolvedPath
|
|
57
|
-
? `Found ${spec.command} on PATH.`
|
|
58
|
-
: `${spec.command} was not found on PATH.`
|
|
57
|
+
? `Found ${spec.command} on PATH or an Embed Labs local toolchain path.`
|
|
58
|
+
: `${spec.command} was not found on PATH or an Embed Labs local toolchain path.`
|
|
59
59
|
});
|
|
60
60
|
if (!resolvedPath) {
|
|
61
61
|
return {
|
|
@@ -64,7 +64,7 @@ async function scanDebugTool(spec, observedAt) {
|
|
|
64
64
|
command: spec.command,
|
|
65
65
|
available: false,
|
|
66
66
|
evidence,
|
|
67
|
-
summary_for_user: `${spec.display_name} is not available on PATH.`
|
|
67
|
+
summary_for_user: `${spec.display_name} is not available on PATH or an Embed Labs local toolchain path.`
|
|
68
68
|
};
|
|
69
69
|
}
|
|
70
70
|
const versionResult = await runCommand(resolvedPath, spec.version_args, VERSION_TIMEOUT_MS);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/adapters/debug.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAStE,MAAM,kBAAkB,GAAG,IAAI,CAAC;AAChC,MAAM,WAAW,GAAoB;IACnC;QACE,OAAO,EAAE,SAAS;QAClB,YAAY,EAAE,SAAS;QACvB,OAAO,EAAE,SAAS;QAClB,YAAY,EAAE,CAAC,WAAW,CAAC;KAC5B;IACD;QACE,OAAO,EAAE,UAAU;QACnB,YAAY,EAAE,UAAU;QACxB,OAAO,EAAE,UAAU;QACnB,YAAY,EAAE,CAAC,WAAW,CAAC;KAC5B;IACD;QACE,OAAO,EAAE,OAAO;QAChB,YAAY,EAAE,OAAO;QACrB,OAAO,EAAE,OAAO;QAChB,YAAY,EAAE,CAAC,WAAW,CAAC;KAC5B;IACD;QACE,OAAO,EAAE,iBAAiB;QAC1B,YAAY,EAAE,yBAAyB;QACvC,OAAO,EAAE,UAAU;QACnB,YAAY,EAAE,CAAC,IAAI,CAAC;KACrB;IACD;QACE,OAAO,EAAE,kBAAkB;QAC3B,YAAY,EAAE,0BAA0B;QACxC,OAAO,EAAE,qBAAqB;QAC9B,YAAY,EAAE,CAAC,IAAI,CAAC;KACrB;CACF,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,cAAc;IAClC,MAAM,UAAU,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IAC5F,MAAM,cAAc,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC;IACrE,OAAO;QACL,KAAK;QACL,WAAW,EAAE,UAAU;QACvB,gBAAgB,EAAE,6BAA6B,cAAc,IAAI,KAAK,CAAC,MAAM,uEAAuE;KACrJ,CAAC;AACJ,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,IAAmB,EAAE,UAAkB;IAClE,MAAM,QAAQ,GAAwB,EAAE,CAAC;IACzC,MAAM,YAAY,GAAG,MAAM,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAExD,QAAQ,CAAC,IAAI,CAAC;QACZ,WAAW,EAAE,UAAU,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,CAAC;QACzD,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,WAAW,EAAE,UAAU;QACvB,SAAS,EAAE,YAAY,KAAK,SAAS;QACrC,aAAa,EAAE,YAAY;QAC3B,OAAO,EAAE,YAAY;YACnB,CAAC,CAAC,SAAS,IAAI,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"debug.js","sourceRoot":"","sources":["../../src/adapters/debug.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { createReadStream } from "node:fs";
|
|
3
|
+
import { copyFile, mkdir, mkdtemp, stat, writeFile } from "node:fs/promises";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { basename, extname, join, parse, resolve } from "node:path";
|
|
6
|
+
export async function prepareBootLogoModification(request) {
|
|
7
|
+
const observedAt = new Date().toISOString();
|
|
8
|
+
const logoPath = resolve(request.logo_path);
|
|
9
|
+
const logoStat = await requireRegularFile(logoPath, "logo_path");
|
|
10
|
+
const kernelLogoPath = request.kernel_logo_path ? resolve(request.kernel_logo_path) : undefined;
|
|
11
|
+
const kernelLogoStat = kernelLogoPath ? await requireRegularFile(kernelLogoPath, "kernel_logo_path") : undefined;
|
|
12
|
+
const inputImagePath = request.image_path ? resolve(request.image_path) : undefined;
|
|
13
|
+
const inputImageStat = inputImagePath ? await requireRegularFile(inputImagePath, "image_path") : undefined;
|
|
14
|
+
const outputDir = request.output_dir
|
|
15
|
+
? resolve(request.output_dir)
|
|
16
|
+
: await mkdtemp(join(tmpdir(), "embed-boot-logo-"));
|
|
17
|
+
await mkdir(outputDir, { recursive: true });
|
|
18
|
+
const unique = `${Date.now()}-${randomUUID().slice(0, 8)}`;
|
|
19
|
+
const sourceName = inputImagePath ? basename(inputImagePath) : "boot-logo-manifest.json";
|
|
20
|
+
const requestedStem = request.output_name ? parse(request.output_name).name : parse(sourceName).name;
|
|
21
|
+
const safeStem = safeFileStem(requestedStem || "boot-logo");
|
|
22
|
+
const imageExtension = inputImagePath ? extname(inputImagePath) || ".img" : undefined;
|
|
23
|
+
const modifiedImagePath = inputImagePath ? join(outputDir, `${safeStem}-${unique}${imageExtension}`) : undefined;
|
|
24
|
+
const manifestPath = join(outputDir, `${safeStem}-${unique}.boot-logo.json`);
|
|
25
|
+
const logoSha256 = await sha256(logoPath);
|
|
26
|
+
const kernelLogoSha256 = kernelLogoPath ? await sha256(kernelLogoPath) : undefined;
|
|
27
|
+
const inputImageSha256 = inputImagePath ? await sha256(inputImagePath) : undefined;
|
|
28
|
+
if (inputImagePath && modifiedImagePath) {
|
|
29
|
+
await copyFile(inputImagePath, modifiedImagePath);
|
|
30
|
+
}
|
|
31
|
+
const manifest = {
|
|
32
|
+
schema_version: "0.1",
|
|
33
|
+
kind: "boot-logo-modification",
|
|
34
|
+
mode: "local_manifest_mvp",
|
|
35
|
+
replacement_applied: false,
|
|
36
|
+
board_id: request.board_id,
|
|
37
|
+
variant_id: request.variant_id,
|
|
38
|
+
input_image_path: inputImagePath,
|
|
39
|
+
modified_image_path: modifiedImagePath,
|
|
40
|
+
logo_path: logoPath,
|
|
41
|
+
kernel_logo_path: kernelLogoPath,
|
|
42
|
+
rotate: request.rotate,
|
|
43
|
+
scale: request.scale,
|
|
44
|
+
input_image_sha256: inputImageSha256,
|
|
45
|
+
input_image_size_bytes: inputImageStat?.size,
|
|
46
|
+
logo_sha256: logoSha256,
|
|
47
|
+
logo_size_bytes: logoStat.size,
|
|
48
|
+
kernel_logo_sha256: kernelLogoSha256,
|
|
49
|
+
kernel_logo_size_bytes: kernelLogoStat?.size,
|
|
50
|
+
free_for_registered_users: true,
|
|
51
|
+
requires_server_workspace: false,
|
|
52
|
+
observed_at: observedAt,
|
|
53
|
+
notes: [
|
|
54
|
+
"This local free operation validates inputs and creates an isolated artifact bundle for boot-logo replacement.",
|
|
55
|
+
"It does not allocate a server workspace and does not flash a board.",
|
|
56
|
+
"Board-profile repack/flash workers must consume this manifest before writing a boot partition."
|
|
57
|
+
]
|
|
58
|
+
};
|
|
59
|
+
await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`, "utf8");
|
|
60
|
+
return {
|
|
61
|
+
board_id: request.board_id,
|
|
62
|
+
variant_id: request.variant_id,
|
|
63
|
+
modified_image_path: modifiedImagePath,
|
|
64
|
+
manifest_path: manifestPath,
|
|
65
|
+
input_image_path: inputImagePath,
|
|
66
|
+
logo_path: logoPath,
|
|
67
|
+
kernel_logo_path: kernelLogoPath,
|
|
68
|
+
output_dir: outputDir,
|
|
69
|
+
input_image_sha256: inputImageSha256,
|
|
70
|
+
logo_sha256: logoSha256,
|
|
71
|
+
kernel_logo_sha256: kernelLogoSha256,
|
|
72
|
+
bytes: inputImageStat?.size,
|
|
73
|
+
destructive: false,
|
|
74
|
+
approval_required: false,
|
|
75
|
+
requires_server_workspace: false,
|
|
76
|
+
free_for_registered_users: true,
|
|
77
|
+
observed_at: observedAt,
|
|
78
|
+
summary_for_user: inputImagePath
|
|
79
|
+
? `Prepared an isolated boot-logo replacement bundle for ${request.board_id}; copied the input image and wrote a manifest without allocating server resources.`
|
|
80
|
+
: `Prepared a boot-logo replacement manifest for ${request.board_id} without allocating server resources.`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function requireRegularFile(path, fieldName) {
|
|
84
|
+
let fileStat;
|
|
85
|
+
try {
|
|
86
|
+
fileStat = await stat(path);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
throw new Error(`${fieldName} does not exist: ${path}`);
|
|
90
|
+
}
|
|
91
|
+
if (!fileStat.isFile()) {
|
|
92
|
+
throw new Error(`${fieldName} must point to a regular file: ${path}`);
|
|
93
|
+
}
|
|
94
|
+
return { size: fileStat.size };
|
|
95
|
+
}
|
|
96
|
+
function safeFileStem(value) {
|
|
97
|
+
const cleaned = value.trim().replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
98
|
+
return cleaned.slice(0, 80) || "boot-logo";
|
|
99
|
+
}
|
|
100
|
+
async function sha256(path) {
|
|
101
|
+
const hash = createHash("sha256");
|
|
102
|
+
const stream = createReadStream(path);
|
|
103
|
+
for await (const chunk of stream) {
|
|
104
|
+
hash.update(chunk);
|
|
105
|
+
}
|
|
106
|
+
return hash.digest("hex");
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=logo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logo.js","sourceRoot":"","sources":["../../src/adapters/logo.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
export interface TaishanPiLiveOptions {
|
|
2
|
+
host?: string;
|
|
3
|
+
user?: string;
|
|
4
|
+
timeout_seconds?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface TaishanPiWifiScanOptions extends TaishanPiLiveOptions {
|
|
7
|
+
interface?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TaishanPiBluetoothScanOptions extends TaishanPiLiveOptions {
|
|
10
|
+
duration_seconds?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface TaishanPiQmlRuntimeOptions extends TaishanPiLiveOptions {
|
|
13
|
+
port?: number;
|
|
14
|
+
force_restart?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export declare function scanTaishanPiWifiNetworks(options?: TaishanPiWifiScanOptions): Promise<{
|
|
17
|
+
board_id: string;
|
|
18
|
+
host: string;
|
|
19
|
+
user: string;
|
|
20
|
+
interface: string | undefined;
|
|
21
|
+
ok: boolean;
|
|
22
|
+
networks: {
|
|
23
|
+
bssid?: string;
|
|
24
|
+
ssid?: string;
|
|
25
|
+
signal_dbm?: number;
|
|
26
|
+
frequency_mhz?: number;
|
|
27
|
+
channel?: number;
|
|
28
|
+
}[];
|
|
29
|
+
network_count: number;
|
|
30
|
+
exit_code: number | null;
|
|
31
|
+
timed_out: boolean;
|
|
32
|
+
output_tail: string[];
|
|
33
|
+
error: {
|
|
34
|
+
code: string;
|
|
35
|
+
message: string;
|
|
36
|
+
} | undefined;
|
|
37
|
+
observed_at: string;
|
|
38
|
+
summary_for_user: string;
|
|
39
|
+
}>;
|
|
40
|
+
export declare function scanTaishanPiBluetoothDevices(options?: TaishanPiBluetoothScanOptions): Promise<{
|
|
41
|
+
board_id: string;
|
|
42
|
+
host: string;
|
|
43
|
+
user: string;
|
|
44
|
+
ok: boolean;
|
|
45
|
+
bringup_attempted: boolean;
|
|
46
|
+
adapter_info: string;
|
|
47
|
+
raw_scan_results: string;
|
|
48
|
+
devices: {
|
|
49
|
+
address: string;
|
|
50
|
+
name?: string;
|
|
51
|
+
}[];
|
|
52
|
+
device_count: number;
|
|
53
|
+
duration_seconds: number;
|
|
54
|
+
exit_code: number | null;
|
|
55
|
+
timed_out: boolean;
|
|
56
|
+
output_tail: string[];
|
|
57
|
+
error: {
|
|
58
|
+
code: string;
|
|
59
|
+
message: string;
|
|
60
|
+
} | undefined;
|
|
61
|
+
observed_at: string;
|
|
62
|
+
summary_for_user: string;
|
|
63
|
+
}>;
|
|
64
|
+
export declare function readTaishanPiCpuFrequency(options?: TaishanPiLiveOptions): Promise<{
|
|
65
|
+
board_id: string;
|
|
66
|
+
host: string;
|
|
67
|
+
user: string;
|
|
68
|
+
ok: boolean;
|
|
69
|
+
frequencies: {
|
|
70
|
+
policy: number;
|
|
71
|
+
khz: number;
|
|
72
|
+
mhz: number;
|
|
73
|
+
}[];
|
|
74
|
+
exit_code: number | null;
|
|
75
|
+
timed_out: boolean;
|
|
76
|
+
output_tail: string[];
|
|
77
|
+
observed_at: string;
|
|
78
|
+
summary_for_user: string;
|
|
79
|
+
}>;
|
|
80
|
+
export declare function readTaishanPiTemperature(options?: TaishanPiLiveOptions): Promise<{
|
|
81
|
+
board_id: string;
|
|
82
|
+
host: string;
|
|
83
|
+
user: string;
|
|
84
|
+
ok: boolean;
|
|
85
|
+
zones: {
|
|
86
|
+
zone: string;
|
|
87
|
+
milli_celsius: number;
|
|
88
|
+
celsius: number;
|
|
89
|
+
}[];
|
|
90
|
+
exit_code: number | null;
|
|
91
|
+
timed_out: boolean;
|
|
92
|
+
output_tail: string[];
|
|
93
|
+
observed_at: string;
|
|
94
|
+
summary_for_user: string;
|
|
95
|
+
}>;
|
|
96
|
+
export declare function getTaishanPiQmlRuntimeStatus(options?: TaishanPiQmlRuntimeOptions): Promise<{
|
|
97
|
+
board_id: string;
|
|
98
|
+
host: string;
|
|
99
|
+
user: string;
|
|
100
|
+
port: number;
|
|
101
|
+
ok: boolean;
|
|
102
|
+
reachable: boolean;
|
|
103
|
+
runner: string;
|
|
104
|
+
runner_executable: boolean;
|
|
105
|
+
log_path: string;
|
|
106
|
+
tcp: import("@embed-labs/protocol").TcpProbeEvidence;
|
|
107
|
+
ssh: {
|
|
108
|
+
ok: boolean;
|
|
109
|
+
exit_code: number | null;
|
|
110
|
+
timed_out: boolean;
|
|
111
|
+
output_tail: string[];
|
|
112
|
+
};
|
|
113
|
+
observed_at: string;
|
|
114
|
+
summary_for_user: string;
|
|
115
|
+
}>;
|
|
116
|
+
export declare function startTaishanPiQmlRuntime(options?: TaishanPiQmlRuntimeOptions): Promise<{
|
|
117
|
+
started: boolean;
|
|
118
|
+
observed_at: string;
|
|
119
|
+
summary_for_user: string;
|
|
120
|
+
board_id: string;
|
|
121
|
+
host: string;
|
|
122
|
+
user: string;
|
|
123
|
+
port: number;
|
|
124
|
+
ok: boolean;
|
|
125
|
+
reachable: boolean;
|
|
126
|
+
runner: string;
|
|
127
|
+
runner_executable: boolean;
|
|
128
|
+
log_path: string;
|
|
129
|
+
tcp: import("@embed-labs/protocol").TcpProbeEvidence;
|
|
130
|
+
ssh: {
|
|
131
|
+
ok: boolean;
|
|
132
|
+
exit_code: number | null;
|
|
133
|
+
timed_out: boolean;
|
|
134
|
+
output_tail: string[];
|
|
135
|
+
};
|
|
136
|
+
} | {
|
|
137
|
+
started: boolean;
|
|
138
|
+
start_exit_code: number | null;
|
|
139
|
+
start_timed_out: boolean;
|
|
140
|
+
start_output_tail: string[];
|
|
141
|
+
error: {
|
|
142
|
+
code: string;
|
|
143
|
+
message: string;
|
|
144
|
+
} | undefined;
|
|
145
|
+
observed_at: string;
|
|
146
|
+
summary_for_user: string;
|
|
147
|
+
board_id: string;
|
|
148
|
+
host: string;
|
|
149
|
+
user: string;
|
|
150
|
+
port: number;
|
|
151
|
+
ok: boolean;
|
|
152
|
+
reachable: boolean;
|
|
153
|
+
runner: string;
|
|
154
|
+
runner_executable: boolean;
|
|
155
|
+
log_path: string;
|
|
156
|
+
tcp: import("@embed-labs/protocol").TcpProbeEvidence;
|
|
157
|
+
ssh: {
|
|
158
|
+
ok: boolean;
|
|
159
|
+
exit_code: number | null;
|
|
160
|
+
timed_out: boolean;
|
|
161
|
+
output_tail: string[];
|
|
162
|
+
};
|
|
163
|
+
}>;
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
import { probeTcpWithEvidence } from "./network.js";
|
|
2
|
+
import { commandExists, runCommand, tailLines } from "../process.js";
|
|
3
|
+
const DEFAULT_HOST = "198.19.77.2";
|
|
4
|
+
const DEFAULT_USER = "root";
|
|
5
|
+
const DEFAULT_TIMEOUT_SECONDS = 8;
|
|
6
|
+
const DEFAULT_QML_PORT = 18130;
|
|
7
|
+
const QML_RUNNER = "/userdata/embed-qml-board-runtime/run-board-runtime.sh";
|
|
8
|
+
const QML_LOG = "/tmp/embed-qml-board-runtime.log";
|
|
9
|
+
export async function scanTaishanPiWifiNetworks(options = {}) {
|
|
10
|
+
const observedAt = new Date().toISOString();
|
|
11
|
+
const host = liveHost(options.host);
|
|
12
|
+
const user = liveUser(options.user);
|
|
13
|
+
const timeoutSeconds = liveTimeoutSeconds(options.timeout_seconds);
|
|
14
|
+
const requestedInterface = options.interface?.trim();
|
|
15
|
+
const interfaceExpr = requestedInterface
|
|
16
|
+
? shellQuote(requestedInterface)
|
|
17
|
+
: "\"$(iw dev 2>/dev/null | sed -n 's/^[[:space:]]*Interface[[:space:]]\\{1,\\}\\([^[:space:]]\\{1,\\}\\).*/\\1/p' | head -n 1)\"";
|
|
18
|
+
const command = [
|
|
19
|
+
"if ! command -v iw >/dev/null 2>&1; then echo __EMBED_IW_NOT_FOUND__; exit 127; fi",
|
|
20
|
+
`IFACE=${interfaceExpr}`,
|
|
21
|
+
"if [ -z \"$IFACE\" ]; then echo __EMBED_NO_WIFI_INTERFACE__; exit 3; fi",
|
|
22
|
+
"iw dev \"$IFACE\" scan 2>&1"
|
|
23
|
+
].join("; ");
|
|
24
|
+
const ssh = await runTaishanPiSsh(host, user, command, timeoutSeconds);
|
|
25
|
+
const output = `${ssh.stdout}\n${ssh.stderr}`.trim();
|
|
26
|
+
const networks = ssh.exit_code === 0 ? parseWifiScan(ssh.stdout) : [];
|
|
27
|
+
const error = wifiError(ssh, output);
|
|
28
|
+
return {
|
|
29
|
+
board_id: "taishanpi",
|
|
30
|
+
host,
|
|
31
|
+
user,
|
|
32
|
+
interface: requestedInterface ?? firstWifiInterface(ssh.stdout) ?? undefined,
|
|
33
|
+
ok: ssh.exit_code === 0,
|
|
34
|
+
networks,
|
|
35
|
+
network_count: networks.length,
|
|
36
|
+
exit_code: ssh.exit_code,
|
|
37
|
+
timed_out: ssh.timed_out,
|
|
38
|
+
output_tail: tailLines(output),
|
|
39
|
+
error,
|
|
40
|
+
observed_at: observedAt,
|
|
41
|
+
summary_for_user: ssh.exit_code === 0
|
|
42
|
+
? `WiFi scan completed on ${host}: ${networks.length} network(s) found.`
|
|
43
|
+
: `WiFi scan did not complete on ${host}${error ? `: ${error.message}` : "."}`
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export async function scanTaishanPiBluetoothDevices(options = {}) {
|
|
47
|
+
const observedAt = new Date().toISOString();
|
|
48
|
+
const host = liveHost(options.host);
|
|
49
|
+
const user = liveUser(options.user);
|
|
50
|
+
const durationSeconds = Math.max(3, Math.min(Math.trunc(options.duration_seconds ?? 8), 30));
|
|
51
|
+
const timeoutSeconds = Math.max(durationSeconds + 8, liveTimeoutSeconds(options.timeout_seconds));
|
|
52
|
+
const command = [
|
|
53
|
+
"if ! command -v hciconfig >/dev/null 2>&1; then echo __EMBED_HCICONFIG_NOT_FOUND__; exit 127; fi",
|
|
54
|
+
"BRINGUP_ATTEMPTED=0",
|
|
55
|
+
"if [ ! -e /sys/class/bluetooth/hci0 ] && command -v hciattach >/dev/null 2>&1 && [ -e /dev/ttyS1 ]; then if ! ps | grep -F 'hciattach -n /dev/ttyS1' | grep -v grep >/dev/null 2>&1; then nohup hciattach -n /dev/ttyS1 any 1500000 noflow >/tmp/embedlabs-hciattach.log 2>&1 < /dev/null & BRINGUP_ATTEMPTED=1; sleep 2; fi; fi",
|
|
56
|
+
"echo __EMBED_BRINGUP_ATTEMPTED__=$BRINGUP_ATTEMPTED",
|
|
57
|
+
"if [ ! -e /sys/class/bluetooth/hci0 ]; then echo __EMBED_NO_BLUETOOTH_HCI__; exit 3; fi",
|
|
58
|
+
"hciconfig hci0 up 2>&1 || true",
|
|
59
|
+
"echo __EMBED_HCICONFIG__",
|
|
60
|
+
"hciconfig -a 2>&1 || true",
|
|
61
|
+
"echo __EMBED_SCAN__",
|
|
62
|
+
`if command -v hcitool >/dev/null 2>&1; then timeout ${durationSeconds} hcitool scan 2>&1 || true; elif command -v bluetoothctl >/dev/null 2>&1; then timeout ${durationSeconds} bluetoothctl scan on 2>&1 || true; else echo __EMBED_BLUETOOTH_SCAN_TOOL_NOT_FOUND__; exit 127; fi`
|
|
63
|
+
].join("; ");
|
|
64
|
+
const ssh = await runTaishanPiSsh(host, user, command, timeoutSeconds);
|
|
65
|
+
const output = `${ssh.stdout}\n${ssh.stderr}`.trim();
|
|
66
|
+
const devices = ssh.exit_code === 0 ? parseBluetoothScan(bluetoothScanSection(ssh.stdout)) : [];
|
|
67
|
+
const error = bluetoothError(ssh, output);
|
|
68
|
+
return {
|
|
69
|
+
board_id: "taishanpi",
|
|
70
|
+
host,
|
|
71
|
+
user,
|
|
72
|
+
ok: ssh.exit_code === 0 && !error,
|
|
73
|
+
bringup_attempted: /__EMBED_BRINGUP_ATTEMPTED__=1/.test(ssh.stdout),
|
|
74
|
+
adapter_info: bluetoothAdapterSection(ssh.stdout),
|
|
75
|
+
raw_scan_results: bluetoothScanSection(ssh.stdout),
|
|
76
|
+
devices,
|
|
77
|
+
device_count: devices.length,
|
|
78
|
+
duration_seconds: durationSeconds,
|
|
79
|
+
exit_code: ssh.exit_code,
|
|
80
|
+
timed_out: ssh.timed_out,
|
|
81
|
+
output_tail: tailLines(output),
|
|
82
|
+
error,
|
|
83
|
+
observed_at: observedAt,
|
|
84
|
+
summary_for_user: ssh.exit_code === 0 && !error
|
|
85
|
+
? `Bluetooth scan completed on ${host}: ${devices.length} device(s) found.`
|
|
86
|
+
: `Bluetooth scan is not ready on ${host}${error ? `: ${error.message}` : "."}`
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export async function readTaishanPiCpuFrequency(options = {}) {
|
|
90
|
+
const observedAt = new Date().toISOString();
|
|
91
|
+
const host = liveHost(options.host);
|
|
92
|
+
const user = liveUser(options.user);
|
|
93
|
+
const ssh = await runTaishanPiSsh(host, user, "cat /sys/devices/system/cpu/cpufreq/policy*/scaling_cur_freq 2>/dev/null", liveTimeoutSeconds(options.timeout_seconds));
|
|
94
|
+
const values = ssh.stdout.split(/\s+/)
|
|
95
|
+
.map((item) => Number(item.trim()))
|
|
96
|
+
.filter((item) => Number.isFinite(item) && item > 0);
|
|
97
|
+
const frequencies = values.map((khz, index) => ({ policy: index, khz, mhz: khz / 1000 }));
|
|
98
|
+
return {
|
|
99
|
+
board_id: "taishanpi",
|
|
100
|
+
host,
|
|
101
|
+
user,
|
|
102
|
+
ok: ssh.exit_code === 0 && frequencies.length > 0,
|
|
103
|
+
frequencies,
|
|
104
|
+
exit_code: ssh.exit_code,
|
|
105
|
+
timed_out: ssh.timed_out,
|
|
106
|
+
output_tail: tailLines(`${ssh.stdout}\n${ssh.stderr}`),
|
|
107
|
+
observed_at: observedAt,
|
|
108
|
+
summary_for_user: frequencies.length > 0
|
|
109
|
+
? `CPU frequency read completed on ${host}: ${frequencies.map((item) => `${item.mhz} MHz`).join(", ")}.`
|
|
110
|
+
: `CPU frequency could not be read on ${host}.`
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
export async function readTaishanPiTemperature(options = {}) {
|
|
114
|
+
const observedAt = new Date().toISOString();
|
|
115
|
+
const host = liveHost(options.host);
|
|
116
|
+
const user = liveUser(options.user);
|
|
117
|
+
const command = "for z in /sys/class/thermal/thermal_zone*; do [ -f \"$z/temp\" ] || continue; type=$(cat \"$z/type\" 2>/dev/null || basename \"$z\"); temp=$(cat \"$z/temp\" 2>/dev/null || true); [ -n \"$temp\" ] && printf '%s=%s\\n' \"$type\" \"$temp\"; done";
|
|
118
|
+
const ssh = await runTaishanPiSsh(host, user, command, liveTimeoutSeconds(options.timeout_seconds));
|
|
119
|
+
const zones = ssh.stdout.split(/\r?\n/)
|
|
120
|
+
.map((line, index) => parseTemperatureLine(line, index))
|
|
121
|
+
.filter((item) => item !== undefined);
|
|
122
|
+
return {
|
|
123
|
+
board_id: "taishanpi",
|
|
124
|
+
host,
|
|
125
|
+
user,
|
|
126
|
+
ok: ssh.exit_code === 0 && zones.length > 0,
|
|
127
|
+
zones,
|
|
128
|
+
exit_code: ssh.exit_code,
|
|
129
|
+
timed_out: ssh.timed_out,
|
|
130
|
+
output_tail: tailLines(`${ssh.stdout}\n${ssh.stderr}`),
|
|
131
|
+
observed_at: observedAt,
|
|
132
|
+
summary_for_user: zones.length > 0
|
|
133
|
+
? `Temperature read completed on ${host}: ${zones.map((item) => `${item.zone}=${item.celsius.toFixed(1)} C`).join(", ")}.`
|
|
134
|
+
: `Temperature could not be read on ${host}.`
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
export async function getTaishanPiQmlRuntimeStatus(options = {}) {
|
|
138
|
+
const observedAt = new Date().toISOString();
|
|
139
|
+
const host = liveHost(options.host);
|
|
140
|
+
const user = liveUser(options.user);
|
|
141
|
+
const port = livePort(options.port);
|
|
142
|
+
const [tcp, ssh] = await Promise.all([
|
|
143
|
+
probeTcpWithEvidence(host, port, 1000, observedAt),
|
|
144
|
+
runTaishanPiSsh(host, user, [
|
|
145
|
+
`[ -x ${shellQuote(QML_RUNNER)} ] && echo runner_executable=true || echo runner_executable=false`,
|
|
146
|
+
`ps | grep -F ${shellQuote("embed-qml-board-runtime")} | grep -v grep || true`,
|
|
147
|
+
`tail -n 40 ${shellQuote(QML_LOG)} 2>/dev/null || true`
|
|
148
|
+
].join("; "), liveTimeoutSeconds(options.timeout_seconds))
|
|
149
|
+
]);
|
|
150
|
+
const output = `${ssh.stdout}\n${ssh.stderr}`.trim();
|
|
151
|
+
const runnerExecutable = /runner_executable=true/.test(ssh.stdout);
|
|
152
|
+
return {
|
|
153
|
+
board_id: "taishanpi",
|
|
154
|
+
host,
|
|
155
|
+
user,
|
|
156
|
+
port,
|
|
157
|
+
ok: tcp.reachable,
|
|
158
|
+
reachable: tcp.reachable,
|
|
159
|
+
runner: QML_RUNNER,
|
|
160
|
+
runner_executable: runnerExecutable,
|
|
161
|
+
log_path: QML_LOG,
|
|
162
|
+
tcp,
|
|
163
|
+
ssh: {
|
|
164
|
+
ok: ssh.exit_code === 0,
|
|
165
|
+
exit_code: ssh.exit_code,
|
|
166
|
+
timed_out: ssh.timed_out,
|
|
167
|
+
output_tail: tailLines(output)
|
|
168
|
+
},
|
|
169
|
+
observed_at: observedAt,
|
|
170
|
+
summary_for_user: tcp.reachable
|
|
171
|
+
? `QML board runtime is reachable at ${host}:${port}.`
|
|
172
|
+
: `QML board runtime is not reachable at ${host}:${port}.`
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
export async function startTaishanPiQmlRuntime(options = {}) {
|
|
176
|
+
const observedAt = new Date().toISOString();
|
|
177
|
+
const host = liveHost(options.host);
|
|
178
|
+
const user = liveUser(options.user);
|
|
179
|
+
const port = livePort(options.port);
|
|
180
|
+
const before = options.force_restart === true ? undefined : await getTaishanPiQmlRuntimeStatus({ host, user, port, timeout_seconds: options.timeout_seconds });
|
|
181
|
+
if (before?.reachable) {
|
|
182
|
+
return {
|
|
183
|
+
...before,
|
|
184
|
+
started: false,
|
|
185
|
+
observed_at: observedAt,
|
|
186
|
+
summary_for_user: `QML board runtime was already reachable at ${host}:${port}.`
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const command = [
|
|
190
|
+
options.force_restart === true ? `pkill -f ${shellQuote("embed-qml-board-runtime")} 2>/dev/null || true` : "",
|
|
191
|
+
`if [ ! -x ${shellQuote(QML_RUNNER)} ]; then echo __EMBED_QML_RUNNER_NOT_FOUND__; exit 3; fi`,
|
|
192
|
+
`nohup ${shellQuote(QML_RUNNER)} > ${shellQuote(QML_LOG)} 2>&1 < /dev/null & echo started_pid=$!`
|
|
193
|
+
].filter(Boolean).join("; ");
|
|
194
|
+
const ssh = await runTaishanPiSsh(host, user, command, liveTimeoutSeconds(options.timeout_seconds));
|
|
195
|
+
await new Promise((resolve) => setTimeout(resolve, 1200));
|
|
196
|
+
const status = await getTaishanPiQmlRuntimeStatus({ host, user, port, timeout_seconds: options.timeout_seconds });
|
|
197
|
+
const output = `${ssh.stdout}\n${ssh.stderr}`.trim();
|
|
198
|
+
return {
|
|
199
|
+
...status,
|
|
200
|
+
started: status.reachable,
|
|
201
|
+
start_exit_code: ssh.exit_code,
|
|
202
|
+
start_timed_out: ssh.timed_out,
|
|
203
|
+
start_output_tail: tailLines(output),
|
|
204
|
+
error: ssh.exit_code === 0 ? undefined : qmlStartError(ssh, output),
|
|
205
|
+
observed_at: observedAt,
|
|
206
|
+
summary_for_user: status.reachable
|
|
207
|
+
? `QML board runtime started and is reachable at ${host}:${port}.`
|
|
208
|
+
: `QML board runtime did not become reachable at ${host}:${port}.`
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
async function runTaishanPiSsh(host, user, command, timeoutSeconds) {
|
|
212
|
+
const sshExists = await commandExists("ssh");
|
|
213
|
+
if (!sshExists) {
|
|
214
|
+
return {
|
|
215
|
+
command: "ssh",
|
|
216
|
+
args: [],
|
|
217
|
+
exit_code: null,
|
|
218
|
+
stdout: "",
|
|
219
|
+
stderr: "ssh binary was not found on PATH.",
|
|
220
|
+
timed_out: false
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return await runCommand("ssh", [
|
|
224
|
+
"-o", "BatchMode=yes",
|
|
225
|
+
"-o", "LogLevel=ERROR",
|
|
226
|
+
"-o", "UserKnownHostsFile=/dev/null",
|
|
227
|
+
"-o", "StrictHostKeyChecking=no",
|
|
228
|
+
"-o", `ConnectTimeout=${Math.max(1, Math.min(timeoutSeconds, 30))}`,
|
|
229
|
+
`${user}@${host}`,
|
|
230
|
+
command
|
|
231
|
+
], timeoutSeconds * 1000 + 1000);
|
|
232
|
+
}
|
|
233
|
+
function parseWifiScan(output) {
|
|
234
|
+
const networks = [];
|
|
235
|
+
const blocks = output.split(/^BSS /m).slice(1);
|
|
236
|
+
for (const block of blocks) {
|
|
237
|
+
const firstLine = block.split(/\r?\n/, 1)[0] ?? "";
|
|
238
|
+
const bssid = firstLine.match(/^([0-9a-f:]{17})/i)?.[1];
|
|
239
|
+
const ssid = block.match(/^[ \t]*SSID:[ \t]*(.*)$/m)?.[1] ?? "";
|
|
240
|
+
const signal = Number(block.match(/^[ \t]*signal:[ \t]*(-?\d+(?:\.\d+)?)[ \t]*dBm$/m)?.[1]);
|
|
241
|
+
const frequency = Number(block.match(/^[ \t]*freq:[ \t]*(\d+)/m)?.[1]);
|
|
242
|
+
const channel = Number(block.match(/^[ \t]*DS Parameter set:[ \t]*channel[ \t]*(\d+)/m)?.[1]);
|
|
243
|
+
networks.push({
|
|
244
|
+
bssid,
|
|
245
|
+
ssid,
|
|
246
|
+
signal_dbm: Number.isFinite(signal) ? signal : undefined,
|
|
247
|
+
frequency_mhz: Number.isFinite(frequency) ? frequency : undefined,
|
|
248
|
+
channel: Number.isFinite(channel) ? channel : undefined
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
return networks;
|
|
252
|
+
}
|
|
253
|
+
function parseBluetoothScan(output) {
|
|
254
|
+
const devices = new Map();
|
|
255
|
+
for (const line of output.split(/\r?\n/)) {
|
|
256
|
+
const bluetoothctlMatch = line.match(/Device\s+([0-9A-F:]{17})\s*(.*)$/i);
|
|
257
|
+
const hcitoolMatch = line.match(/^\s*([0-9A-F:]{17})\s+(.+?)\s*$/i);
|
|
258
|
+
const match = bluetoothctlMatch ?? hcitoolMatch;
|
|
259
|
+
if (match && !/^Scanning\b/i.test(line.trim())) {
|
|
260
|
+
const address = match[1].toUpperCase();
|
|
261
|
+
devices.set(address, { address, name: match[2]?.trim() || undefined });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return [...devices.values()];
|
|
265
|
+
}
|
|
266
|
+
function bluetoothAdapterSection(output) {
|
|
267
|
+
return output
|
|
268
|
+
.split("__EMBED_HCICONFIG__")[1]
|
|
269
|
+
?.split("__EMBED_SCAN__")[0]
|
|
270
|
+
?.trim() ?? "";
|
|
271
|
+
}
|
|
272
|
+
function bluetoothScanSection(output) {
|
|
273
|
+
return output.split("__EMBED_SCAN__")[1]?.trim() ?? output;
|
|
274
|
+
}
|
|
275
|
+
function parseTemperatureLine(line, index) {
|
|
276
|
+
const match = line.match(/^([^=]+)=(-?\d+)$/);
|
|
277
|
+
if (!match) {
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
const milliCelsius = Number(match[2]);
|
|
281
|
+
if (!Number.isFinite(milliCelsius)) {
|
|
282
|
+
return undefined;
|
|
283
|
+
}
|
|
284
|
+
return {
|
|
285
|
+
zone: match[1] || `thermal_zone${index}`,
|
|
286
|
+
milli_celsius: milliCelsius,
|
|
287
|
+
celsius: milliCelsius / 1000
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function firstWifiInterface(output) {
|
|
291
|
+
return output.match(/^\s*Interface\s+(\S+)/m)?.[1];
|
|
292
|
+
}
|
|
293
|
+
function wifiError(ssh, output) {
|
|
294
|
+
if (ssh.timed_out) {
|
|
295
|
+
return { code: "wifi_scan_timeout", message: "WiFi scan timed out." };
|
|
296
|
+
}
|
|
297
|
+
if (output.includes("__EMBED_IW_NOT_FOUND__")) {
|
|
298
|
+
return { code: "iw_not_found", message: "iw is not installed on the board." };
|
|
299
|
+
}
|
|
300
|
+
if (output.includes("__EMBED_NO_WIFI_INTERFACE__")) {
|
|
301
|
+
return { code: "wifi_interface_not_found", message: "No WiFi interface was found on the board." };
|
|
302
|
+
}
|
|
303
|
+
return ssh.exit_code === 0 ? undefined : { code: "wifi_scan_failed", message: "The board WiFi scan command failed." };
|
|
304
|
+
}
|
|
305
|
+
function bluetoothError(ssh, output) {
|
|
306
|
+
if (ssh.timed_out) {
|
|
307
|
+
return { code: "bluetooth_scan_timeout", message: "Bluetooth scan timed out." };
|
|
308
|
+
}
|
|
309
|
+
if (output.includes("__EMBED_BLUETOOTHCTL_NOT_FOUND__")) {
|
|
310
|
+
return { code: "bluetoothctl_not_found", message: "bluetoothctl is not installed on the board." };
|
|
311
|
+
}
|
|
312
|
+
if (output.includes("__EMBED_HCICONFIG_NOT_FOUND__")) {
|
|
313
|
+
return { code: "hciconfig_not_found", message: "hciconfig is not installed on the board." };
|
|
314
|
+
}
|
|
315
|
+
if (output.includes("__EMBED_BLUETOOTH_SCAN_TOOL_NOT_FOUND__")) {
|
|
316
|
+
return { code: "bluetooth_scan_tool_not_found", message: "Neither hcitool nor bluetoothctl is installed on the board." };
|
|
317
|
+
}
|
|
318
|
+
if (output.includes("__EMBED_NO_BLUETOOTH_HCI__")) {
|
|
319
|
+
return { code: "bluetooth_controller_not_found", message: "No Bluetooth HCI controller is exposed by the board runtime." };
|
|
320
|
+
}
|
|
321
|
+
return ssh.exit_code === 0 ? undefined : { code: "bluetooth_scan_failed", message: "The board Bluetooth scan command failed." };
|
|
322
|
+
}
|
|
323
|
+
function qmlStartError(ssh, output) {
|
|
324
|
+
if (ssh.timed_out) {
|
|
325
|
+
return { code: "qml_runtime_start_timeout", message: "QML runtime start command timed out." };
|
|
326
|
+
}
|
|
327
|
+
if (output.includes("__EMBED_QML_RUNNER_NOT_FOUND__")) {
|
|
328
|
+
return { code: "qml_runner_not_found", message: `${QML_RUNNER} is not executable on the board.` };
|
|
329
|
+
}
|
|
330
|
+
return ssh.exit_code === 0 ? undefined : { code: "qml_runtime_start_failed", message: "QML runtime start command failed." };
|
|
331
|
+
}
|
|
332
|
+
function liveHost(host) {
|
|
333
|
+
return host?.trim() || DEFAULT_HOST;
|
|
334
|
+
}
|
|
335
|
+
function liveUser(user) {
|
|
336
|
+
return user?.trim() || DEFAULT_USER;
|
|
337
|
+
}
|
|
338
|
+
function liveTimeoutSeconds(timeoutSeconds) {
|
|
339
|
+
const value = Number.isFinite(timeoutSeconds) ? Math.trunc(Number(timeoutSeconds)) : DEFAULT_TIMEOUT_SECONDS;
|
|
340
|
+
return Math.max(3, Math.min(value, 60));
|
|
341
|
+
}
|
|
342
|
+
function livePort(port) {
|
|
343
|
+
const value = Number.isFinite(port) ? Math.trunc(Number(port)) : DEFAULT_QML_PORT;
|
|
344
|
+
return Math.max(1, Math.min(value, 65_535));
|
|
345
|
+
}
|
|
346
|
+
function shellQuote(value) {
|
|
347
|
+
return `'${value.replace(/'/g, "'\\''")}'`;
|
|
348
|
+
}
|
|
349
|
+
//# sourceMappingURL=taishanpi-live.js.map
|