259 lines
9.9 KiB
Markdown
259 lines
9.9 KiB
Markdown
# 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<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):**
|
|
```csharp
|
|
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<PrintJob>)
|
|
- `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<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<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
|
|
- 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
|