diff --git a/apps/server/src/routes/qr-reader.ts b/apps/server/src/routes/qr-reader.ts index 71985a7..b09665c 100644 --- a/apps/server/src/routes/qr-reader.ts +++ b/apps/server/src/routes/qr-reader.ts @@ -44,8 +44,14 @@ export async function qrReaderRoutes( // No auth: the reader is a machine on the isolated device subnet and offers no // auth on its side. Public route, like the Dingtian input push. - const handler = async (req: { query: ReaderQuery }) => { + const handler = async (req: { query: ReaderQuery }, reply: import("fastify").FastifyReply) => { const q = req.query; + // The reader sends `Connection: keep-alive` but only ACTS on our verdict (beep, + // drive output) once the socket CLOSES — every vendor demo replies + // `Connection: close` and shuts the socket. Without it the reader waits out a + // ~10 s keep-alive timeout before beeping. So force-close the connection. + // See wiki/sources/qrcode-sdk.md, entities/gee-qr-er80.md. + reply.header("connection", "close"); const cardid = (q.cardid ?? "").trim(); const mjihao = q.mjihao != null ? Number(q.mjihao) : 0; const serial = (q.cjihao ?? "").trim(); diff --git a/wiki/entities/gee-qr-er80.md b/wiki/entities/gee-qr-er80.md index 20fb5f3..dedb657 100644 --- a/wiki/entities/gee-qr-er80.md +++ b/wiki/entities/gee-qr-er80.md @@ -111,3 +111,12 @@ enter its serial here. `status:1` 2-beep when the QR matches a permit/open session. - `output` is replied as `0` (Access). Confirm on hardware whether the reader needs `1`/`2` (WG26/34) to drive its access line, vs. `0`. + +## ⚠️ Reply MUST set `Connection: close` (verified on hardware) + +The reader sends `Connection: keep-alive` but **only acts on the verdict (beep/output) once the TCP +socket CLOSES**. Fastify's default keeps the connection alive → the reader waits out a **~10 s +keep-alive timeout before beeping**, even though the server replied in ~15 ms. Every vendor demo +replies **`Connection: close`** and shuts the socket. Fix: the endpoint sets +`reply.header("connection","close")`. Symptom if regressed: correct accept/reject but a ~10 s lag +before the beep. (The request arrives fast; the delay is entirely the reader waiting for close.) diff --git a/wiki/log.md b/wiki/log.md index ca1b2f2..5869505 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -691,3 +691,12 @@ guarantee. Recorded in [[dingtian-relay]] (new Hardening section). [[dingtian-relay]] connected. NOT for production. Registered in the catalog. - To get a live accept: assign Stub barrier to the reader's lane + a permit whose QR = the scanned code → status:1 (2-beep) + logged pulseOpen. + +## [2026-06-16] fix | QR reader 10s beep delay — reply must Connection: close +- Live accept worked (status:1, pulseOpen, 2 beeps) but the beep came ~10 s LATE. Server responded + in 14.7 ms; user confirmed request is fast, only the beep lags → delay is the READER, not us. +- Cause: reader sends `Connection: keep-alive` but only ACTS on the verdict once the socket CLOSES; + Fastify kept it alive → reader waited out a ~10 s keep-alive timeout. Every vendor demo replies + `Connection: close` + shuts the socket. +- Fix: endpoint sets `reply.header("connection","close")`. Verified the header is now sent. +- Updated [[gee-qr-er80]] (⚠️ Connection: close requirement).