# 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 TM-m30III thermal receipt printer integration. The solution consists of a core SDK library (Inspectron.Epson) and multiple applications for production printing, configuration, testing, and discovery. ## Solution Structure ### Core Projects - **Inspectron.Epson** - Core SDK library providing printer communication, status monitoring, and ESC/POS commands - **EpsonPrintService** - Production console application with SignalR-based remote job source and systemd service support - **ConfigurationPannel** - ASP.NET Razor Pages web application for printer configuration with embedded print server - **Discovery** - Printer network discovery utilities (SLP, ENPC, mDNS) - **EpsonTest** - Test project for SDK functionality - **TestClient** - SDK usage examples and testing - **SendPrintJob** - Direct print job submission utility ## 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 dotnet build ConfigurationPannel/ConfigurationPannel.csproj # Run projects dotnet run --project EpsonPrintService/EpsonPrintService.csproj dotnet run --project ConfigurationPannel/ConfigurationPannel.csproj # Run tests dotnet test EpsonTest/EpsonTest.csproj # Clean build artifacts dotnet clean ``` ## Architecture ### Three-Layer Architecture 1. **SDK Layer (Inspectron.Epson)** - `EpsonPrinter`: Main facade for printer operations (TCP/IP, async/await) - `EpsonCommands`: Static ESC/POS command definitions - `EpsonImageConverter`: Image processing with Floyd-Steinberg dithering - `EpsonPrinterDiscovery`: Network printer discovery (SLP, ENPC) - Status classes: PrinterStatus, OfflineStatus, ErrorStatus, PaperSensorStatus, OverallStatus - Exception hierarchy: EpsonPrinterException → EpsonConnectionException, EpsonCommandException 2. **Print Server Layer (PrintServer/ namespace in Inspectron.Epson)** - `PrintServer`: Manager for printer queues, coordinates multiple printers by IP - `PrinterQueue`: Per-printer queue with retry logic (ConcurrentQueue + priority ConcurrentStack) - `PrintLoop`: Orchestrator connecting job sources to print server - `IPrintJobSource`: Strategy pattern for job sources (SignalRPrintJobSource, SingleJobSource, NoOpPrintJobSource) - `IPrintService`: Print workflow orchestration (EpsonPrintService implementation) - `IPrinter`: Printer model abstraction (TM-T30III, TM-U220II) - `PrinterFactory`: Factory pattern for creating printer instances by model ID 3. **Application Layer** - EpsonPrintService: Production deployment with Ninject DI, SignalR remote jobs, systemd service - ConfigurationPannel: ASP.NET Core with Microsoft.Extensions.DI, cookie authentication, hosted service pattern ### Dependency Injection Patterns The codebase uses TWO different DI containers: **Ninject (EpsonPrintService console app):** ```csharp StandardKernel kernel = new(); kernel.Bind().To().InSingletonScope(); kernel.Bind().To(); kernel.Bind().ToConstant(config); kernel.Bind().To().InSingletonScope(); kernel.Bind().ToSelf().InSingletonScope(); ``` **Microsoft.Extensions.DependencyInjection (ConfigurationPannel ASP.NET):** ```csharp builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(); ``` When adding new services, match the DI pattern of the project you're working in. ### Key Interfaces and Abstractions - `IPrintJobSource`: Pluggable job sources (async via Channel) - `IPrintService`: Print workflow execution (PrintAsync returns bool for success/failure) - `IAssignedPrinterRepository`: Maps work area IDs to printer IPs (GetAssignedPrinter) - `IPrinterConfigurationSource`: Provides printer configuration (address, font size, logo) - `IPrinter`: Printer model-specific operations (InitAsync, PrintImageAsync, SetFontSizeAsync, PrintTextAsync, Cut) - `IPrinterFactory`: Creates IPrinter from model ID byte (0x01 = TM-T30III, 0x13 = TM-U220II) ### Print Job Flow ``` PrintLoop.StartAsync() → IPrintJobSource.GetNextJobAsync() [blocks until job available] → IAssignedPrinterRepository.GetAssignedPrinter(job.AreaId) [returns printer IP] → PrintServer.SubmitJob(printerIp, job) → PrinterQueue.Enqueue(job) [per-printer queue] → ProcessQueueAsync() [background loop] → IPrintService.PrintAsync(printerIp, job) → Get configuration, connect to printer, detect model → IPrinterFactory.CreatePrinterFromId() → Initialize printer, print logo (if configured), set font size → Print text content, cut paper → Return true/false for success → On failure: retry up to 3 times via priority queue ``` ### Configuration System **EpsonPrintServiceConfiguration** (config.json): ```json { "GroupId": "67e534f8e86e816689323023", "RestaurantId": "63e37295bd6f26dbd36164c0", "PrinterConfigurations": { "192.168.1.100": { "Address": "192.168.1.100:9100", "FontSize": 2, "LogoFilename": "logo.png", "AreaId": "kitchen" } } } ``` This class implements BOTH `IPrinterConfigurationSource` and `IAssignedPrinterRepository` for dual roles. ### 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, PrintTextAsync, Cut 4. Add case to `PrinterFactory.CreatePrinterFromId()` with the printer ID byte 5. Test with actual hardware - different models have different image widths and positioning ## 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 file (`epson.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 ``` ### ASP.NET Hosted Service ConfigurationPannel runs print server as `IHostedService`: ```bash dotnet run --project ConfigurationPannel/ConfigurationPannel.csproj ``` Access web UI at http://localhost:5000 (default login credentials in users.json). ## Important Implementation Notes ### PrinterQueue Retry Logic Failed print jobs are automatically retried up to 3 times via priority queue: - Failed jobs pushed to `ConcurrentStack` (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) ### SignalR Job Source `SignalRPrintJobSource` connects to `https://api.gastrojames.ch/hubs/internal`: - Authentication: Bearer token from configuration - Group: Joins via GroupId from configuration - Message: "PrintJob" with { WorkingAreaId, Content } - Uses Channel 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 ` - Check printer is on same network/VLAN - Ensure port 9100 is not blocked by firewall - Try printer discovery: run Discovery project ### "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 is accessible - Ensure printer model supports raster graphics (TM-T30III does, older models may not) ### SignalR connection failures - Check GroupId and RestaurantId in config.json - Verify network connectivity to api.gastrojames.ch - Check bearer token expiration ### ConfigurationPannel login issues - Default credentials in users.json (BCrypt hashed) - Cookie expiration: 8 hours with sliding expiration - Authentication uses CookieAuthenticationDefaults scheme