Ver código fonte

feat: CSN SDK x64 DLL直连USB打印——EnumUSB+OpenUSBIO+Pos_Text+Pos_Qrcode

lq 1 mês atrás
pai
commit
eda5116404

+ 124 - 0
internal/modules/printer/service/csn_dll.go

@@ -0,0 +1,124 @@
+package service
+
+import (
+	"fmt"
+	"syscall"
+	"unsafe"
+)
+
+// CSN Printer SDK — Go wrapper for CsnPrinterLibs.dll (x64)
+
+var csnDll *syscall.LazyDLL
+
+func initCSN(dllPath string) error {
+	csnDll = syscall.NewLazyDLL(dllPath)
+	// Test load
+	if err := csnDll.Load(); err != nil {
+		return fmt.Errorf("加载 CsnPrinterLibs.dll 失败: %w", err)
+	}
+	return nil
+}
+
+// === Port functions ===
+
+func csn_EnumUSB() string {
+	if csnDll == nil {
+		return ""
+	}
+	buf := make([]byte, 256)
+	ret, _, _ := csnDll.NewProc("Port_EnumUSB").Call(
+		uintptr(unsafe.Pointer(&buf[0])), uintptr(len(buf)))
+	if ret == 0 {
+		return ""
+	}
+	return string(buf[:ret])
+}
+
+func csn_OpenUSB(name string) (uintptr, error) {
+	if csnDll == nil {
+		return 0, fmt.Errorf("DLL 未加载")
+	}
+	nameBytes := append([]byte(name), 0)
+	ret, _, _ := csnDll.NewProc("Port_OpenUSBIO").Call(
+		uintptr(unsafe.Pointer(&nameBytes[0])))
+	if ret == 0 {
+		return 0, fmt.Errorf("打开 USB 打印机失败: %s", name)
+	}
+	return ret, nil
+}
+
+func csn_Close(handle uintptr) {
+	if csnDll != nil && handle != 0 {
+		csnDll.NewProc("Port_ClosePort").Call(handle)
+	}
+}
+
+// === Print functions ===
+
+func csn_Reset() {
+	if csnDll != nil {
+		csnDll.NewProc("Pos_Reset").Call()
+	}
+}
+
+func csn_FeedLine() {
+	if csnDll != nil {
+		csnDll.NewProc("Pos_FeedLine").Call()
+	}
+}
+
+func csn_FeedLines(n int) {
+	if csnDll != nil {
+		csnDll.NewProc("Pos_Feed_N_Line").Call(uintptr(n))
+	}
+}
+
+func csn_Align(value int) { // 0=左, 1=中, 2=右
+	if csnDll != nil {
+		csnDll.NewProc("Pos_Align").Call(uintptr(value))
+	}
+}
+
+func csn_FullCut() {
+	if csnDll != nil {
+		csnDll.NewProc("Pos_FullCutPaper").Call()
+	}
+}
+
+func csn_Text(text string, widthTimes, heightTimes, fontType, fontStyle int) {
+	if csnDll == nil {
+		return
+	}
+	// Pos_Text(const wchar_t *prnText, int nLan, int nOrgx, int nWidthTimes, int nHeightTimes, int FontType, int nFontStyle)
+	wstr, _ := syscall.UTF16FromString(text)
+	csnDll.NewProc("Pos_Text").Call(
+		uintptr(unsafe.Pointer(&wstr[0])),
+		0, // nLan: 0=default
+		0, // nOrgx
+		uintptr(widthTimes),
+		uintptr(heightTimes),
+		uintptr(fontType),
+		uintptr(fontStyle))
+	csn_FeedLine()
+}
+
+func csn_QRCode(data string, width int) {
+	if csnDll == nil {
+		return
+	}
+	wstr, _ := syscall.UTF16FromString(data)
+	csnDll.NewProc("Pos_Qrcode").Call(
+		uintptr(unsafe.Pointer(&wstr[0])),
+		uintptr(width), 0, 4) // nVersion=0(auto), nErrLevel=4
+}
+
+func csn_Barcode(data string, barcodeType int, unitWidth, unitHeight, fontStyle, fontPos int) {
+	if csnDll == nil {
+		return
+	}
+	dataBytes := append([]byte(data), 0)
+	csnDll.NewProc("Pos_Barcode").Call(
+		uintptr(unsafe.Pointer(&dataBytes[0])),
+		uintptr(barcodeType), 0, uintptr(unitWidth), uintptr(unitHeight),
+		uintptr(fontStyle), uintptr(fontPos))
+}

+ 70 - 15
internal/modules/printer/service/service.go

@@ -7,6 +7,9 @@ import (
 	"wails-app/internal/global"
 )
 
+// EMD807 CSN SDK DLL 路径 (x64)
+const sdkDllPath = `D:\lq\资料\停车场 道闸 车牌识别 收费机\票机自制\打印机\EMD807嵌入式热敏票据打印模组\5、SDK指令集\Win_SDK_Demo_2023_08_14\Win_SDK_Demo_2023_08_14\lib\x64\CsnPrinterLibs.dll`
+
 type PrintService struct{}
 
 func NewPrintService() *PrintService {
@@ -23,15 +26,34 @@ func (s *PrintService) PrintTicket(printerID uint, lotName, channelCode, plateNo
 	// 根据 port_name 判断连接类型
 	var err error
 	var srl *SerialPrinter
-	write := func(data []byte) {}
+	var usbHandle uintptr
+	useCSN := false
 
 	if len(printer.PortName) >= 3 && printer.PortName[:3] == "COM" {
 		srl, err = NewSerialPrinter(printer.PortName, printer.BaudRate)
-		if err == nil { defer srl.Close(); write = func(data []byte) { srl.Write(data) } }
+		if err == nil {
+			defer srl.Close()
+		}
 	} else {
-		// USB/Windows 打印机 → COPY /B 到共享路径
-		write = func(data []byte) {
-			err = copyToPrinter(printer.Name, data)
+		// USB → 使用 CSN SDK 直连
+		err = initCSN(sdkDllPath)
+		if err == nil {
+			name := printer.PortName
+			// 如果配置的是 "USB" 或 "auto",自动枚举
+			if name == "USB" || name == "auto" || name == "" {
+				name = csn_EnumUSB()
+				if name == "" {
+					name = printer.PortName
+				}
+			}
+			usbHandle, err = csn_OpenUSB(name)
+			if err == nil {
+				useCSN = true
+				defer csn_Close(usbHandle)
+			}
+		}
+		if !useCSN {
+			err = fmt.Errorf("无法连接打印机: %s", printer.PortName)
 		}
 	}
 	if err != nil {
@@ -42,9 +64,47 @@ func (s *PrintService) PrintTicket(printerID uint, lotName, channelCode, plateNo
 	now := time.Now().Format("02-01-2006 15:04:05")
 	slipNo := fmt.Sprintf("%d", ticketID)
 
-	// ── 打印入场小票 (79.5mm纸, Font A 32字/行) ──
-	// 顶部分割线
-	line32 := "================================" // 32 chars
+	// ── CSN SDK 模式 ──
+	if useCSN {
+		csn_Reset()
+		csn_Align(1) // 居中
+		csn_Text("PARKING TICKET", 2, 2, 0, 0)
+		csn_Text("", 1, 1, 0, 0)
+		csn_Text(lotName, 1, 1, 0, 0)
+		csn_Text("A-General Car", 1, 1, 0, 0)
+		csn_Text("", 1, 1, 0, 0)
+		csn_Text("PARK AT YOUR OWN RISK", 1, 1, 0, 1)
+		csn_Text("================================", 1, 1, 0, 0)
+
+		csn_Align(0) // 左对齐
+		csn_Text("In-Time::"+now, 1, 1, 0, 0)
+		if channelCode != "" {
+			csn_Text("InGate::"+channelCode, 1, 1, 0, 0)
+		}
+		if plateNo != "" {
+			csn_Text("Veh No::"+plateNo, 1, 1, 0, 0)
+		}
+		csn_Text("================================", 1, 1, 0, 0)
+		csn_Text("Slip No::"+slipNo, 1, 1, 0, 0)
+		csn_Text("================================", 1, 1, 0, 0)
+
+		csn_Align(1) // 居中
+		csn_FeedLine()
+		csn_QRCode(ticketNo, 4)
+		csn_FeedLine()
+
+		csn_Text("SCAN AND PAY WITH", 1, 1, 0, 0)
+		csn_Text("NEW THE CANADIA BANK APP", 1, 1, 0, 0)
+		csn_FeedLines(3)
+		csn_FullCut()
+
+		global.GVA_DB.Model(&printer).Update("status", "online")
+		return nil
+	}
+
+	// ── 串口模式(原有 ESC/POS)──
+	write := func(data []byte) { srl.Write(data) }
+	line32 := "================================"
 
 	write(InitCmd)
 	write(AlignCenterCmd)
@@ -56,7 +116,7 @@ func (s *PrintService) PrintTicket(printerID uint, lotName, channelCode, plateNo
 	write(ESCPOS_Text(""))
 	write(ESCPOS_Text(lotName))
 	if len(lotName) > 32 {
-		write(FontBCmd) // 长名称切 Font B 小字体
+		write(FontBCmd)
 		write(AlignCenterCmd)
 		write(ESCPOS_Text(lotName))
 		write(NormalCmd)
@@ -65,7 +125,6 @@ func (s *PrintService) PrintTicket(printerID uint, lotName, channelCode, plateNo
 	write(ESCPOS_Text(""))
 	write(ESCPOS_Text("PARK AT YOUR OWN RISK"))
 	write(ESCPOS_Text(line32))
-
 	write(AlignLeftCmd)
 	write(ESCPOS_Text("In-Time::" + now))
 	if channelCode != "" {
@@ -77,17 +136,13 @@ func (s *PrintService) PrintTicket(printerID uint, lotName, channelCode, plateNo
 	write(ESCPOS_Text(line32))
 	write(ESCPOS_Text("Slip No::" + slipNo))
 	write(ESCPOS_Text(line32))
-
 	write(AlignCenterCmd)
-	write(ESCPOS_Text("")) // 空行:二维码前留白
+	write(ESCPOS_Text(""))
 	write(ESCPOS_QRCode(ticketNo))
 	write(ESCPOS_Text(""))
-
 	write(AlignCenterCmd)
 	write(ESCPOS_Text("SCAN AND PAY WITH"))
 	write(ESCPOS_Text("NEW THE CANADIA BANK APP"))
-
-	// 纸尾留白 + 切纸
 	write(InitCmd)
 	write(ESCPOS_Text(""))
 	write(ESCPOS_Text(""))