314 lines
9.9 KiB
JavaScript
314 lines
9.9 KiB
JavaScript
export const DEVICE_POD_SERVICE_ID = "hwlab-device-pod";
|
|
export const DEVICE_POD_CONTRACT_VERSION = "device-pod-fake-v1";
|
|
export const DEFAULT_DEVICE_POD_ID = "device-pod-71-freq";
|
|
|
|
const DEFAULT_OBSERVED_AT = "2026-05-27T08:00:00.000Z";
|
|
|
|
export function buildDevicePodList(options = {}) {
|
|
const pod = buildFakeDevicePod(options);
|
|
return {
|
|
serviceId: DEVICE_POD_SERVICE_ID,
|
|
contractVersion: DEVICE_POD_CONTRACT_VERSION,
|
|
status: "ok",
|
|
observedAt: observedAt(options),
|
|
devicePods: [devicePodListItem(pod)],
|
|
selectedDevicePodId: pod.devicePodId,
|
|
source: sourceSummary(options)
|
|
};
|
|
}
|
|
|
|
export function buildDevicePodStatus(devicePodId = DEFAULT_DEVICE_POD_ID, options = {}) {
|
|
const pod = buildFakeDevicePod({ ...options, devicePodId });
|
|
return {
|
|
serviceId: DEVICE_POD_SERVICE_ID,
|
|
contractVersion: DEVICE_POD_CONTRACT_VERSION,
|
|
status: pod.status,
|
|
observedAt: observedAt(options),
|
|
source: sourceSummary(options),
|
|
devicePod: pod,
|
|
summary: {
|
|
devicePodId: pod.devicePodId,
|
|
targetId: pod.target.targetId,
|
|
status: pod.status,
|
|
statusLabel: "Fake 数据在线",
|
|
freshness: pod.freshness,
|
|
profileHash: pod.profile.profileHash,
|
|
blocker: pod.blocker,
|
|
latestEvent: buildDevicePodEvents(pod.devicePodId, { ...options, limit: 1 }).events[0] ?? null
|
|
}
|
|
};
|
|
}
|
|
|
|
export function buildDevicePodEvents(devicePodId = DEFAULT_DEVICE_POD_ID, options = {}) {
|
|
const pod = buildFakeDevicePod({ ...options, devicePodId });
|
|
const limit = normalizeLimit(options.limit, 80);
|
|
const events = fakeEvents(pod).slice(0, limit);
|
|
return {
|
|
serviceId: DEVICE_POD_SERVICE_ID,
|
|
contractVersion: DEVICE_POD_CONTRACT_VERSION,
|
|
status: "ok",
|
|
observedAt: observedAt(options),
|
|
devicePodId: pod.devicePodId,
|
|
targetId: pod.target.targetId,
|
|
source: sourceSummary(options),
|
|
events,
|
|
lines: events.map(formatDevicePodEventLine),
|
|
truncation: {
|
|
limit,
|
|
returned: events.length,
|
|
truncated: fakeEvents(pod).length > limit
|
|
}
|
|
};
|
|
}
|
|
|
|
export function buildDevicePodChipId(devicePodId = DEFAULT_DEVICE_POD_ID, options = {}) {
|
|
const pod = buildFakeDevicePod({ ...options, devicePodId });
|
|
return {
|
|
serviceId: DEVICE_POD_SERVICE_ID,
|
|
contractVersion: DEVICE_POD_CONTRACT_VERSION,
|
|
status: "ok",
|
|
observedAt: observedAt(options),
|
|
devicePodId: pod.devicePodId,
|
|
targetId: pod.target.targetId,
|
|
source: sourceSummary(options),
|
|
profileHash: pod.profile.profileHash,
|
|
interface: "debug-probe",
|
|
intent: "debug.probe.chip-id",
|
|
chipId: pod.debugInterface.chipId,
|
|
probe: pod.debugInterface.probe,
|
|
freshness: pod.debugInterface.freshness,
|
|
blocker: null,
|
|
refs: fakeRefs("debug-chip-id")
|
|
};
|
|
}
|
|
|
|
export function buildDevicePodUartStatus(devicePodId = DEFAULT_DEVICE_POD_ID, options = {}) {
|
|
const pod = buildFakeDevicePod({ ...options, devicePodId });
|
|
return {
|
|
serviceId: DEVICE_POD_SERVICE_ID,
|
|
contractVersion: DEVICE_POD_CONTRACT_VERSION,
|
|
status: "ok",
|
|
observedAt: observedAt(options),
|
|
devicePodId: pod.devicePodId,
|
|
targetId: pod.target.targetId,
|
|
source: sourceSummary(options),
|
|
profileHash: pod.profile.profileHash,
|
|
interface: "io-probe",
|
|
intent: "io.uart.status",
|
|
resourcePath: "/uart/1",
|
|
uart: pod.ioInterface.uart1,
|
|
freshness: pod.ioInterface.uart1.freshness,
|
|
blocker: null,
|
|
refs: fakeRefs("io-uart-status")
|
|
};
|
|
}
|
|
|
|
export function buildDevicePodUartTail(devicePodId = DEFAULT_DEVICE_POD_ID, options = {}) {
|
|
const pod = buildFakeDevicePod({ ...options, devicePodId });
|
|
const maxBytes = normalizeLimit(options.maxBytes, 12000);
|
|
const raw = pod.ioInterface.uart1.tail;
|
|
const truncated = Buffer.byteLength(raw, "utf8") > maxBytes;
|
|
const text = truncated ? raw.slice(0, maxBytes) : raw;
|
|
return {
|
|
serviceId: DEVICE_POD_SERVICE_ID,
|
|
contractVersion: DEVICE_POD_CONTRACT_VERSION,
|
|
status: "ok",
|
|
observedAt: observedAt(options),
|
|
devicePodId: pod.devicePodId,
|
|
targetId: pod.target.targetId,
|
|
source: sourceSummary(options),
|
|
profileHash: pod.profile.profileHash,
|
|
interface: "io-probe",
|
|
intent: "io.uart.fetch",
|
|
resourcePath: "/uart/1",
|
|
text,
|
|
bytes: Buffer.byteLength(text, "utf8"),
|
|
truncation: {
|
|
maxBytes,
|
|
truncated
|
|
},
|
|
freshness: pod.ioInterface.uart1.freshness,
|
|
blocker: null,
|
|
refs: fakeRefs("io-uart-tail")
|
|
};
|
|
}
|
|
|
|
export function buildDevicePodRestPayload(pathname, searchParams = new URLSearchParams(), options = {}) {
|
|
const cleanPath = String(pathname ?? "/").replace(/\/+$/u, "") || "/";
|
|
if (cleanPath === "/v1/device-pods") return buildDevicePodList(options);
|
|
const prefix = "/v1/device-pods/";
|
|
if (!cleanPath.startsWith(prefix)) return null;
|
|
const suffix = cleanPath.slice(prefix.length);
|
|
const [devicePodId, ...segments] = suffix.split("/").map((item) => decodeURIComponent(item)).filter(Boolean);
|
|
const route = segments.join("/");
|
|
if (!devicePodId) return null;
|
|
if (route === "status") return buildDevicePodStatus(devicePodId, options);
|
|
if (route === "events") return buildDevicePodEvents(devicePodId, { ...options, limit: searchParams.get("limit") });
|
|
if (route === "debug-probe/chip-id") return buildDevicePodChipId(devicePodId, options);
|
|
if (route === "io-probe/uart/1") return buildDevicePodUartStatus(devicePodId, options);
|
|
if (route === "io-probe/uart/1/tail") return buildDevicePodUartTail(devicePodId, { ...options, maxBytes: searchParams.get("maxBytes") });
|
|
return null;
|
|
}
|
|
|
|
export function formatDevicePodEventLine(event) {
|
|
const clock = event.ts ? event.ts.slice(11, 19) : "00:00:00";
|
|
const refs = event.refs ?? {};
|
|
return [
|
|
clock,
|
|
event.scope?.toUpperCase() ?? "SYSTEM",
|
|
event.status ?? "ok",
|
|
event.intent ?? "event",
|
|
event.summary ?? "",
|
|
refs.traceId ? `trace=${refs.traceId}` : null,
|
|
refs.operationId ? `op=${refs.operationId}` : null,
|
|
refs.evidenceId ? `evidence=${refs.evidenceId}` : null,
|
|
event.blocker?.code ? `blocker=${event.blocker.code}` : null
|
|
].filter(Boolean).join(" ");
|
|
}
|
|
|
|
function buildFakeDevicePod(options = {}) {
|
|
const now = observedAt(options);
|
|
const devicePodId = stringOr(options.devicePodId, DEFAULT_DEVICE_POD_ID);
|
|
return {
|
|
devicePodId,
|
|
target: {
|
|
targetId: "stm32f103-minsys-01",
|
|
name: "STM32F103 最小系统",
|
|
lock: {
|
|
status: "free",
|
|
owner: null
|
|
}
|
|
},
|
|
status: "ok",
|
|
freshness: {
|
|
observedAt: now,
|
|
ageMs: 8000,
|
|
stale: false
|
|
},
|
|
profile: {
|
|
profilePath: `.device-pod/${devicePodId}.json`,
|
|
profileHash: "sha256:8ad3f0b75a6f1f2b.fake-device-pod-profile",
|
|
loadedAt: now,
|
|
validation: "ok"
|
|
},
|
|
projectWorkspace: {
|
|
status: "ok",
|
|
workspaceRoot: "f:/work/hwlab-device/71-freq",
|
|
projectFile: "firmware/71-freq.uvprojx",
|
|
buildProfile: "debug",
|
|
latestBuild: {
|
|
status: "ok",
|
|
artifact: "build/71-freq.hex",
|
|
completedAt: "2026-05-27T07:59:42.000Z",
|
|
refs: fakeRefs("workspace-build")
|
|
}
|
|
},
|
|
debugInterface: {
|
|
status: "ok",
|
|
probe: "DAPLink CMSIS-DAP",
|
|
chipId: "STM32F103C8T6",
|
|
latestJob: {
|
|
status: "ok",
|
|
intent: "debug.probe.chip-id",
|
|
completedAt: now,
|
|
refs: fakeRefs("debug-chip-id")
|
|
},
|
|
freshness: {
|
|
observedAt: now,
|
|
ageMs: 8000,
|
|
stale: false
|
|
}
|
|
},
|
|
ioInterface: {
|
|
status: "ok",
|
|
uart1: {
|
|
status: "ok",
|
|
baudRate: 115200,
|
|
freshness: {
|
|
observedAt: now,
|
|
ageMs: 6000,
|
|
stale: false
|
|
},
|
|
tail: [
|
|
"[boot] device-pod fake UART ready",
|
|
"[app] freq=1000Hz duty=50% mode=debug",
|
|
"[io] uart/1 sample window ok"
|
|
].join("\n")
|
|
}
|
|
},
|
|
blocker: null,
|
|
refs: fakeRefs("device-pod-status")
|
|
};
|
|
}
|
|
|
|
function devicePodListItem(pod) {
|
|
return {
|
|
devicePodId: pod.devicePodId,
|
|
targetId: pod.target.targetId,
|
|
status: pod.status,
|
|
freshness: pod.freshness,
|
|
profileHash: pod.profile.profileHash,
|
|
summary: "Fake Device Pod 数据源,仅用于前端右侧看板迁移。",
|
|
blocker: pod.blocker
|
|
};
|
|
}
|
|
|
|
function fakeEvents(pod) {
|
|
return [
|
|
event(pod, "debug", "debug.probe.chip-id", "ok", "chip-id STM32F103C8T6", "debug-chip-id"),
|
|
event(pod, "io", "io.uart.fetch", "ok", "uart/1 tail bytes=86 truncated=false freshness=6s", "io-uart-tail"),
|
|
event(pod, "workspace", "workspace.build", "ok", "artifact build/71-freq.hex", "workspace-build"),
|
|
event(pod, "profile", "profile.sync", "ok", `profile=${pod.profile.profileHash}`, "profile-sync"),
|
|
event(pod, "system", "device-pod.fake-data", "ok", "fake device-pod service ready", "device-pod-status")
|
|
];
|
|
}
|
|
|
|
function event(pod, scope, intent, status, summary, refKey) {
|
|
return {
|
|
eventId: `evt_${refKey}`,
|
|
devicePodId: pod.devicePodId,
|
|
targetId: pod.target.targetId,
|
|
ts: observedAt(),
|
|
level: status === "ok" ? "info" : "warn",
|
|
scope,
|
|
intent,
|
|
status,
|
|
summary,
|
|
freshness: pod.freshness,
|
|
blocker: null,
|
|
refs: fakeRefs(refKey)
|
|
};
|
|
}
|
|
|
|
function fakeRefs(key) {
|
|
return {
|
|
operationId: `op_devicepod_${key}`,
|
|
traceId: `trc_devicepod_${key}`,
|
|
evidenceId: `evd_devicepod_${key}`,
|
|
jobId: `job_devicepod_${key}`
|
|
};
|
|
}
|
|
|
|
function sourceSummary(options = {}) {
|
|
return {
|
|
kind: options.sourceKind ?? "FAKE",
|
|
serviceId: DEVICE_POD_SERVICE_ID,
|
|
note: "当前 device-pod 服务只提供前端迁移用 fake 数据,不连接真实硬件。"
|
|
};
|
|
}
|
|
|
|
function observedAt(options = {}) {
|
|
return stringOr(options.observedAt, new Date().toISOString() || DEFAULT_OBSERVED_AT);
|
|
}
|
|
|
|
function normalizeLimit(value, fallback) {
|
|
const parsed = Number.parseInt(value ?? "", 10);
|
|
if (!Number.isInteger(parsed) || parsed <= 0) return fallback;
|
|
return Math.min(parsed, 1000);
|
|
}
|
|
|
|
function stringOr(value, fallback) {
|
|
const text = value === undefined || value === null ? "" : String(value).trim();
|
|
return text || fallback;
|
|
}
|