Files
Print_server/CLAUDE.md
2026-01-13 09:06:47 +01:00

9.9 KiB

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

# 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):

StandardKernel kernel = new();
kernel.Bind<IPrintJobSource, SignalRPrintJobSource>().To<SignalRPrintJobSource>().InSingletonScope();
kernel.Bind<IPrintService>().To<EpsonPrintService>();
kernel.Bind<IAssignedPrinterRepository>().ToConstant(config);
kernel.Bind<IPrinterFactory>().To<PrinterFactory>().InSingletonScope();
kernel.Bind<PrintServer>().ToSelf().InSingletonScope();

Microsoft.Extensions.DependencyInjection (ConfigurationPannel ASP.NET):

builder.Services.AddSingleton<UserService>();
builder.Services.AddSingleton<ConfigurationManager>();
builder.Services.AddHostedService<PrintServerHostedService>();

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):

{
  "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

// 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):

# 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:

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<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)

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 <printer-ip>
  • 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