7.5 KiB
| phase | plan | subsystem | tags | dependency_graph | tech_stack | key_files | decisions | metrics | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 04-usb-manager-label-printing | 03 | printer |
|
|
|
|
|
|
Phase 4 Plan 03: Printer Driver Interface + HTTP Endpoints Summary
One-liner: PrinterDriver interface with MockDriver (PNG-to-/tmp) and PrtQutieDriver stub, plus POST /api/labels/:id/print (rate-limited) and GET /api/usb/events (SSE with goroutine-safe teardown).
What Was Built
internal/printer/driver.go
PrinterDriver interface with Connect() error, Print(bitmap []byte, width, height int) error, Disconnect() error. ImageToRawBitmap(img image.Image) ([]byte, int, int) converts image.Image to 1-bit packed row-major bitmap (dark pixels → 1). Sentinel errors: ErrNoDevice, ErrNotConnected, ErrEmptyBitmap.
internal/printer/mock_driver.go
MockDriver implements PrinterDriver. Print() reconstructs a grayscale PNG from the 1-bit bitmap and saves it to SaveDir (default /tmp) as hwlab-label-<timestamp>.png. Logs path to stdout. No hardware required — default driver until PRT Qutie arrives.
internal/printer/prt_qutie.go
PrtQutieDriver implements PrinterDriver using go.bug.st/serial. Connect() returns ErrNoDevice (stub) — real port resolution deferred to hardware characterization day 2026-04-13. Print protocol unknown; TODO(hardware) comments reference Wireshark capture approach.
internal/api/handlers/label.go
LabelHandler handles POST /api/labels/:deviceID/print. Fetches device from NetBox, routes to labels.RenderCable or labels.RenderStandard based on IsCableDevice(), converts to bitmap via printer.ImageToRawBitmap, calls LabelPrinter.Print(). Rate-limited to 1 print/second via sync.Mutex + lastPrintTime (T-04-09 mitigation).
internal/api/handlers/usb_events.go
USBEventsHandler handles GET /api/usb/events. Streams usb.DeviceEvent values as Server-Sent Events. 30-second keepalive ticker prevents proxy timeouts. Returns on r.Context().Done() — no goroutine leak (T-04-11 mitigation verified by Test 5).
internal/api/router.go
Updated NewRouter signature to accept *handlers.LabelHandler and *handlers.USBEventsHandler. Routes: POST /api/labels/{deviceID}/print and GET /api/usb/events.
cmd/hwlab/main.go
USB Manager created (usb.NewManager(2*time.Second)), started via go usbManager.Start(ctx), stopped via defer usbManager.Stop(). printer.NewMockDriver() constructed and connected. LabelHandler and USBEventsHandler constructed and passed to NewRouter.
Commits
| Task | Commit | Description |
|---|---|---|
| Task 1 RED | b223b54 |
Failing printer driver tests (TDD RED) |
| Task 1 GREEN | 1156eff |
PrinterDriver interface, MockDriver, PrtQutieDriver stub |
| Task 2 RED | dd381ee |
Failing handler tests for label print and USB SSE (TDD RED) |
| Task 2 GREEN | 9f57cbd |
LabelHandler, USBEventsHandler, router wiring, main.go init |
Test Results
=== RUN TestMockDriverPrintSavesPNG PASS
=== RUN TestMockDriverPrintNilBitmap PASS
=== RUN TestMockDriverConnectDisconnect PASS
=== RUN TestImageToRawBitmapLength PASS
=== RUN TestPrtQutieConnectReturnsErrNoDevice PASS
=== RUN TestMockDriverSaveDirHonoured PASS
=== RUN TestLabelPrintSuccess PASS
=== RUN TestLabelPrintDeviceNotFound PASS
=== RUN TestLabelPrintPrinterError PASS
=== RUN TestUSBEventsSSEContentType PASS
=== RUN TestUSBEventsNoGoroutineLeak PASS
PASS — go build ./... PASS
Deviations from Plan
Auto-fixed Issues
1. [Rule 2 - Missing Critical] Rate limiting on print endpoint (T-04-09)
- Found during: Task 2 — threat model review
- Issue: Threat register marks T-04-09 (DoS via runaway print calls / label waste) as
mitigate. Plan code snippet had no rate limiting. - Fix: Added
sync.Mutex + lastPrintTime + printCooldown(1s)toLabelHandler. Returns HTTP 429 when violated. - Files modified: internal/api/handlers/label.go
- Commit:
9f57cbd
Known Stubs
| File | Line | Item | Reason |
|---|---|---|---|
| internal/printer/prt_qutie.go | 35 | Connect() returns ErrNoDevice |
Real VID/PID port enumeration deferred to hardware arrival 2026-04-13 |
| internal/printer/prt_qutie.go | 43–50 | Print() is a no-op |
PRT Qutie protocol unknown — requires Wireshark capture on hardware day |
| internal/api/handlers/label.go | 92–99 | USBVersion: "Unknown" in cable label |
Cable field parsing from TestData JSON deferred to Phase 5 |
These stubs do not prevent the plan's goal from being achieved: MockDriver renders and saves real PNG labels; the PrtQutieDriver stub is intentionally non-functional until hardware arrives.
Threat Flags
None — no new network endpoints beyond the two planned (POST /api/labels/:id/print, GET /api/usb/events). Both are LAN-only per project constraints.
Self-Check: PASSED
- internal/printer/driver.go: FOUND
- internal/printer/mock_driver.go: FOUND
- internal/printer/prt_qutie.go: FOUND
- internal/printer/driver_test.go: FOUND
- internal/api/handlers/label.go: FOUND
- internal/api/handlers/label_test.go: FOUND
- internal/api/handlers/usb_events.go: FOUND
- internal/api/router.go: FOUND (modified)
- cmd/hwlab/main.go: FOUND (modified)
- Commit
b223b54: FOUND - Commit
1156eff: FOUND - Commit
dd381ee: FOUND - Commit
9f57cbd: FOUND