homelabby/.planning/phases/04-usb-manager-label-printing/04-03-SUMMARY.md

7.5 KiB
Raw Blame History

phase plan subsystem tags dependency_graph tech_stack key_files decisions metrics
04-usb-manager-label-printing 03 printer
go
printer
serial
sse
http
tdd
requires provides affects
internal/usb/manager.go (DeviceEvent, Manager.Events)
internal/labels/renderer.go (RenderStandard, RenderCable, IsCableDevice)
internal/netbox/types.go (Device, CustomFields)
internal/printer package (PrinterDriver, MockDriver, PrtQutieDriver)
POST /api/labels/:deviceID/print endpoint
GET /api/usb/events SSE endpoint
cmd/hwlab/main.go (USB Manager start, MockDriver, new handler wiring)
internal/api/router.go (two new parameters + routes)
added patterns
PrinterDriver interface: Connect/Print(bitmap,w,h)/Disconnect — driver-agnostic bitmap protocol
MockDriver: saves PNG to SaveDir for visual inspection without hardware
PrtQutieDriver: stub returns ErrNoDevice — safe before hardware arrives
SSE handler: select on r.Context().Done() for goroutine-leak-safe teardown
Rate limiter: sync.Mutex + lastPrintTime guards 1/s print cadence (T-04-09)
TDD: RED commit → GREEN commit for both tasks
created modified
internal/printer/driver.go
internal/printer/mock_driver.go
internal/printer/prt_qutie.go
internal/printer/driver_test.go
internal/api/handlers/label.go
internal/api/handlers/label_test.go
internal/api/handlers/usb_events.go
internal/api/router.go
cmd/hwlab/main.go
Print() signature is Print(bitmap []byte, width, height int) not Print(image.Image) — driver-agnostic; bitmap conversion happens in handler via ImageToRawBitmap()
Rate limit implemented inline (sync.Mutex + lastPrintTime) rather than chi middleware — simpler for single-driver use case, avoids middleware ordering complexity
MockDriver.SaveDir is exported so tests can inject t.TempDir() without /tmp pollution
PrtQutieDriver.Connect() returns ErrNoDevice stub — real port resolution deferred to hardware arrival day 2026-04-13
USBEventsHandler uses 30s ticker keepalive to prevent proxy timeouts on quiet periods
duration_minutes completed_date tasks_completed tasks_total files_created files_modified
4 2026-04-10 2 2 7 2

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) to LabelHandler. 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 4350 Print() is a no-op PRT Qutie protocol unknown — requires Wireshark capture on hardware day
internal/api/handlers/label.go 9299 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