通过 WebUSB 驱动打印机打印

需求

网页通过 USB 发送 ESC/POS 指令到打印机。

概念

什么是 WebUSB?

WebUSB 是一个 Web API,它允许网页通过 USB 连接与本地的 USB 设备进行通信。通过 WebUSB,网页可以与各种类型的 USB 设备进行直接交互,而无需通过平台特定的驱动程序或中间件。

使用 WebUSB,开发人员可以创建具有以下功能的网页应用程序:

  • 识别和连接可用的 USB 设备。
  • 与 USB 设备进行数据交换,包括读取和写入设备的数据端点。
  • 监听 USB 设备上的事件,例如设备连接和断开连接。

由于 WebUSB 使用了 USB 设备的通用性标准,因此它可以与各种类型的设备进行通信,例如打印机、扫描仪、键盘、鼠标、游戏控制器等。这使得开发人员可以创建具有更高级别的交互和控制的 Web 应用程序,而无需依赖于特定平台或操作系统。

需要注意的是,为了保护用户安全和隐私,WebUSB 需要用户的明确授权才能访问 USB 设备。用户将通过浏览器的权限提示决定是否允许网页应用程序与指定的 USB 设备进行通信。

什么是 ESC/POS 指令?

ESC/POS(Epson Standard Code for Printers)是一种打印机指令集,由爱普生(Epson)公司创建并广泛使用。ESC/POS 指令集包括一系列控制命令,以控制打印机的各种操作,例如打印文本、绘制条形码、切纸等等。它被应用到广泛的打印机应用程序中,例如收银系统、票据打印、咨询机等等。

ESC/POS 指令集为打印机提供了许多有用和高级的特性。例如,它支持各种字体和字号、颜色、对齐方式、旋转、加粗、下划线、倾斜等功能,以及各种类型的条形码和二维码。此外,ESC/POS 指令集还支持自定义 logo 和图像,以及打印多个副本和自动切纸等功能。

ESC/POS 命令是通过向打印机发送 ASCII 字符串来实现的。对于不同的打印机,其 ESC/POS 指令集可能非常相似,但也存在一些差异。因此,开发人员应该了解所使用打印机的文档以正确使用其 ESC/POS 指令集。

实战

流程:获取设备(配对,或者从已配对的列表中获取)-> 初始化(打开设备,选择配置,声明接口)-> 发送 ESC/POS 命令

配对

js 复制代码
const getDevice = async () => {
  const device = await navigator.usb
    .requestDevice({ filters: [] })
    .then((device) => {
      return device;
    })
    .catch(() => {
      return null;
    });
  return device;
};

初始化设备

js 复制代码
 const initDevice = async (device) => {
  await device.open();
  const { configurationValue, interfaces } = device.configuration;
  await device.selectConfiguration(configurationValue || 0);
  await device.claimInterface(interfaces[0].interfaceNumber || 0);
  return device;
};

发送 ESC/POS 指令

js 复制代码
const sendCmd = async (device) => {
  const cmd = new Uint8Array([0x1f, 0x1b, 0x1f, 0x67, 0x00]);
  const { outEndpoint } = getEndpoint(device);
  device.transferOut(outEndpoint, cmd);
};

完整代码

html 复制代码
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>WebUSB 驱动打印机打印</title>
</head>
<body>
  <button id="connectBtn">连接</button>
  <button id="sendBtn">打印自检页</button>
  <script src="./usb.js"></script>
</body>
</html>
js 复制代码
let selectedDevice; // 当前选择的设备

const getEndpoint = (device) => {
  let inEndpoint = undefined;
  let outEndpoint = undefined;

  for (const { alternates } of device.configuration.interfaces) {
    const alternate = alternates[0];
    const USB_PRINTER_CLASS = 7;
    if (alternate.interfaceClass !== USB_PRINTER_CLASS) {
      continue;
    }

    for (const endpoint of alternate.endpoints) {
      if (endpoint.type !== "bulk") {
        continue;
      }

      if (endpoint.direction === "in") {
        inEndpoint = endpoint.endpointNumber;
      } else if (endpoint.direction === "out") {
        outEndpoint = endpoint.endpointNumber;
      }
    }
  }

  return {
    inEndpoint,
    outEndpoint,
  };
};

const connect = async () => {
  let device = await getDevice();
  if (!device) {
    return;
  }
  selectedDevice = device;
  await initDevice(device);
};

const getDevice = async () => {
  const device = await navigator.usb
    .requestDevice({ filters: [] })
    .then((device) => {
      return device;
    })
    .catch(() => {
      return null;
    });
  return device;
};

const initDevice = async (device) => {
  await device.open();
  const { configurationValue, interfaces } = device.configuration;
  await device.selectConfiguration(configurationValue || 0);
  await device.claimInterface(interfaces[0].interfaceNumber || 0);
};

const sendCmd = async () => {
  if (!selectedDevice) {
    console.warn("请先配对设备");
    return;
  }
  const cmd = new Uint8Array([0x1f, 0x1b, 0x1f, 0x67, 0x00]);
  const { outEndpoint } = getEndpoint(selectedDevice);
  selectedDevice.transferOut(outEndpoint, cmd);
};

const init = () => {
  navigator.usb.addEventListener("connect", (e) => {
    console.log("新连上的设备", e.device);
  });

  navigator.usb.addEventListener("disconnect", (e) => {
    console.log("断开的设备", e.device);
  });

  const connectBtn = document.querySelector("#connectBtn");
  connectBtn.addEventListener("click", connect);

  const sendBtn = document.querySelector("#sendBtn");
  sendBtn.addEventListener("click", sendCmd);
};

document.addEventListener("DOMContentLoaded", init);
相关推荐
王哈哈^_^1 小时前
【数据集】【YOLO】【目标检测】交通事故识别数据集 8939 张,YOLO道路事故目标检测实战训练教程!
前端·人工智能·深度学习·yolo·目标检测·计算机视觉·pyqt
cs_dn_Jie2 小时前
钉钉 H5 微应用 手机端调试
前端·javascript·vue.js·vue·钉钉
开心工作室_kaic2 小时前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿2 小时前
webWorker基本用法
前端·javascript·vue.js
cy玩具3 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161774 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test4 小时前
js下载excel示例demo
前端·javascript·excel
Yaml44 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事4 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶4 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json