需求
网页通过 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 命令
配对
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);
return device;
};
发送 ESC/POS 指令
const sendCmd = async (device) => {
const cmd = new Uint8Array([0x1f, 0x1b, 0x1f, 0x67, 0x00]);
const { outEndpoint } = getEndpoint(device);
device.transferOut(outEndpoint, cmd);
};
完整代码
<!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>
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);