Honeywell 打印机(ZPL)配置调用超详细教程 -YB

20231214 Honeywell 打印机(ZPL)配置调用超详细教程 -YB

–noted by yuanbao
在线笔记地址:https://note.youdao.com/s/9iwQsXJi

一、概述:

1.1 打印机介绍:

image-20231221200943001

规格参数:

image-20231221200958471

image-20231221201040348

1.2 打印机设置方式:

  • 网络打印机:网线插到打印机上。

  • 共享打印机:电脑与打印机通过 USB 连接,电脑将打印机共享到网络中,电脑不能关机。

1.3 传动需求场景:

传动现场环境是:现场一个下线点(含多个工位)配置一台工控机和一台打印机,工控机和打印机分别通过网线接入生产网络中。通过过点信息(自动)和手动补打的方式调用打印,也就是需要做到某工位群 A 在这台打印机 P1,某工位群 B 在另一台打印机 P2。最终会有一百多台打印机,能实现在对应工位打印条码。

解决办法:将每个打印机配置成网络打印机,之后配置到 MES 应用服务器上,MES 在调用时需确定使用哪台打印机,通过维护工位和打印机的关联关系(考虑在自定义档案中),这样在一条业务数据中就能直接匹配到对应打印机了,通过网络打印完成条码打印。

1.4 核心思路:

1. 安装打印机驱动;
2. 配置打印机端口,使得电脑可直接调通打印机(控制面板中有该打印机且可打印测试页);
3. 启动打印微服务,通过传参 zpl 指令调用打印机完成打印;
4. 集成到 MES 中,MES 配置打印模板,完成参数替换后调打印服务完成打印。

1.5 打印机服务设置网页:

浏览器访问https:// 打印机 ip

image-20231221201052070

二、配置打印机:

2.1 驱动安装

资源包内容:

image-20231221201115901

tips:HoneyWell 官方驱动可以,Bartender 网站上下载的 HoneyWell 对应打印机型号的驱动也可以,都行。 运行 exe 会进行解压,生成文件:

image-20231221201126900

运行 DriverWizard.exe 才开始安装驱动,注意选择正确打印机的型号。

image-20231221201133466

image-20231221201152879

image-20231221201203841

根据自己的场景选择,下面演示通过网络的配置方式:选择对应的打印机型号。(如果是 USB 方式会自动匹配出打印机)

image-20231221201210855

image-20231221201221707

image-20231221201232176

2.2 配置 ip 端口

image-20231221201239719

image-20231221201251488

创建端口后,再指定这个端口:

image-20231221201258145

image-20231221201305250

image-20231221201317976

2.3 若配置的端口不对可删除端口重新配置。【非必须】

若无法删除端口,则作如下操作: 运行:services.msc 停掉 Print Spooler 服务后,运行 spool 删除 PRINTERS 文件夹后,再启动 Print Spooler 服务,再对端口删除就可以了。

image-20231221201325719

image-20231221201331821

image-20231221201341178

三、打印操作:

3.1 打印测试页、一般文档或图片打印:

image-20231221201351519

按需设置纸张规格:(这个设置的效果不一定好,可作联通性测试。纸张规格可能会影响最终打印效果,以实际为准)

image-20231221201456497

至此,说明我们的电脑与打印机已是可连通状态。 如果不通,自行 ping 一下打印机 ip,是否能通,解决网络问题后再作尝试。

3.2 打印微服务:

a. 资源包:启动微服务。

image-20231221201535478

b. 获取打印列表(浏览器或 Postman):

http://127.0.0.1:8181/print-proxy/

image-20231221201527790

c. 打印本地图片(浏览器或 Postman):

http://localhost:8181/print-proxy/printer/printPicture?filePath=E:/Jordan.jpg&printerName=Honeywell+PX240S+(300+dpi)-NET

d. ZPL 打印:(需用 Postman 使用 Post 请求,参数要求放置在 body 的 form-data 下

image-20231221201547359

另外: 在线调试网址(预览样式):(有时候不一定准确,得看打印机具体适配情况) https://labelary.com/viewer.html

image-20231221201647008

3.3 BarTender 打印:

快速设计,解决临时性打印需求。

image-20231222094349755

四、打印服务集成到 MES

4.1 主要页面效果

V52 的打印模板存在问题,传动项目已修复。

image-20231221201707434

在传动项目中做了一个页面,来做条码打印,操作方式是:传参条码内容和选择打印机。

image-20231221201719429

效果:

image-20231221201853836

打印模板的 ZPL 指令内容:

^XA  
^LH5,20  
^CF0,135,210  
^TBN,870,500  
^FO50,50^FD#barcode#^FS  
^XZ

4.2 核心代码:

a. 前端:
// YB-打印相关方法~~~~
// 获取打印机集合
getPrinters() {
  this.$http.post("ubcPrintController!getServicePrinterList.m").then((res) => {
	this.printersList = res;
	this.printBarForm.printer = this.optionData(res);
  });
},
// 打开打印对话框
goPrintDialog() {
  // this.printBarForm.barCode = "";
  // this.printBarForm.printer = ;

  // 从缓存里取打印机,避免重复操作
  this.printBarForm.printer = window.localStorage.getItem("printerSelected") == null ? "" : window.localStorage.getItem("printerSelected");

  this.isPrintFlag = true;
},
// 关闭打印对话框
doCacel() {
  this.$refs["printBarForm"].resetFields();
  this.isPrintFlag = false;
},
// 提交打印
submitPrint() {
  if (!this.printBarForm.barcode) {
	this.$message({
	  message: "打印条码不能为空!",
	  type: "warning",
	});
	return false;
  }

  // 将需转换打印模板的变量进行封装
  let zplValJson = {
	"barcode": this.printBarForm.barcode,
  };
  let data = qs.stringify({
	printer: this.printBarForm.printer,
	printTemplate: "UBC_TEMPLET_REPORT-20231212-1713", // 此处固定这个打印模板
	zplValJson: JSON.stringify(zplValJson),
  });
  this.$http.post("ubcPrintController!printBarcodeYB.m", data).then((res) => {
	if (res.type == "success") {
	  this.$message({
		message: res.data,
		type: res.type,
	  });
	  // 保存选择的打印机到浏览器缓存,避免下次重复操作
	  window.localStorage.setItem('printerSelected', this.printBarForm.printer);

	  this.isPrintFlag = false;
	  this.doCacel();
	} else {
	  this.$message({
		message: res.data,
		type: res.type,
	  });
	}
  });
},
b. 后端:
/**
 * @MethodName: printBarcodeYB
 * @Description: 打印zpl-调用时传参打印模板编码、打印变量(map结构,非写死,便于后期对打印模板的变量调整)、打印机名称
 * @Param printTemplateCode 打印模板编码
 * @Param zplValJo 打印变量jsonObject
 * @Param printer 打印机名称
 * @Return void
 * @Author: yuanbao
 * @Date: 2023/12/13
 **/
public void printBarcodeYB(String printTemplateCode, JSONObject zplValJo, String printer)
{
	// 1、数据预处理
	JSONArray jsonArray = new JSONArray();
	jsonArray.add(zplValJo);

	List<UbcTempletReport> templetReportList = this.getDao().find(UbcTempletReport.class, new String[]{"code", "isDelete"}, new Object[]{printTemplateCode, UmaConstantUtil.IS_DELETE_NO});
	if (CollectionUtils.isEmpty(templetReportList))
	{
		throw new MestarException("打印模板编码为:" + printTemplateCode + " 未找到对应打印模板,请检查!");
	}
	UbcTempletReport ubcTempletReport = templetReportList.get(0);

	// 2、获取文本换后zplText
	List<Map<String, String>> list = self.getCommonText(ubcTempletReport.getId(), jsonArray);

	// 3、开始打印
	for (Map<String, String> map : list)
	{
		String zplText = map.get("zplText");
		String zpl = zplText.trim();
		if (!(zpl.startsWith("^XA") || zpl.startsWith("~DG")) || !zpl.endsWith("^XZ"))
		{
			throw new MestarException("打印模板编码为:" + printTemplateCode + ",ZPL语句格式不正确,请检查!");
		}

		String returnMsg = ZplPrint(zpl, printer);
		if (!"success".equals(returnMsg))
		{
			throw new MestarException(returnMsg);
		}
	}

}

/**
 * @MethodName: printBarcodeYB
 * @Description: 通过工位,打印zpl-调用时传参打印模板编码、打印变量(map结构,非写死,便于后期对打印模板的变量调整)、工位编码
 * @Param printTemplateCode 打印模板编码
 * @Param zplValJo 打印变量jsonObject
 * @Param cellCode 工位编码
 * @Return void
 * @Author: yuanbao
 * @Date: 2023/12/13
 **/
public void printBarcodeByCellYB(String printTemplateCode, JSONObject zplValJo, String cellCode)
{
	// 1、数据预处理
	JSONArray jsonArray = new JSONArray();
	jsonArray.add(zplValJo);

	List<UbcTempletReport> templetReportList = this.getDao().find(UbcTempletReport.class, new String[] { "code", "isDelete" }, new Object[] { printTemplateCode, UmaConstantUtil.IS_DELETE_NO });
	if (CollectionUtils.isEmpty(templetReportList))
	{
		throw new MestarException("打印模板编码为:" + printTemplateCode + " 未找到对应打印模板,请检查!");
	}
	UbcTempletReport ubcTempletReport = templetReportList.get(0);

	// 2、获取文本换后zplText
	List<Map<String, String>> list = self.getCommonText(ubcTempletReport.getId(), jsonArray);

	// 3、由工位转换得到对应的打印机名称:通过自定义档案
	Map<String, MbbUdiBO> udiMap = udiImpl.getUdiBOMap(IMOMPrjConstants.UDI_TYPE_PRINTER_ADDRESS, 2);
	if (CollectionUtils.isEmpty(udiMap))
	{
		throw new MestarException("打印机列表未找到!自定义项类型中未维护编码为:" + IMOMPrjConstants.UDI_TYPE_PRINTER_ADDRESS + " 的类型,请维护!");
	}

	String hql = "select u from MbbUdi u left join u.udiType ut where ut.code=:udiTypeCode and ut.isDelete=0 and u.shortName like '%" + cellCode + "%' and u.isDelete=0";
	List<MbbUdi> udiList = this.getDao().createQuery(hql).setParameter("udiTypeCode", IMOMPrjConstants.UDI_TYPE_PRINTER_ADDRESS).list();
	if (CollectionUtils.isEmpty(udiList))
	{
		throw new MestarException("打印机未找到!自定义档案中(打印机列表的类型下),未维护工位为:" + cellCode + " 的打印机,请维护!");
	} else if (udiList.size() > 1)
	{
		MestarLogger.error("工位为:" + cellCode + " 的打印机,找到了" + udiList.size() + "台!应注意维护准确,现随机取其中一台!");
	}
	MbbUdi udi = udiList.get(0);
	String printer = udi.getName(); // udi的名称放打印机名称,简称放工位编码拼接

	// 4、开始打印
	for (Map<String, String> map : list)
	{
		String zplText = map.get("zplText");
		String zpl = zplText.trim();
		if (!(zpl.startsWith("^XA") || zpl.startsWith("~DG")) || !zpl.endsWith("^XZ"))
		{
			throw new MestarException("打印模板编码为:" + printTemplateCode + ",ZPL语句格式不正确,请检查!");
		}

		String returnMsg = ZplPrint(zpl, printer);
		if (!"success".equals(returnMsg))
		{
			throw new MestarException(returnMsg);
		}
	}

}

五、待完善部分:

用自定义档案维护打印机名称和工位(多个工位编码逗号拼接,不可重叠)的关联关系。
提供方法,传参:打印模板编码、打印 json 数据(map)、工位编码。
从而准确匹配到对应网络打印机进行打印。

后已实现:

image-20231221204609848

代码:

/**
 * @MethodName: printBarcodeYB
 * @Description: 通过工位,打印zpl-调用时传参打印模板编码、打印变量(map结构,非写死,便于后期对打印模板的变量调整)、工位编码
 * @Param printTemplateCode 打印模板编码
 * @Param zplValJo 打印变量jsonObject
 * @Param cellCode 工位编码
 * @Return void
 * @Author: yuanbao
 * @Date: 2023/12/13
 **/
public void printBarcodeByCellYB(String printTemplateCode, JSONObject zplValJo, String cellCode)
{
	// 1、数据预处理
	JSONArray jsonArray = new JSONArray();
	jsonArray.add(zplValJo);

	List<UbcTempletReport> templetReportList = this.getDao().find(UbcTempletReport.class, new String[] { "code", "isDelete" }, new Object[] { printTemplateCode, UmaConstantUtil.IS_DELETE_NO });
	if (CollectionUtils.isEmpty(templetReportList))
	{
		throw new MestarException("打印模板编码为:" + printTemplateCode + " 未找到对应打印模板,请检查!");
	}
	UbcTempletReport ubcTempletReport = templetReportList.get(0);

	// 2、获取文本换后zplText
	List<Map<String, String>> list = self.getCommonText(ubcTempletReport.getId(), jsonArray);

	// 3、由工位转换得到对应的打印机名称:通过自定义档案
	Map<String, MbbUdiBO> udiMap = udiImpl.getUdiBOMap(IMOMPrjConstants.UDI_TYPE_PRINTER_ADDRESS, 2);
	if (CollectionUtils.isEmpty(udiMap))
	{
		throw new MestarException("打印机列表未找到!自定义项类型中未维护编码为:" + IMOMPrjConstants.UDI_TYPE_PRINTER_ADDRESS + " 的类型,请维护!");
	}

	String hql = "select u from MbbUdi u left join u.udiType ut where ut.code=:udiTypeCode and ut.isDelete=0 and u.shortName like '%" + cellCode + "%' and u.isDelete=0";
	List<MbbUdi> udiList = this.getDao().createQuery(hql).setParameter("udiTypeCode", IMOMPrjConstants.UDI_TYPE_PRINTER_ADDRESS).list();
	if (CollectionUtils.isEmpty(udiList))
	{
		throw new MestarException("打印机未找到!自定义档案中(打印机列表的类型下),未维护工位为:" + cellCode + " 的打印机,请维护!");
	} else if (udiList.size() > 1)
	{
		MestarLogger.error("工位为:" + cellCode + " 的打印机,找到了" + udiList.size() + "台!应注意维护准确,现随机取其中一台!");
	}
	MbbUdi udi = udiList.get(0);
	String printer = udi.getName(); // udi的名称放打印机名称,简称放工位编码拼接

	// 4、开始打印
	for (Map<String, String> map : list)
	{
		String zplText = map.get("zplText");
		String zpl = zplText.trim();
		if (!(zpl.startsWith("^XA") || zpl.startsWith("~DG")) || !zpl.endsWith("^XZ"))
		{
			throw new MestarException("打印模板编码为:" + printTemplateCode + ",ZPL语句格式不正确,请检查!");
		}

		String returnMsg = ZplPrint(zpl, printer);
		if (!"success".equals(returnMsg))
		{
			throw new MestarException(returnMsg);
		}
	}

}

六、调试过程问题

6.1 ZPL 打印机通过打印微服务调用时,报错:

java.lang.IllegalArgumentException: Invalid character found in the request target.
** 问题原因:**url 中带有特殊字符,如花括号冒号 {:^~| 等等。
解决:
1、去除 url 中的特殊字符;
2、不用浏览器请求,改用 post 请求。(使用 PostMan 工具)且将参数信息不要放在 Params 里,而是放在 Body 的 form-data 里。(亲测可用)

image-20231221201926502

3、其他方式:改 tomcat 的配置, 在 conf/catalina.properties 添加或修改:tomcat.util.http.parser.HttpParser.requestTargetAllow=|{} 然后重启即可

6.2 问题:自动换行后有自动补在行末尾的符号。

image-20231221201937999

和厂家沟通后:可能打印机对 ZPL 指令中的 FB 不太行,换成 TB 指令。
命令块:

^XA
^LH5,20
^FO50,50
^CF0,135,210
^TBN,870,500
^FD2400010-PB5C/E-J016^FS
^XZ

效果:

image-20231221201946998

6.3 问题:打印中文乱码问题

YQF- 后端代码对中文作 byte 数组转化处理。要求命令格式满足正则:

\\^FO([0-9.]*),([0-9.]*)(\\^A(.*?)([NRIB]),([0-9.]*),([0-9.]*))\\^FD(.*?)\\^FS

命令块:

^XA
^LH5,20
^CF0,135,210
^TBN,900,500
^FO50,50^A0N,5,5^FD#barcode#^FS
^XZ

这样即可打印中文了。

但又出现换行问题。只能通过排版解决。

^XA
^LH5,20
^CF0,135,210
^TBN,900,500
^FO50,50^A0N,3,5^FD艾普工华^FS
^FO50,150^A0N,7,7^FD杨超^FS
^FO50,350^A0N,3,5^FD15871739924^FS
^XZ

暂时不需要中文,未启用此模板。

另外,HoneyWell 打印机支持在线上传字体到打印机,然后在 zpl 指令中使用该字体即可:(前提先自行上传字体到打印机)

image-20231222101017126

^XA  
^CWA,E:simsun.ttf  
^CI28  
^FO50,300^AA,64,64^FD日期:2023年12月15日^FS  
^XZ

七、其他

7.1 Code 128 条码

也称作条形码。

image-20231221202004894

样式:

image-20231221203622533

ZPL 命令块:

^XA
^FO150,150^BY3
^BCN,200,Y,N,N
^FDbarcode123-TEST^FS^PQ1
^XZ

7.2 QR Code 条码

也称作二维码。

image-20231221202014674

样式:

image-20231221203543397

ZPL 命令块:

^XA
^FO100,100
^BQN,2,10
^FDTEST-ABC123^FS
^XZ