diff --git a/Inspectron.Epson.slnx b/Inspectron.Epson.slnx index 6cb719b..875148b 100644 --- a/Inspectron.Epson.slnx +++ b/Inspectron.Epson.slnx @@ -2,6 +2,7 @@ + diff --git a/Pos895Test/Pos895Test.csproj b/Pos895Test/Pos895Test.csproj new file mode 100644 index 0000000..fef97ee --- /dev/null +++ b/Pos895Test/Pos895Test.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + diff --git a/Pos895Test/Program.cs b/Pos895Test/Program.cs new file mode 100644 index 0000000..87e73d8 --- /dev/null +++ b/Pos895Test/Program.cs @@ -0,0 +1,160 @@ +using System.Text; +using Inspectron.Epson; + +// POS895 smoke test: Font A column count vs. GS ! width magnification. +// +// Prints one ruler row per width magnification, each at 48/width characters, so +// every row should end at the same right margin with progressively larger type. +// A row that stops short means that magnification was ignored. +// +// All settings are the constants below. Edit them here; there are no arguments. + +// An AnyDesk TCP tunnel forwards this local endpoint to the remote printer's +// port 9100, so the printer is reachable as though it sat on this machine. +// A refused connection here usually means the AnyDesk session or its tunnel is +// down, not that the printer is off. +const string PrinterIp = "127.0.0.1"; +const int PrinterPort = 8888; +const int ConnectTimeoutSeconds = 5; + +// Font A is 12 dots wide on a 576-dot line. Width magnification divides this. +const int FontAColumns = 48; +const int HeightMagnification = 2; +int[] widths = { 1, 2, 3 }; + +// Blank lines between the last row and the cut. Literal LF bytes rather than +// ESC d n: LF is the most universally implemented feed in ESC/POS, and this +// firmware appears to ignore ESC d. +const int BlankLinesAfterText = 10; + +// Set true to drop the ESC t 18 (code page 18 / CP852) selection. The ruler is +// pure ASCII either way; this only isolates whether ESC t itself is honoured. +bool useRawAscii = false; + +byte[] job = BuildJob(FontAColumns, widths, HeightMagnification, BlankLinesAfterText, useRawAscii); + +Console.WriteLine($"POS895 smoke test -> {PrinterIp}:{PrinterPort}"); +Console.WriteLine($"Font A, height {HeightMagnification}x, widths {string.Join("x / ", widths)}x"); +Console.WriteLine(); +foreach (int w in widths) +{ + int cols = FontAColumns / w; + Console.WriteLine($" GS ! 0x{SizeByte(w, HeightMagnification):X2} w={w} h={HeightMagnification} {cols,2} cols {Ruler(cols)}"); +} +Console.WriteLine(); +Console.WriteLine("Byte sequence:"); +Console.WriteLine(Describe(job)); +Console.WriteLine(); + +try +{ + await using var printer = new EpsonPrinter(); + + Console.Write("Connecting... "); + await printer.ConnectAsync(PrinterIp, PrinterPort, ConnectTimeoutSeconds); + Console.WriteLine("connected."); + + Console.Write($"Sending {job.Length} bytes... "); + await printer.SendRawCommandAsync(job); + Console.WriteLine("sent."); + + Console.WriteLine(); + Console.WriteLine("[OK] Bytes sent. All three rows should end at the same right"); + Console.WriteLine(" margin, each in larger type than the one above it."); + return 0; +} +catch (EpsonConnectionException ex) +{ + Console.Error.WriteLine(); + Console.Error.WriteLine($"[FAIL] Could not reach {PrinterIp}:{PrinterPort}."); + Console.Error.WriteLine(" Is the port forward to the printer's 9100 up?"); + Console.Error.WriteLine($" {ex.Message}"); + if (ex.InnerException is not null) + { + Console.Error.WriteLine($" {ex.InnerException.Message}"); + } + return 1; +} +catch (Exception ex) +{ + Console.Error.WriteLine(); + Console.Error.WriteLine($"[FAIL] {ex.GetType().Name}: {ex.Message}"); + return 1; +} + +// The whole job as one array, so what gets listed above is exactly what goes on +// the wire. +static byte[] BuildJob(int fontACols, int[] widths, int heightMag, int blankLines, bool rawAscii) +{ + var job = new List { 0x1B, 0x40 }; // ESC @ initialize + + if (!rawAscii) + { + job.AddRange(new byte[] { 0x1B, 0x74, 0x12 }); // ESC t 18 code page 852 + } + + job.AddRange(new byte[] { 0x1B, 0x21, 0x00 }); // ESC ! 0 Font A, no attributes + + foreach (int w in widths) + { + job.AddRange(new byte[] { 0x1D, 0x21, SizeByte(w, heightMag) }); + job.AddRange(Encoding.ASCII.GetBytes(Ruler(fontACols / w))); + job.Add(0x0A); + } + + job.AddRange(new byte[] { 0x1D, 0x21, 0x00 }); // GS ! 0 back to 1x1 + + for (int i = 0; i < blankLines; i++) + { + job.Add(0x0A); // blank line + } + + job.AddRange(new byte[] { 0x1D, 0x56, 0x00 }); // GS V 0 full cut + return job.ToArray(); +} + +// GS ! n: bits 4-6 are width magnification - 1, bits 0-2 height - 1. Same +// formula as EpsonPrinter.SetFontSizeAsync. +static byte SizeByte(int width, int height) => + (byte)(((height - 1) & 0x07) | (((width - 1) & 0x07) << 4)); + +// "|---" repeated and cut to an exact column count, so the pipes fall every +// 4 characters and the row ends precisely at the claimed right margin. +static string Ruler(int columns) +{ + var sb = new StringBuilder(); + while (sb.Length < columns) + { + sb.Append("|---"); + } + + return sb.ToString(0, columns); +} + +// Hex plus a printable-ASCII gutter, so the output can be compared against the +// printer's hex dump mode (open cover, FEED twice within 3 s, close cover). +static string Describe(byte[] bytes) +{ + var sb = new StringBuilder(); + for (int offset = 0; offset < bytes.Length; offset += 16) + { + int count = Math.Min(16, bytes.Length - offset); + sb.Append($" {offset:X4} "); + + for (int i = 0; i < 16; i++) + { + sb.Append(i < count ? $"{bytes[offset + i]:X2} " : " "); + } + + sb.Append(' '); + for (int i = 0; i < count; i++) + { + byte b = bytes[offset + i]; + sb.Append(b >= 0x20 && b < 0x7F ? (char)b : '.'); + } + + sb.AppendLine(); + } + + return sb.ToString().TrimEnd(); +}