Resolution notes:
- Inspectron.Epson.Tests was added on both sides. Took the master version
wholesale (POS895, CompositeDiscoveryService, SprtDiscoveryService,
BitImagePrinting, PrinterRegistration tests). This drops the
Configuration/, Sanity/ and Telemetry/ tests that existed only on main,
and the test project's reference to EpsonPrintService.
- CLAUDE.md, PrintServerBootstrapper.cs and Inspectron.Epson.slnx
auto-merged; verified the composite discovery binding and the Insin
telemetry bindings both survive in the bootstrapper.
Solution builds with 0 errors; unit tests 33/33 pass on the merged tree.
Claude-Session: https://claude.ai/code/session_01KyCpB2jH3urYByRG5t98gz
FIX 1 — UDP 9100 binding race: SprtDiscoveryServiceTests and
CompositeDiscoveryServiceTests both use exclusive UDP 9100 binds. xunit
runs test classes concurrently by default, causing the tests to fail
intermittently with AddressAlreadyInUse. Add [Collection("udp-9100")]
attribute to both classes so they run serially instead.
FIX 2 — Design doc: CompositeDiscoveryService does NOT forward the callback
down to children; it raises the caller's callback itself after all children
finish, once per unique IP. Rewrite the relevant sentence to describe the
real behavior.
Verify: 30 tests pass (x2), build clean, no production code touched.
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
SprtDiscoveryService caught SocketException on the UDP 9100 bind and the
broadcast send and returned an empty list either way. That defeated the
CompositeDiscoveryService fix that rethrows only when every child discovery
service fails: on a real network fault the composite would see one child
throw (ENPC) and one child silently succeed empty (SPRT), conclude discovery
worked and found nothing, and let PrinterDiscoveryBackgroundTask expire and
deregister live Epson printers after the retention window - discarding their
queued jobs.
Move the policy decision to the composite, which already has it right: the
child now reports bind/broadcast failure honestly by rethrowing, and
CompositeDiscoveryService absorbs a single child's failure while still
rethrowing when every child fails. The per-datagram receive-loop catch is
unchanged - a single bad receive still shouldn't abort the pass.
Rewrote SprtDiscoveryServiceTests' bind-failure test to assert the throw, and
added an end-to-end CompositeDiscoveryServiceTests case with a real
SprtDiscoveryService failing to bind alongside a stub that succeeds, proving
the composite still degrades gracefully rather than losing the pass. Updated
the POS895 design doc to match the new contract.
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
Pre-flight scan found two things the plan mandated that a reviewer would
rightly flag: Task 1 committed a DiscoverPrintersAsync that threw
NotImplementedException until Task 2 replaced it, and a unit test asserted
an empty result from a real UDP broadcast, which passes on a laptop and
fails on any network with a POS895 on it.
Tasks 1 and 2 are now one task delivering the complete service, and the
network-dependent test is gone. The deterministic bind-failure test still
covers the degradation path. Six tasks now.
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
Seven TDD tasks derived from the design spec, each ending in a commit:
test project and SPRT protocol parsing, the discovery network path,
CompositeDiscoveryService, the POS895 adapter, the three switch
registrations, the nullable printer-id guard, and DI wiring plus an
emulated POS895.
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
Closes every hardware question that was blocking the integration design,
so the design now rests on measurements rather than assumptions.
Probe results, all against the real printer:
- GS I 1 returns 0x20, unique against 0x01/0x0D/0x13. This is what keeps
the integration small: model identity stays a byte and the existing
per-job identification flow works. Had it returned nothing or 0x01 the
POS895 would have been indistinguishable from a TM-T30III by probe, and
model identity would have had to come from discovery instead.
- GS I 66 and 67 are blank, so the model-name fallback the work items
proposed is useless here. GS I 68 returns the serial, and it matches
the SPRT discovery serial exactly, which makes it a correlation key
between a discovered printer and a connected one.
- GS ( L prints nothing. ESC * 33 and GS v 0 both render correctly across
the full 576 dots, so the adapter uses PrintImageBitModeAsync and the
GS v 0 work item is dropped as redundant.
- ESC t is honoured and index 18 really is CP852; 2 and 0 also render.
That last one exposed a fleet-wide bug that is deliberately left out of
scope: PrintTextAsync hardcodes CP852 for every printer, and CP852 cannot
encode e-grave, a-grave, u-grave, e-circumflex, i-grave or o-grave, so
French and Italian accents are dropped on the Epsons too. Now known to be
fixable via ESC t 2, but it needs its own spec.
The design spec covers discovery composition, the adapter, the three
switch registrations, testing and risks. It calls out that the
ReceiptConverterFactory WorkareaTicket switch defaults to 33 columns, so
missing it yields a POS895 printing kitchen tickets at the narrow
TM-U220 layout: wrong, but not obviously broken.
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
The POS895 does not answer Epson's ENPC broadcast, so PrintServer never
registers it and every job for it fails with "Printer not found". The
feasibility notes listed the vendor's own discovery protocol as unknown
and expected a packet capture to recover it, which is what this does.
Captured pktmon traffic while running the vendor EasySet tool on the
printer's segment. The protocol is:
query UDP broadcast 255.255.255.255:3289, sent FROM source port 9100
53 50 52 54 2D 49 6E 66 6F 72 00 00 0D "SPRT-Infor" 00 00 0D
reply from <printer>:3289 back to <querier>:9100, 39 ASCII bytes
"28-0e-8b-3d-ca-f1 2608120478"
MAC, 17 chars 12 spaces serial, 10 chars
The printer's address is the reply's source IP. Pos895Test now sends this
probe and parses the replies; run against the real printer it returns the
same MAC and serial the vendor tool got, so the reimplementation matches.
Two useful side effects. Port 3289 is the same UDP port ENPC uses, so a
composite IDiscoveryService can share one socket. And the querying machine
and the printer sat in different /24s of one flat segment, so the /24 TCP
sweep the notes originally preferred would have missed this printer.
The print test is commented out at the bottom of Program.cs rather than
deleted; it confirmed Font A is 48 columns and that GS ! magnification
works at width 1x/2x/3x with height 2x.
Notes updated: the discovery work item is no longer the largest one, the
compatibility table records DLE EOT and GS ! as confirmed and ESC d as
apparently ignored, and the answered questions are separated from the
ones still open.
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref