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
-
SDK Layer (Inspectron.Epson)
EpsonPrinter: Main facade for printer operations (TCP/IP, async/await)EpsonCommands: Static ESC/POS command definitionsEpsonImageConverter: Image processing with Floyd-Steinberg ditheringEpsonPrinterDiscovery: Network printer discovery (SLP, ENPC)- Status classes: PrinterStatus, OfflineStatus, ErrorStatus, PaperSensorStatus, OverallStatus
- Exception hierarchy: EpsonPrinterException → EpsonConnectionException, EpsonCommandException
-
Print Server Layer (PrintServer/ namespace in Inspectron.Epson)
PrintServer: Manager for printer queues, coordinates multiple printers by IPPrinterQueue: Per-printer queue with retry logic (ConcurrentQueue + priority ConcurrentStack)PrintLoop: Orchestrator connecting job sources to print serverIPrintJobSource: 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
-
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
- Get printer ID byte via
EpsonPrinter.GetPrinterIdAsync()(GS I 1 command) - Create new class implementing
IPrinterinInspectron.Epson/PrintServer/Printers/ - Implement required methods: InitAsync, PrintImageAsync, SetFontSizeAsync, PrintTextAsync, Cut
- Add case to
PrinterFactory.CreatePrinterFromId()with the printer ID byte - 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():
- Load image with SixLabors.ImageSharp
- Resize maintaining aspect ratio (max width: printer-specific)
- Convert to grayscale
- Apply Floyd-Steinberg dithering for 1-bit black/white conversion
- Pack pixels to byte-aligned format
- 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.Recommendationsfor 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