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"; // CI fast-lane marker: code-only edits here must roll by HWLAB_BOOT_COMMIT without rebuilding the env image. 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; }