diff --git a/packages/devices/src/drivers/printer-k200l.test.ts b/packages/devices/src/drivers/printer-k200l.test.ts
index 4476b00..8beac03 100644
--- a/packages/devices/src/drivers/printer-k200l.test.ts
+++ b/packages/devices/src/drivers/printer-k200l.test.ts
@@ -57,6 +57,26 @@ describe("parseStatusPage", () => {
it("leaves unknown pages empty rather than guessing", () => {
expect(parseStatusPage(INDEX)).toEqual({});
});
+ it("reads a fault's Yes, which the board wraps in (captured live, cover open)", () => {
+ // Verbatim from the unit on 2026-09-09 with the cover open: the three fault cells carry
+ // markup the No cells don't — the first parser rejected them ("missing coverOpen,
+ // paperEnd, offline" on the booth) while the No cells parsed.
+ const page = boardPage()
+ .replace("Cover Is Open
No ", "Cover Is Open | Yes ")
+ .replace("Paper End | No ", "Paper End | Yes ")
+ .replace("Printer Off-Line | No ", "Printer Off-Line | Yes ");
+ expect(parseStatusPage(page)).toEqual({ coverOpen: true, cutterError: false, paperEnd: true, paperNearEnd: false, offline: true });
+ });
+ it("tolerates other markup shapes around a value", () => {
+ const page = boardPage()
+ .replace("Paper End | No ", "Paper End | Yes ")
+ .replace("Printer Off-Line | No ", "Printer Off-Line | \r\n\r\nYes\r\n");
+ expect(parseStatusPage(page)).toEqual({ coverOpen: false, cutterError: false, paperEnd: true, paperNearEnd: false, offline: true });
+ });
+ it("reads labels wrapped in markup too", () => {
+ const page = boardPage({ nearEnd: "Yes" }).replace(" | Paper Near End | ", "Paper Near End | ");
+ expect(parseStatusPage(page).paperNearEnd).toBe(true);
+ });
});
describe("k200lDriver.readStatus over TCP", () => {
diff --git a/packages/devices/src/drivers/printer-k200l.ts b/packages/devices/src/drivers/printer-k200l.ts
index a1998aa..395a65e 100644
--- a/packages/devices/src/drivers/printer-k200l.ts
+++ b/packages/devices/src/drivers/printer-k200l.ts
@@ -107,21 +107,33 @@ export function parseRawReply(raw: string): { status: number; body: string } {
return { status: Number(m[1]), body };
}
+/** A cell's visible text: inner tags stripped (the board wraps a "Yes" in markup the
+ * "No" cells don't carry), entities and padding normalised, lowercased. */
+function cellText(inner: string): string {
+ return inner
+ .replace(/<[^>]*>/g, "")
+ .replace(/ /gi, " ")
+ .replace(/\s+/g, " ")
+ .trim()
+ .toLowerCase();
+}
+
/**
* Parse the status table into boolean flags. Each fault is a `label |
- * Yes|No | ` pair (the board pads the value with spaces). Returns only the
+ * Yes|No | ` pair (the board pads the value with spaces, and may wrap a fault's
+ * "Yes" in its own tags — 2026-09-09, seen live as "missing coverOpen, paperEnd,
+ * offline" with the cover open, i.e. exactly the Yes cells). Returns only the
* recognised fields; a missing field stays undefined so the caller can detect an
* unexpected page (fail safe, not a false "ok").
*/
export function parseStatusPage(html: string): StatusFlags {
const out: StatusFlags = {};
- const rowRe = /]*>([^<]*?)<\/TD>\s* | ]*>([^<]*?)<\/TD>/gi;
+ const rowRe = / | ]*>\s*| ]*>([\s\S]*?)<\/TD>\s* | ]*>([\s\S]*?)<\/TD>/gi;
let m: RegExpExecArray | null;
while ((m = rowRe.exec(html))) {
if (m[1] === undefined || m[2] === undefined) continue;
- const label = m[1].replace(/ /gi, " ").replace(/\s+/g, " ").trim().toLowerCase();
- const value = m[2].replace(/ /gi, " ").trim().toLowerCase();
- const key = STATUS_FIELDS[label];
+ const key = STATUS_FIELDS[cellText(m[1])];
+ const value = cellText(m[2]);
if (key && (value === "yes" || value === "no")) out[key] = value === "yes";
}
return out;
diff --git a/wiki/decisions/appliance-provisioning.md b/wiki/decisions/appliance-provisioning.md
index 8fee786..2c11a91 100644
--- a/wiki/decisions/appliance-provisioning.md
+++ b/wiki/decisions/appliance-provisioning.md
@@ -314,6 +314,12 @@ sudo loginctl enable-linger admin # so the user service starts at boot witho
**Verify:** `systemctl --user status periphery` → active; the server **`park-buzi`** appears and
goes **OK/green** in Core → Servers. Then **delete the onboarding key**.
+> **After a reboot: `Unit periphery.service not loaded` (park-lab, 2026-09-09).** The unit
+> existed but had never been **enabled**, so nothing started it at boot and `reset-failed` /
+> `restart` had nothing to act on. Fix: `systemctl --user daemon-reload && systemctl --user enable
+> --now periphery`. Add `enable --now` to the install sequence above whenever the installer's own
+> enable did not stick (check with `systemctl --user is-enabled periphery` before leaving).
+
**➜ Next step is §7b below — the Stack itself is not deployed yet.** A green Server in Core just
means the agent connected; it runs nothing until you add the Registry/Git accounts and deploy.
diff --git a/wiki/entities/k200l-printer.md b/wiki/entities/k200l-printer.md
index 216aa11..8a637d9 100644
--- a/wiki/entities/k200l-printer.md
+++ b/wiki/entities/k200l-printer.md
@@ -67,6 +67,12 @@ Paper Near End Yes/No
Printer Off-Line Yes/No
```
+A fault is written as **`Yes`** while a clear row is a bare, space-padded
+`No` — the first parser only accepted tag-free cells, so with the cover open the booth showed
+*"unexpected status page (missing coverOpen, paperEnd, offline)"*: exactly the Yes cells. Fixed
+the same day (cell text is read with inner tags stripped); the verbatim markup is pinned in the
+driver's tests.
+
**Same rows, same ` | label | Yes|No | ` shape as the Rongta board's `/prn_stat.htm`**
([[printer-status-monitoring]]) — only the path differs, which is why nobody found it in July
(the Rongta driver looked for `/prn_stat.htm`, got nothing, and the unit was filed as "serves no
diff --git a/wiki/log.md b/wiki/log.md
index 3d9b430..088b8d7 100644
--- a/wiki/log.md
+++ b/wiki/log.md
@@ -3297,3 +3297,14 @@ factory reset), radar (idle level → activeLow), printers (K200L / Rongta / Cas
USB; "Test" probes, print a card to verify), the on-site order of work, and the gaps still
unrecorded (Cashino/Rongta factory addresses, the reader tool screens, camera activation, where
the site record lives). Linked from [[appliance-provisioning]] and [[k200l-printer]]; indexed.
+
+## [2026-09-09] fix | K200L status parser — a fault's "Yes" is wrapped in
+First live run on park-lab (driver `k200l`, LAN, cover open) showed *degraded — unexpected status
+page (missing coverOpen, paperEnd, offline)*: precisely the three Yes cells. Captured the raw page
+with the cover open: the board writes `Yes` for a fault and a bare
+padded `No` otherwise; the parser accepted only tag-free cells. Fix: cell text is read with inner
+tags stripped (row-anchored regex); tests pin the verbatim markup plus other shapes (devices 79).
+Live after the fix: degraded "cover open, paper out, printer off-line"; closed → ready. User:
+"we are good using the network with this printer." Also: park-lab Periphery was "not loaded"
+after a reboot — the unit had never been enabled; `systemctl --user enable --now periphery`
+recorded as a §7a gotcha on [[appliance-provisioning]]. Pages: [[k200l-printer]].