# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

This is a C# .NET 8.0 solution for Epson thermal receipt printer integration. The solution consists of a core SDK library (Inspectron.Epson) and a production print service application.

## Solution Structure

### Projects

- **Inspectron.Epson** - Core SDK library providing printer communication, status monitoring, ESC/POS commands, print queue management, and receipt conversion
- **EpsonPrintService** - Production console application with SignalR-based remote job source, printer discovery, and systemd service support
- **EpsonTest** - Test/example project for SDK functionality and receipt conversion

## Common Build Commands

```bash
# Build entire solution
dotnet build Inspectron.Epson.slnx

# Build specific project
dotnet build Inspectron.Epson/Inspectron.Epson.csproj
dotnet build EpsonPrintService/EpsonPrintService.csproj

# Run print service
dotnet run --project EpsonPrintService/EpsonPrintService.csproj

# Run tests/examples
dotnet run --project EpsonTest/EpsonTest.csproj

# Clean build artifacts
dotnet clean
```

## Architecture

### Three-Layer Architecture

1. **SDK Layer (Inspectron.Epson)**
   - `IEpsonPrinter` / `EpsonPrinter`: Main interface and TCP/IP implementation for printer operations (async/await)
   - `EpsonCommands`: Static ESC/POS command definitions
   - `EpsonImageConverter`: Image processing with Floyd-Steinberg dithering
   - `IDiscoveryService` / `DiscoveryService`: Network printer discovery (SLP, ENPC)
   - `PreconfiguredDiscoveryService`: Preconfigured printer list (no network scan)
   - `BonjourDiscovery`: mDNS-based printer discovery
   - `HtmlPrinter`: HTML output implementation for testing without hardware
   - Status classes: PrinterStatus, OfflineStatus, ErrorStatus, PaperSensorStatus, OverallStatus
   - Exception hierarchy: EpsonPrinterException -> EpsonConnectionException, EpsonCommandException

2. **Queue Layer (Inspectron.Epson.Queue namespace)**
   - `PrintServer`: Manager for per-printer queues with read/write lock coordination between printing and discovery
   - `PrinterQueue`: Per-printer queue with retry logic (ConcurrentQueue + priority ConcurrentStack)
   - `IPrintService`: Print workflow interface (PrintAsync returns `PrintResult`)
   - `PrintJob`: Job model with IP, Document, QueuedAt, RetryCount
   - `PrintResult`: Success/Failure result with `PrintErrorType`
   - `IJobStatusReporter`: Interface for reporting job status updates

3. **PrintServer Layer (Inspectron.Epson.PrintServer namespace)**
   - `PrintLoop`: Orchestrator connecting job sources to print server
   - **JobSources/**: `IPrintJobSource` strategy pattern (SignalRPrintJobSource, SingleJobSource)
   - **PrintServices/**: `IPrintService` implementation (`EpsonPrintService`), `IAssignedPrinterRepository`, `IPrinterConfigurationSource`
   - **ConfigurationSources/**: `EpsonPrintServiceConfiguration`, `FixedConfigurationSource`
   - **Printers/**: `IPrinterFactory` (creates `IEpsonPrinter`), `IWrapperPrinterFactory` / `PrinterWrapperFactory` (creates `IPrinter` adapter by model ID)
   - **Printers/Utils/**: `PrintCommand` model, `IReceiptConverter` / `IReceiptConverterFactory` with receipt type implementations
   - **WorkAreaSources/**: `IWorkAreasSource`, `JamesWorkAreaSource`
   - **DiscoveredPrintersReceiver/**: `IDiscoveredPrintersReceiver`, `JamesDiscoveredPrintersReceiver`

4. **Application Layer (EpsonPrintService)**
   - Production deployment with Ninject DI, SignalR remote jobs, printer discovery, heartbeat, systemd service

### Receipt Conversion System

Receipt content is converted from JSON to `PrintCommand` lists via `IReceiptConverterFactory`:
- **KitchenReceipt** - Kitchen order tickets
- **FinalReceipt** - Final customer receipts
- **InvoiceReceipt** - Invoice receipts
- **BarReceipt** - Bar order tickets
- **OrderItems** - Order item lists

`PrintCommand` properties: Text, IsBig, IsTall, IsBold, IsRed, SetLineSpacing, IsCut

### Printer Model Adapters

`IPrinter` implementations wrap `IEpsonPrinter` with model-specific behavior:
- `TM-T30III` / `TM-T30IIITranslated` - Thermal receipt printer (384px image width)
- `TM-U220II` / `TM-U220IITranslated` - Dot matrix receipt printer (narrower image width)

Created via `IWrapperPrinterFactory.CreatePrinterFromId(modelIdByte, epsonPrinter)`.

### Dependency Injection (Ninject)

```csharp
StandardKernel kernel = new();
kernel.Bind<IPrinterFactory>().To<EpsonPrinterFactory>();
kernel.Bind<IWrapperPrinterFactory>().To<PrinterWrapperFactory>().InSingletonScope();
kernel.Bind<IPrintJobSource, SignalRPrintJobSource>().To<SignalRPrintJobSource>().InSingletonScope();
kernel.Bind<IPrintService>().To<EpsonPrintService>();
kernel.Bind<IPrinterConfigurationSource>().To<FixedConfigurationSource>();
kernel.Bind<IReceiptConverterFactory>().To<ReceiptConverterFactory>().InSingletonScope();
kernel.Bind<IJobStatusReporter>().To<NullJobStatusReporter>();
kernel.Bind<IDiscoveryService>().ToConstant(discovery);
kernel.Bind<IDiscoveredPrintersReceiver>().To<JamesDiscoveredPrintersReceiver>().InSingletonScope();
kernel.Bind<PrintServer>().ToSelf().InSingletonScope();
```

### Key Interfaces and Abstractions

- `IEpsonPrinter`: Low-level printer operations (connect, status, ESC/POS commands, image printing)
- `IPrinter`: Printer model adapter (InitAsync, PrintImageAsync, SetFontSizeAsync, PrintAsync, Cut)
- `IPrintJobSource`: Pluggable job sources (async via Channel<PrintJob>)
- `IPrintService`: Print workflow execution (PrintAsync returns `PrintResult`)
- `IPrinterFactory`: Creates `IEpsonPrinter` instances (EpsonPrinterFactory, HtmlPrinterFactory)
- `IWrapperPrinterFactory`: Creates `IPrinter` adapter from model ID byte
- `IPrinterConfigurationSource`: Provides printer configuration
- `IReceiptConverter` / `IReceiptConverterFactory`: Converts JSON receipt content to `PrintCommand` list
- `IDiscoveryService`: Printer network discovery
- `IDiscoveredPrintersReceiver`: Receives and processes discovered printers
- `IJobStatusReporter`: Reports job status updates

### Print Job Flow

```
PrintLoop.StartAsync()
  -> IPrintJobSource.GetNextJobAsync() [blocks until job available]
  -> PrintServer.SubmitJob(job.IP, job)
    -> PrinterQueue.Enqueue(job) [per-printer queue]
      -> ProcessQueueAsync() [background loop]
        -> IPrintService.PrintAsync(printerIp, job)
          -> IPrinterFactory.CreatePrinter() [creates IEpsonPrinter]
          -> Connect, get printer ID, check status
          -> IWrapperPrinterFactory.CreatePrinterFromId() [creates IPrinter adapter]
          -> Initialize printer, download & cache logo (if URL provided)
          -> IReceiptConverterFactory.Create(receiptType, printerId)
          -> Convert JSON content to PrintCommand list
          -> printerAdapter.PrintAsync(commands), cut paper
          -> Return PrintResult.Ok() or PrintResult.Fail(errorType)
        -> On failure: retry up to 3 times via priority queue
```

### Configuration System

Configuration is loaded from a base64-encoded `config.txt` file containing `apiUrl;restaurantId;apiKey`:

```
# config.txt contains a single base64-encoded line
# Decoded format: apiUrl;restaurantId;apiKey
# Example decoded: https://api.gastrojames.ch;63e37295bd6f26dbd36164c0;yourApiKeyHere
```

`ConfigurationPaths` resolves the config path:
- **Windows**: local `config.txt`
- **Linux**: `/root/epsonprintservice/config.txt`

Printers are registered via `PreconfiguredDiscoveryService` with hardcoded IPs in `Program.cs`, or dynamically via `PrinterDiscoveryBackgroundTask`.

### PrintServer Read/Write Lock

`PrintServer` uses a readers-writer lock pattern to coordinate printing with discovery:
- **Print operations** acquire a read lock (`EnterPrintLock` / `ExitPrintLock`) - multiple prints run concurrently
- **Discovery operations** acquire a write lock (`EnterDiscoveryLockAsync` / `ExitDiscoveryLock`) - blocks until all prints complete, prevents new prints during discovery

### Printer Communication

- **Protocol**: ESC/POS over TCP/IP (port 9100 default)
- **Encoding**: UTF-8 (ESC t 255) or CP852 (ESC t 18) with System.Text.Encoding.CodePages
- **Status Queries**: DLE EOT real-time commands (0x10 0x04 + sub-command byte)
- **Image Printing**: Two modes
  - Raster mode (GS ( L): 384px width for TM-T30III
  - Bit-image mode (ESC *): 8-dot or 24-dot with single/double density
- **Error Handling**: All async operations throw EpsonConnectionException or EpsonCommandException on failure

## Adding New Printer Models

1. Get printer ID byte via `EpsonPrinter.GetPrinterIdAsync()` (GS I 1 command)
2. Create new class implementing `IPrinter` in `Inspectron.Epson/PrintServer/Printers/`
3. Implement required methods: InitAsync, PrintImageAsync, SetFontSizeAsync, PrintAsync, Cut
4. Add case to `PrinterWrapperFactory.CreatePrinterFromId()` with the printer ID byte
5. Add receipt converter support in `ReceiptConverterFactory` if needed
6. Test with actual hardware - different models have different image widths and positioning

## Adding New Receipt Types

1. Create a new folder under `Inspectron.Epson/PrintServer/Printers/Utils/`
2. Add Models.cs with the receipt data model
3. Create a converter implementing `IReceiptConverter` (returns `List<PrintCommand>`)
4. Register the new receipt type in `ReceiptConverterFactory`

## Testing Printer Connectivity

```csharp
// Basic connection test
await using var printer = new EpsonPrinter(logger);
await printer.ConnectAsync("192.168.1.100");
var status = await printer.GetOverallStatusAsync();
Console.WriteLine($"Status: {status.StatusText}, Ready: {status.IsReady}");

// Full diagnostics
var diagnostics = new EpsonDiagnostics(logger);
var report = await diagnostics.RunFullDiagnosticsAsync("192.168.1.100");
Console.WriteLine(report);
```

## Deployment

### Systemd Service (Linux)

EpsonPrintService includes systemd service files (`epson.service`, `print_server.service`):
```bash
# Copy files to deployment directory
cp -r EpsonPrintService/bin/Debug/net8.0/* /home/pi/epson_service/

# Install and start service
sudo cp epson.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable epson.service
sudo systemctl start epson.service

# Check status
sudo systemctl status epson.service
```

Deployment scripts included: `start.sh`, `postinst`, `prem`, `uninstall.sh`.

## Important Implementation Notes

### PrinterQueue Retry Logic

Failed print jobs are automatically retried up to 3 times via priority queue:
- Failed jobs pushed to `ConcurrentStack<PrintJob>` (LIFO)
- Priority stack checked before main queue in `ProcessQueueAsync()`
- RetryCount incremented on each failure
- Jobs with RetryCount >= 3 are discarded

### Image Processing Pipeline

When printing images via `EpsonPrinter.LoadImageAsync()`:
1. Load image with SixLabors.ImageSharp
2. Resize maintaining aspect ratio (max width: printer-specific)
3. Convert to grayscale
4. Apply Floyd-Steinberg dithering for 1-bit black/white conversion
5. Pack pixels to byte-aligned format
6. Generate column format for bit-image mode (ESC * 33)

### Logo Caching

`EpsonPrintService` downloads logos from URLs provided in print jobs:
- Logos are cached locally in a `logos/` directory under the app base directory
- Filename is base64-encoded to avoid path issues
- Subsequent prints skip the download if the cached file exists

### SignalR Job Source

`SignalRPrintJobSource` connects to `{ApiUrl}/hubs/printer-servers`:
- Uses `InfiniteRetryPolicy` with exponential backoff (capped at 30s) for automatic reconnection
- Retries initial connection in a loop on failure
- Joins group by RestaurantId from configuration
- Message: "PrintJob" with { PrinterIp, LogoUrl, ReceiptType, Content }
- Uses Channel<PrintJob> for async producer-consumer pattern

### Font Sizing

Font magnification is 1-8x for both width and height:
- ESC ! command: bits 4-7 control size
- Formula: `(widthMagnifier - 1) << 4 | (heightMagnifier - 1)`
- Example: Size 2 = 0x11 (2x width, 2x height)

## Common Troubleshooting

### "Printer not found" or connection timeout
- Verify IP address with `ping <printer-ip>`
- Check printer is on same network/VLAN
- Ensure port 9100 is not blocked by firewall
- Check `PreconfiguredDiscoveryService` entries in `Program.cs`

### "Cover open" or "Paper end" errors
- Check `OverallStatus.Recommendations` for specific issues
- Common: Cover not fully closed, paper roll empty/misaligned

### Image not printing or garbled
- Verify image width matches printer specification (384px for TM-T30III)
- Check image file path or URL is accessible
- Ensure printer model supports raster graphics (TM-T30III does, older models may not)

### SignalR connection failures
- Check RestaurantId and ApiKey in config.txt (base64-encoded)
- Verify network connectivity to the API URL
- SignalR auto-reconnects with infinite retry; check logs for reconnection attempts
