Commit Graph

107 Commits

Author SHA1 Message Date
EugeneTes
d43b0f147c bump version to 1.0.18 2026-09-04 06:35:44 +00:00
EugeneTes
f8095267ac Merge branch 'master' (POS895/POS896 integration) into main
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
2026-09-04 08:31:22 +02:00
EugeneTes
36eab4b4b2 fix ESC * bit-image banding: match line spacing to the slice height
The POS895 logo printed with a white line under every band. Root cause is
in PrintImageBitModeAsync, not in the adapter: between slices it advances
with a bare LF, which moves the paper by the CURRENT line spacing rather
than by the slice height. ESC @ leaves that spacing at 1/6 inch, about
33.8 dots at 203 dpi, so each pass painted 24 dot rows and then advanced
~34, leaving a ~10 dot gap. The code assumed LF advances exactly one
slice; nothing ever made that true.

Confirmed on hardware before changing anything: printed two identical
solid 576x96 blocks, one at default spacing and one after ESC 3 24. The
first came out striped, the second solid.

Fixed by setting the spacing to the slice height for the duration of the
image and restoring the default in a finally. The restore matters as much
as the fix - leaving it at 24 would compress every following line of text,
turning a logo defect into a whole-receipt one.

Fixed here rather than in POS895 because the assumption is wrong for any
caller of this method. Blast radius is nil: PrintImageBitModeAsync has no
other production consumer, since TM_T30IIITranslated uses the raster path
and TM_U220IITranslated.PrintImageAsync is an empty stub.

Three regression tests drive a real EpsonPrinter against a loopback TCP
listener and assert on the wire bytes: ESC 3 <sliceHeight> before the
first ESC *, ESC 2 after the last, and the spacing tracking the mode
(ESC 3 8 for 8-dot) rather than a hardcoded 24. All three failed before
the fix.

Verified on the printer: the logo now prints solid.

Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
2026-09-03 14:11:17 +02:00
EugeneTes
ac72e9ac4e add a POS895 target to EpsonTest that prints via the IPrinter adapter
EpsonTest predates the adapter layer: PrintCommandsToEpsonPrinterAsync
takes an EpsonPrinter and drives it directly, so PrinterWrapperFactory,
the model adapters and the 0x20 dispatch were never exercised. Against a
POS895 that path feeds with ESC d, which the firmware ignores, so every
cut slices through the last printed line, and it never sends a logo at
all - the only logo branch is the HTML one, which uses GS ( L, the
command that prints nothing on this hardware.

PrintCommandsViaAdapterAsync instead does what EpsonPrintService does for
a real job: query GS I 1, let PrinterWrapperFactory choose the adapter
from the reported byte, then print the logo and commands through it and
cut. It logs the reported id and the chosen adapter, so a wrong or
missing model byte is visible rather than silent.

Menu gains a third target and the non-interactive form accepts "pos895",
so `dotnet run -- final pos895` prints a Final receipt with its logo
through the production path. That covers the one part of the POS895 work
nothing had executed end to end: the ESC * bit-image logo.

Note EpsonTest must be run from its own directory; the logo paths are
relative and `dotnet run --project` from the repo root cannot resolve
them. That predates this change.

Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
2026-09-03 13:34:02 +02:00
EugeneTes
b42d980877 Fix UDP port 9100 test race; correct CompositeDiscoveryService design doc
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
2026-09-03 13:08:39 +02:00
EugeneTes
fd19cd9358 fix: propagate SPRT discovery bind/broadcast failures instead of swallowing them
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
2026-09-03 13:01:58 +02:00
EugeneTes
a4e96f3041 Fix three pre-merge review findings on POS895 branch
1. Strengthen POS895 IsCut test: add RecordingEpsonPrinter.CallOrder so
   the test can assert feeds happen BEFORE the cut, plus a LineFeedBytes
   count assertion. The prior test could not detect the FeedAsync call
   being deleted from the IsCut branch (verified: test now fails when
   that line is removed, passes when restored).

2. CompositeDiscoveryService: distinguish "some children failed" from
   "every child failed". Per-child failures are still absorbed, but if
   every discovery service throws, the composite now rethrows instead
   of silently returning an empty list. Previously a total discovery
   failure (e.g. a routeless interface) looked like "discovery
   succeeded, found nothing" to PrinterDiscoveryBackgroundTask, which
   would expire and unregister every known printer after the retention
   window, discarding their queued jobs. Also corrected a doc comment
   that overstated Task.WhenAll's timeout guarantee.

3. Update CLAUDE.md to describe the POS895/POS896 work as shipped
   rather than future work: added the Inspectron.Epson.Tests and
   Pos895Test projects, a dotnet test command, SprtDiscoveryService/
   CompositeDiscoveryService/POS895 in the architecture sections, the
   0x20 printer-ID note, and the established SPRT hardware facts.

Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
2026-09-03 12:46:14 +02:00
EugeneTes
9680ab0ad3 bind composite discovery and add emulated POS895
Epson printers answer ENPC and POS895/POS896 answer SPRT; only the
composite discovery service sees both. Wire CompositeDiscoveryService
(wrapping DiscoveryService + SprtDiscoveryService) into production DI,
and add a 576px emulated POS895 (127.0.0.4) alongside the existing
TM-U220II/TM-T30III emulation entries so receipt layouts can be
checked without hardware.

Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
2026-09-03 12:26:56 +02:00
EugeneTes
ca773610ad fail cleanly when a printer does not answer GS I 1
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
2026-09-03 12:22:14 +02:00
EugeneTes
51792fcd20 add reflection-based test coverage for DiscoveryService.GetModelNameFromId
Ensures the 0x20 => "POS895" registration is actually tested. Without this,
the line could be deleted and the whole suite would still pass.

Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
2026-09-03 12:19:34 +02:00
EugeneTes
020c17896a register printer id 0x20 as POS895 across the three dispatch points
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
2026-09-03 12:13:41 +02:00
EugeneTes
cd916c2761 add POS895 printer adapter with LF feed and ESC * logo
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
2026-09-03 12:07:32 +02:00
EugeneTes
40a72195d3 strengthen CompositeDiscoveryService tests for deduplication and concurrency
- Replace weak deduplication test with one using distinguishable printers
  (TM-T30III/ENPC vs POS895/SPRT on same IP) to prove first-wins semantics
- Add concurrency test using SlowStubDiscovery to catch sequential regressions
  (3 * 300ms tasks must complete in < 700ms, not >= 900ms)
- Add StubDiscovery constructor accepting List<DiscoveredPrinter> for
  fully-populated test data

Test suite now 13/13 passing with proven concurrent execution.

Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
2026-09-03 12:03:24 +02:00
EugeneTes
e93dfdc184 add CompositeDiscoveryService merging ENPC and SPRT results
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
2026-09-03 11:57:03 +02:00
EugeneTes
f219129ce4 add SPRT discovery service for POS895 printers
Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
2026-09-03 11:51:05 +02:00
EugeneTes
2f26a933cb amend POS895 plan: merge discovery tasks, drop network-dependent test
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
2026-09-03 11:46:25 +02:00
EugeneTes
84b94e8c1a add POS895 integration implementation plan
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
2026-09-03 11:32:19 +02:00
EugeneTes
0fb4edfaab probe remaining POS895 unknowns; add integration design spec
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
2026-09-03 11:19:59 +02:00
EugeneTes
17a6eb6b39 reverse engineer POS895 discovery; add SPRT discovery probe
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
2026-09-03 10:45:35 +02:00
EugeneTes
8ca8370426 add POS895 hardware smoke test project
Pos895Test is a console app that drives a POS895 over TCP through the
existing EpsonPrinter class, to answer the first question in the
feasibility notes: does the printer accept our ESC/POS at all.

It builds the whole job as one byte array and sends that exact array, so
the hex listing it prints is the wire content by construction rather than
a reconstruction that can drift. Current job prints a "|---" column ruler
at 48/width characters per row for width magnifications 1x, 2x and 3x at
double height, so every row should end at the same right margin.

Confirmed against the hardware so far:

- DLE EOT 1..4 are answered and follow the Epson bit layout exactly, with
  the 0x12 fixed bits set on every reply, so EpsonPrinterStatus parses
  this printer unmodified.
- Font A is 48 columns on the 576-dot line, as the manuals claim.
- GS ! magnification works, verified at width 1x/2x/3x and height 2x.
- ESC ! 0x00 selects Font A.

Not yet confirmed: Font B and its claimed 64 columns, the GS I 1 model
byte, ESC d n feed (this firmware appears to ignore it, hence the literal
LF bytes before the cut), code page 18 being CP852, and raster graphics.

Deliberately does not call GS I 1: the model byte is still an open
unknown and a smoke test must not be able to fail on one.

All settings are constants at the top of Program.cs; there are no
arguments.

Claude-Session: https://claude.ai/code/session_01E6N3P4nSZLtp4BC2LXVref
2026-09-03 10:10:08 +02:00
EugeneTes
772982deb2 add POS895/POS896 feasibility notes and CLAUDE.md pointer
Study-only writeup of what supporting the SPRT-derived POS895/POS896
ESC/POS clones would take: no vendor SDK linking, a 576-dot IPrinter
adapter, non-ENPC discovery, a sturdier printer-ID probe, and the
hardware checks still open.

Claude-Session: https://claude.ai/code/session_01WAG8xogHxq9hDJScu386Jf
2026-09-03 09:23:55 +02:00
EugeneTes
678e353d5b target linux-arm (armv7) for raspberry pi 32-bit OS, bump to 1.0.16 2026-07-23 14:15:52 +00:00
EugeneTes
894836f94d Merge branch 'feature/insin-integration'
Add Insin integration:
- Runtime telemetry via loopback (service.started, job.printed, job.failed.*, printer.discovered/online/offline)
- Package delivery: install.cfg, postinst, prem, systemd unit
- Manual publish script (scripts/publish-insin.sh)
- Config path move to /opt/epson-print-service
- Bump EpsonPrintService version to 1.0.15
2026-07-23 08:44:38 +00:00
EugeneTes
421f3085f0 select insin CLI by host arch, drop qemu note, bump version to 1.0.15 2026-07-23 08:44:17 +00:00
EugeneTes
7105659ab0 add scripts/publish-insin.sh for manual insin package publish 2026-07-23 08:25:33 +00:00
EugeneTes
732ee08561 drop duplicate print_server.service from insin package 2026-07-23 08:23:56 +00:00
EugeneTes
21ecea9741 add insin package assets (install.cfg, hooks, systemd units) 2026-07-23 08:20:08 +00:00
EugeneTes
7dfce95dc1 move linux config path to /opt/epson-print-service 2026-07-23 08:16:23 +00:00
EugeneTes
120634e569 bind IInsinTelemetry in DI and emit service.started 2026-07-23 08:09:54 +00:00
EugeneTes
c6d0d58722 emit telemetry on exception path and include durationMs in failure emits 2026-07-23 08:08:30 +00:00
EugeneTes
e70126be2b emit insin telemetry for job outcomes in PrinterQueue 2026-07-23 08:03:32 +00:00
EugeneTes
050616b595 add InsinTelemetryDiscoveredPrintersReceiver decorator 2026-07-23 07:57:04 +00:00
EugeneTes
465d9b4b4d add LoopbackInsinTelemetry with rate-limited warn logging 2026-07-23 07:51:03 +00:00
EugeneTes
37318f9653 add InsinEventKinds and PrintErrorType mapping 2026-07-23 07:46:44 +00:00
EugeneTes
2c52861d70 add IInsinTelemetry and NullInsinTelemetry 2026-07-23 07:43:35 +00:00
EugeneTes
d01efdd8c2 add InsinMessageFormatter 2026-07-23 07:39:52 +00:00
EugeneTes
f8e5515050 add Inspectron.Epson.Tests xUnit project 2026-07-23 07:36:42 +00:00
EugeneTes
360320a474 add Insin integration implementation plan 2026-07-23 07:34:32 +00:00
EugeneTes
042b9c9633 add Insin integration spec and CLAUDE.md notes 2026-07-23 07:21:07 +00:00
EugeneTes
dc9bbbe467 Merge branch 'feature/PRI-9-interactive-console-test-runner' 2026-07-20 08:21:41 +02:00
EugeneTes
db30523697 bump EpsonPrintService version to 1.0.14 2026-07-09 08:54:30 +02:00
EugeneTes
5adb8f975c bump EpsonPrintService version to 1.0.13 2026-07-09 08:48:26 +02:00
EugeneTes
a51e5cb636 adapt money return receipt to new refund payload schema 2026-07-09 08:48:26 +02:00
EugeneTes
f64d2c8f28 add pudu robot control client; drop global.json SDK pin 2026-07-06 14:51:39 +02:00
EugeneTes
b927f48260 add print-job-arrived hook system 2026-07-06 14:51:29 +02:00
EugeneTes
91d2d18c80 add money return / credit note receipt type with HTML test 2026-07-06 14:51:23 +02:00
EugeneTes
398a3e3db8 hardcoded discovery 2026-04-20 10:43:44 +02:00
006ddf12dd Merge pull request 'Interactive console test runner for EpsonTest' (#1) from feature/PRI-9-interactive-console-test-runner into master
Reviewed-on: admin/inspectron_print_server#1
2026-04-10 06:36:09 +00:00
CTO
c94acc8bf7 Fix ambiguous Gang/Dish/DishModifications references in EpsonTest build
Use fully-qualified KitchenReceipt namespace for Gang, Dish, and
DishModifications in BuildSampleKitchenReceipt and
BuildSampleKitchenReceiptNextDish to resolve CS0104 errors caused by
identical type names in BarReceipt and KitchenReceipt namespaces.
Add global.json to pin SDK to .NET 8.0 for compatibility with .slnx.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-09 14:55:35 +00:00
CTO
a4582ee1f8 interactive console test runner for EpsonTest
Replace commented-out code blocks with arrow-key navigation menu.
First menu selects receipt type (Kitchen/Final/Invoice/Bar/OrderItems),
second selects printer target (HTML or Epson hardware).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-04-09 14:18:01 +00:00