fix(devices): K200L status parser reads a fault's Yes, which the board wraps in <FONT color=#ff0000>
Build & push images / images (push) Successful in 2m55s

First live run on park-lab with the cover open reported "unexpected status page
(missing coverOpen, paperEnd, offline)" — exactly the three fault cells. The board
writes a fault as <FONT color=#ff0000>Yes</FONT> and a clear row as a bare padded
No; the parser accepted only tag-free cells. Cell text is now read with inner tags
stripped (row-anchored match). Tests pin the verbatim captured markup plus other
shapes. Live after the fix: degraded "cover open, paper out, printer off-line";
cover closed → ready.

Wiki: the markup on the K200L page; Periphery "not loaded after reboot" (unit never
enabled → `systemctl --user enable --now periphery`) as a §7a gotcha in the
provisioning runbook; log.

Claude-Session: https://claude.ai/code/session_01FWncR69HgGPuei1dLrW3cU
This commit is contained in:
2026-09-09 12:57:58 +02:00
parent e8cb057082
commit 88f9c53fda
5 changed files with 60 additions and 5 deletions
@@ -57,6 +57,26 @@ describe("parseStatusPage", () => {
it("leaves unknown pages empty rather than guessing", () => { it("leaves unknown pages empty rather than guessing", () => {
expect(parseStatusPage(INDEX)).toEqual({}); expect(parseStatusPage(INDEX)).toEqual({});
}); });
it("reads a fault's Yes, which the board wraps in <FONT color=#ff0000> (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</TD><TD style=\"width: 23px\">No ", "Cover Is Open</TD><TD style=\"width: 23px\"><FONT color=#ff0000>Yes</FONT> ")
.replace("Paper End</TD><TD style=\"width: 23px\">No ", "Paper End</TD><TD style=\"width: 23px\"><FONT color=#ff0000>Yes</FONT> ")
.replace("Printer Off-Line</TD><TD style=\"width: 23px\">No ", "Printer Off-Line</TD><TD style=\"width: 23px\"><FONT color=#ff0000>Yes</FONT> ");
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</TD><TD style=\"width: 23px\">No ", "Paper End</TD><TD style=\"width: 23px\"><B><FONT color=\"#ff0000\">Yes </FONT></B>")
.replace("Printer Off-Line</TD><TD style=\"width: 23px\">No ", "Printer Off-Line</TD>\r\n<TD style=\"width: 23px\">\r\n<font>Yes</font>\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("<TD>Paper Near End</TD>", "<TD><B>Paper&nbsp;Near End</B></TD>");
expect(parseStatusPage(page).paperNearEnd).toBe(true);
});
}); });
describe("k200lDriver.readStatus over TCP", () => { describe("k200lDriver.readStatus over TCP", () => {
+17 -5
View File
@@ -107,21 +107,33 @@ export function parseRawReply(raw: string): { status: number; body: string } {
return { status: Number(m[1]), body }; 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(/&nbsp;/gi, " ")
.replace(/\s+/g, " ")
.trim()
.toLowerCase();
}
/** /**
* Parse the status table into boolean flags. Each fault is a `<TD>label</TD> * Parse the status table into boolean flags. Each fault is a `<TD>label</TD>
* <TD>Yes|No</TD>` pair (the board pads the value with spaces). Returns only the * <TD>Yes|No</TD>` 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 * recognised fields; a missing field stays undefined so the caller can detect an
* unexpected page (fail safe, not a false "ok"). * unexpected page (fail safe, not a false "ok").
*/ */
export function parseStatusPage(html: string): StatusFlags { export function parseStatusPage(html: string): StatusFlags {
const out: StatusFlags = {}; const out: StatusFlags = {};
const rowRe = /<TD[^>]*>([^<]*?)<\/TD>\s*<TD[^>]*>([^<]*?)<\/TD>/gi; const rowRe = /<TR[^>]*>\s*<TD[^>]*>([\s\S]*?)<\/TD>\s*<TD[^>]*>([\s\S]*?)<\/TD>/gi;
let m: RegExpExecArray | null; let m: RegExpExecArray | null;
while ((m = rowRe.exec(html))) { while ((m = rowRe.exec(html))) {
if (m[1] === undefined || m[2] === undefined) continue; if (m[1] === undefined || m[2] === undefined) continue;
const label = m[1].replace(/&nbsp;/gi, " ").replace(/\s+/g, " ").trim().toLowerCase(); const key = STATUS_FIELDS[cellText(m[1])];
const value = m[2].replace(/&nbsp;/gi, " ").trim().toLowerCase(); const value = cellText(m[2]);
const key = STATUS_FIELDS[label];
if (key && (value === "yes" || value === "no")) out[key] = value === "yes"; if (key && (value === "yes" || value === "no")) out[key] = value === "yes";
} }
return out; return out;
+6
View File
@@ -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 **Verify:** `systemctl --user status periphery` → active; the server **`park-buzi`** appears and
goes **OK/green** in Core → Servers. Then **delete the onboarding key**. 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 **➜ 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. means the agent connected; it runs nothing until you add the Registry/Git accounts and deploy.
+6
View File
@@ -67,6 +67,12 @@ Paper Near End Yes/No
Printer Off-Line Yes/No Printer Off-Line Yes/No
``` ```
A fault is written as **`<FONT color=#ff0000>Yes</FONT>`** 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 `<TD>label</TD><TD>Yes|No</TD>` shape as the Rongta board's `/prn_stat.htm`** **Same rows, same `<TD>label</TD><TD>Yes|No</TD>` shape as the Rongta board's `/prn_stat.htm`**
([[printer-status-monitoring]]) — only the path differs, which is why nobody found it in July ([[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 (the Rongta driver looked for `/prn_stat.htm`, got nothing, and the unit was filed as "serves no
+11
View File
@@ -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 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 unrecorded (Cashino/Rongta factory addresses, the reader tool screens, camera activation, where
the site record lives). Linked from [[appliance-provisioning]] and [[k200l-printer]]; indexed. 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 <FONT color=#ff0000>
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 `<FONT color=#ff0000>Yes</FONT>` 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]].