From 59079872af9fa86df9b4fda6a9f88a4054ce2064 Mon Sep 17 00:00:00 2001 From: Gary-gr9 Date: Thu, 6 Jul 2023 17:16:36 +1200 Subject: [PATCH] Implemented native USB Printing support for Windows (should work for linux as well) with complete printer status report back. No driver install or Virtual Serial Port needed. Choose printer from list of usb devices and send data straight to the printer like Serial or Network printer. Possible fix for Isuues : #186 #220 #223 --- ESCPOS_NET.ConsoleTest/Program.cs | 46 ++++++- ESCPOS_NET/Printers/USBPrinter.cs | 34 +++++ ESCPOS_NET/Utils/DeviceDetails.cs | 18 +++ ESCPOS_NET/Utils/DeviceFinder.cs | 211 ++++++++++++++++++++++++++++++ 4 files changed, 306 insertions(+), 3 deletions(-) create mode 100644 ESCPOS_NET/Printers/USBPrinter.cs create mode 100644 ESCPOS_NET/Utils/DeviceDetails.cs create mode 100644 ESCPOS_NET/Utils/DeviceFinder.cs diff --git a/ESCPOS_NET.ConsoleTest/Program.cs b/ESCPOS_NET.ConsoleTest/Program.cs index 4e6c60c..6d4bb06 100644 --- a/ESCPOS_NET.ConsoleTest/Program.cs +++ b/ESCPOS_NET.ConsoleTest/Program.cs @@ -5,6 +5,7 @@ using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; using System.Threading; +using ESCPOS_NET.Printers; namespace ESCPOS_NET.ConsoleTest { @@ -32,13 +33,15 @@ static void Main(string[] args) Console.WriteLine("1 ) Test Serial Port"); Console.WriteLine("2 ) Test Network Printer"); Console.WriteLine("3 ) Test Samba-Shared Printer"); + Console.WriteLine("4 ) Test USB Printer"); Console.Write("Choice: "); string comPort = ""; string ip; string networkPort; string smbPath; + string usbPort = string.Empty; response = Console.ReadLine(); - var valid = new List { "1", "2", "3" }; + var valid = new List { "1", "2", "3", "4" }; if (!valid.Contains(response)) { response = "1"; @@ -58,8 +61,8 @@ static void Main(string[] args) { comPort = "COM5"; } - } - Console.Write("Baud Rate (enter for default 115200): "); + } + Console.Write("Baud Rate (enter for default 115200): "); if (!int.TryParse(Console.ReadLine(), out var baudRate)) { baudRate = 115200; @@ -100,6 +103,43 @@ static void Main(string[] args) printer = new SambaPrinter(tempFileBasePath: @"C:\Temp", filePath: smbPath); } + else if (choice == 4) + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + var usboptions = DeviceFinder.GetDevices();//gets the usb devices connected to the pc + if (usboptions.Count > 0) + { + int i = 0; + int num = 1; + while (i < usboptions.Count) + { + + Console.WriteLine($"{i + num}. Name: {usboptions[i].BusName} S/N: {usboptions[i].SerialNum}"); + i++; + //serial number and name for printer. Name reported might just be USB Printing Support or something generic + //the property necessary for printing is Device Path this is just for UI + } + Console.Write("Choose Printer (eg. 1): "); + string c = Console.ReadLine(); + if (int.TryParse(c, out int chosen) && chosen > 0) + { + usbPort = usboptions[chosen - 1].DevicePath; + } + } + printer = new USBPrinter(usbPort); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + Console.Write("File / usb path (eg. /dev/usb/lp0): "); + usbPort = Console.ReadLine(); + if (string.IsNullOrWhiteSpace(usbPort)) + { + comPort = "/dev/usb/lp0"; + } + printer = new FilePrinter(filePath: usbPort, false); + } + } bool monitor = false; Thread.Sleep(500); diff --git a/ESCPOS_NET/Printers/USBPrinter.cs b/ESCPOS_NET/Printers/USBPrinter.cs new file mode 100644 index 0000000..ccd8f13 --- /dev/null +++ b/ESCPOS_NET/Printers/USBPrinter.cs @@ -0,0 +1,34 @@ +using System.IO; + +namespace ESCPOS_NET.Printers +{ + public class USBPrinter : BasePrinter + { + private readonly FileStream _rfile; + private readonly FileStream _wfile; + public USBPrinter(string usbPath) + : base() + { + //keeping separate file streams performs better + //while using 1 file stream printers were having intermittent issues while printing + //your milege may vary + _rfile = File.Open(usbPath, FileMode.Open, FileAccess.Read); + _wfile = File.Open(usbPath, FileMode.Open, FileAccess.Write); + Writer = new BinaryWriter(_wfile); + Reader = new BinaryReader(_rfile); + } + + ~USBPrinter() + { + Dispose(false); + } + + protected override void OverridableDispose() + { + _rfile?.Close(); + _rfile?.Dispose(); + _wfile?.Close(); + _wfile?.Dispose(); + } + } +} diff --git a/ESCPOS_NET/Utils/DeviceDetails.cs b/ESCPOS_NET/Utils/DeviceDetails.cs new file mode 100644 index 0000000..84762f0 --- /dev/null +++ b/ESCPOS_NET/Utils/DeviceDetails.cs @@ -0,0 +1,18 @@ + +namespace ESCPOS_NET +{ + public class DeviceDetails + { + public string DisplayName { get; set; } + /// + /// DEVPKEY_Device_BusReportedDeviceDesc see reference + /// + public string BusName { get; set; } + public string SerialNum { get; set; } + public string DeviceDescription { get; set; } + public string DevicePath { get; set; } + public string Manufacturer { get; set; } + public ushort VID { get; set; } + public ushort PID { get; set; } + } +} diff --git a/ESCPOS_NET/Utils/DeviceFinder.cs b/ESCPOS_NET/Utils/DeviceFinder.cs new file mode 100644 index 0000000..97cd48a --- /dev/null +++ b/ESCPOS_NET/Utils/DeviceFinder.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Text; +using System.Text.RegularExpressions; + +namespace ESCPOS_NET +{ + public class DeviceFinder + { + #region Win API + private struct SP_DEVICE_INTERFACE_DATA + { + internal int cbSize; + + internal Guid InterfaceClassGuid; + + internal int Flags; + + internal IntPtr Reserved; + } + private struct SP_DEVINFO_DATA + { + internal int cbSize; + + internal Guid ClassGuid; + + internal int DevInst; + + internal IntPtr Reserved; + } + [StructLayout(LayoutKind.Sequential, Pack = 1)] + private struct DEVPROPKEY + { + public Guid fmtid; + public uint pid; + } + private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); + [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern IntPtr SetupDiGetClassDevs(ref Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, int Flags); + [DllImport("setupapi.dll", SetLastError = true)] + private static extern bool SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData, ref Guid InterfaceClassGuid, int MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData); + [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, int DeviceInterfaceDetailDataSize, ref int RequiredSize, IntPtr DeviceInfoData); + [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, int DeviceInterfaceDetailDataSize, ref int RequiredSize, ref SP_DEVINFO_DATA DeviceInfoData); + [DllImport("setupapi.dll", CharSet = CharSet.Unicode, SetLastError = true)] + private static extern bool SetupDiGetDeviceProperty(IntPtr deviceInfoSet, ref SP_DEVINFO_DATA DeviceInfoData, ref DEVPROPKEY propertyKey, out uint propertyType, IntPtr propertyBuffer, uint propertyBufferSize, out uint requiredSize, uint flags); + [DllImport("setupapi.dll", SetLastError = true)] + private static extern int SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet); + private static void DEFINE_DEVPROPKEY(out DEVPROPKEY key, uint l, ushort w1, ushort w2, byte b1, byte b2, byte b3, byte b4, byte b5, byte b6, byte b7, byte b8, uint pid) + { + key.fmtid = new Guid(l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8); + key.pid = pid; + } + #endregion + #region Device Methods + public static List GetDevices() + { + //https://learn.microsoft.com/en-us/windows-hardware/drivers/install/guid-devinterface-usb-device + //USB devices: a5dcbf10-6530-11d2-901f-00c04fb951ed + //Bluetooth devices: 00f40965-e89d-4487-9890-87c3abb211f4 + var devices = GetDevicesbyClassID("a5dcbf10-6530-11d2-901f-00c04fb951ed"); + return devices; + } + private static List GetDevicesbyClassID(string classguid) + { + IntPtr intPtr = IntPtr.Zero; + var devices = new List(); + try + { + Guid guid = new(classguid); + intPtr = SetupDiGetClassDevs(ref guid, IntPtr.Zero, IntPtr.Zero, 18); + if (intPtr == INVALID_HANDLE_VALUE) + { + Win32Exception("Failed to enumerate devices."); + } + + int num = 0; + while (true) + { + SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = default; + DeviceInterfaceData.cbSize = Marshal.SizeOf((object)DeviceInterfaceData); + if (!SetupDiEnumDeviceInterfaces(intPtr, IntPtr.Zero, ref guid, num, ref DeviceInterfaceData)) + { + break; + } + + int RequiredSize = 0; + if (!SetupDiGetDeviceInterfaceDetail(intPtr, ref DeviceInterfaceData, IntPtr.Zero, 0, ref RequiredSize, IntPtr.Zero) && Marshal.GetLastWin32Error() != 122) + { + Win32Exception("Failed to get interface details buffer size."); + } + + IntPtr intPtr2 = IntPtr.Zero; + try + { + intPtr2 = Marshal.AllocHGlobal(RequiredSize); + Marshal.WriteInt32(intPtr2, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8); + SP_DEVINFO_DATA DeviceInfoData = default; + DeviceInfoData.cbSize = Marshal.SizeOf((object)DeviceInfoData); + if (!SetupDiGetDeviceInterfaceDetail(intPtr, ref DeviceInterfaceData, intPtr2, RequiredSize, ref RequiredSize, ref DeviceInfoData)) + { + Win32Exception("Failed to get device interface details."); + } + string path = Marshal.PtrToStringUni(new IntPtr(intPtr2.ToInt64() + 4)); + + DeviceDetails deviceDetails = GetDeviceDetails(path, intPtr, DeviceInfoData); + devices.Add(deviceDetails); + } + finally + { + if (intPtr2 != IntPtr.Zero) + { + Marshal.FreeHGlobal(intPtr2); + intPtr2 = IntPtr.Zero; + } + } + num++; + } + if (Marshal.GetLastWin32Error() != 259) + { + Win32Exception("Failed to get device interface."); + } + } + finally + { + if (intPtr != IntPtr.Zero && intPtr != INVALID_HANDLE_VALUE) + { + SetupDiDestroyDeviceInfoList(intPtr); + } + } + return devices; + } + private static DeviceDetails GetDeviceDetails(string devicePath, IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData) + { + DeviceDetails result = new DeviceDetails + { + DevicePath = devicePath + }; + if (!string.IsNullOrWhiteSpace(devicePath) && devicePath.Contains("#")) + { + var spserial = devicePath.Split('#'); + result.SerialNum = spserial[spserial.Length - 2]; + //last in array is guid and last second is the serial number + //serial number might not be the actual serial number for the device it may be system generated + } + DEFINE_DEVPROPKEY(out DEVPROPKEY key, 0x540b947e, 0x8b40, 0x45bc, 0xa8, 0xa2, 0x6a, 0x0b, 0x89, 0x4c, 0xbd, 0xa2, 4); + result.BusName = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key)[0]; + DEFINE_DEVPROPKEY(out key, 0xb725f130, 0x47ef, 0x101a, 0xa5, 0xf1, 0x02, 0x60, 0x8c, 0x9e, 0xeb, 0xac, 10); + result.DisplayName = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key)[0]; + DEFINE_DEVPROPKEY(out key, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 2); + result.DeviceDescription = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key)[0]; + DEFINE_DEVPROPKEY(out key, 0xa45c254e, 0xdf1c, 0x4efd, 0x80, 0x20, 0x67, 0xd1, 0x46, 0xa8, 0x50, 0xe0, 13); + result.Manufacturer = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key)[0]; + DEFINE_DEVPROPKEY(out key, 0x78c34fc8, 0x104a, 0x4aca, 0x9e, 0xa4, 0x52, 0x4d, 0x52, 0x99, 0x6e, 0x57, 256); + string[] multiStringProperty = GetPropertyForDevice(deviceInfoSet, deviceInfoData, key); + Regex regex = new Regex("VID_([0-9A-F]{4})&PID_([0-9A-F]{4})", RegexOptions.IgnoreCase); + bool flag = false; + string[] array = multiStringProperty; + foreach (string input in array) + { + Match match = regex.Match(input); + if (match.Success) + { + result.VID = ushort.Parse(match.Groups[1].Value, NumberStyles.AllowHexSpecifier); + result.PID = ushort.Parse(match.Groups[2].Value, NumberStyles.AllowHexSpecifier); + flag = true; + break; + } + } + + if (!flag) + { + Win32Exception("Failed to find VID and PID for USB device. No hardware ID could be parsed."); + } + + return result; + } + private static string[] GetPropertyForDevice(IntPtr deviceInfoSet, SP_DEVINFO_DATA deviceInfoData, DEVPROPKEY key) + { + IntPtr buffer = IntPtr.Zero; + try + { + uint buflen = 512; + buffer = Marshal.AllocHGlobal((int)buflen); + if (!SetupDiGetDeviceProperty(deviceInfoSet, ref deviceInfoData, ref key, out uint proptype, buffer, buflen, out uint outsize, 0)) + { + Win32Exception("Failed to get property for device"); + } + byte[] lbuffer = new byte[outsize]; + Marshal.Copy(buffer, lbuffer, 0, (int)outsize); + var val = Encoding.Unicode.GetString(lbuffer); + var aval = val.Split('\0'); + return aval; + } + finally + { + if (buffer != IntPtr.Zero) + Marshal.FreeHGlobal(buffer); + } + } + private static void Win32Exception(string message) + { + throw new Exception(message, new Win32Exception(Marshal.GetLastWin32Error())); + } + #endregion + } +}