跳转到内容

OpenHarmony SDK

OpenHarmony SDK 适用于 HarmonyOS NEXT 和 OpenHarmony 应用开发,通过蓝牙(经典蓝牙/BLE)、Wi-Fi、USB 等方式连接打印机设备。

所有包均发布至 OHPM,以 @psdk/ 为前缀。

oh-package.json5 中添加依赖:

{
"dependencies": {
"@psdk/frame-father": "^0.7.6",
"@psdk/cpcl": "^0.7.6",
"@psdk/tspl": "^0.7.6",
"@psdk/esc": "^0.7.6",
"@psdk/ohos-bluetooth-le": "^0.7.6",
"@psdk/ohos-bluetooth-classic": "^0.7.6",
"@psdk/ohos-network": "^0.7.6",
"@psdk/ohos-usb": "^0.7.6"
}
}

然后执行安装:

Terminal window
ohpm install
包名说明
@psdk/frame-father核心框架
@psdk/frame-imageb图片处理
包名说明
@psdk/cpclCPCL 指令
@psdk/escESC 指令
@psdk/tsplTSPL 指令
包名说明
@psdk/ohos-bluetooth-le低功耗蓝牙 (BLE)
@psdk/ohos-bluetooth-classic经典蓝牙
@psdk/ohos-bluetooth-classic-raw经典蓝牙原始模式
@psdk/ohos-networkWi-Fi 网络
@psdk/ohos-usbUSB 连接
@psdk/device-bluetooth-traits蓝牙设备特征

module.json5 中添加所需权限:

{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.ACCESS_BLUETOOTH"
},
{
"name": "ohos.permission.DISCOVER_BLUETOOTH"
},
{
"name": "ohos.permission.MANAGE_BLUETOOTH"
},
{
"name": "ohos.permission.APPROXIMATELY_LOCATION"
}
]
}
}
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
}
}
import { OhosBluetoothLe } from '@psdk/ohos-bluetooth-le';
import { ConnectedDevice, Lifecycle } from '@psdk/frame-father';
import { CPCL, GenericCPCL } from '@psdk/cpcl';
import { JluetoothDevice } from '@psdk/device-bluetooth-traits';
import ble from '@ohos.bluetooth.ble';
private ohosBluetoothLe = new OhosBluetoothLe({
allowNoName: false,
allowedWriteCharacteristic: '49535343-8841-43F4-A8D4-ECBE34729BB3',
allowedReadCharacteristic: '49535343-1e4d-4bd9-ba61-23c647249616'
});
@State discoveredDevices: Array<JluetoothDevice<ble.ScanResult>> = [];
aboutToAppear() {
this.ohosBluetoothLe.discovered((devices) => {
devices.forEach(device => {
const isDuplicate = this.discoveredDevices.find(
item => item.deviceId === device.deviceId
);
if (!isDuplicate) {
this.discoveredDevices.push(device);
}
});
return Promise.resolve();
});
}
async discovery() {
this.discoveredDevices = [];
try {
await this.ohosBluetoothLe.startDiscovery();
} catch (err) {
console.error('Discovery error:', err);
}
}
@State connectedDevice?: ConnectedDevice = undefined;
async onConnect(device: JluetoothDevice<ble.ScanResult>) {
try {
this.connectedDevice = await this.ohosBluetoothLe.connect(device);
console.log('Connected:', this.connectedDevice.deviceName());
} catch (err) {
console.error('Connection error:', err);
}
}
async onDisconnect() {
if (this.connectedDevice) {
this.connectedDevice.disconnect();
this.connectedDevice = undefined;
}
}
import { OhosBluetoothClassic } from '@psdk/ohos-bluetooth-classic';
import { ConnectedDevice } from '@psdk/frame-father';
import { JluetoothDevice } from '@psdk/device-bluetooth-traits';
private ohosBluetoothClassic = new OhosBluetoothClassic({
allowNoName: false
});
@State discoveredDevices: Array<JluetoothDevice<string>> = [];
aboutToAppear() {
this.ohosBluetoothClassic.discovered((devices) => {
devices.forEach(device => {
const isDuplicate = this.discoveredDevices.find(
item => item.deviceId === device.deviceId
);
if (!isDuplicate) {
this.discoveredDevices.push(device);
}
});
return Promise.resolve();
});
}
async discovery() {
this.discoveredDevices = [];
try {
await this.ohosBluetoothClassic.startDiscovery();
} catch (err) {
console.error('Discovery error:', err);
}
}
async onConnect(device: JluetoothDevice<string>) {
try {
this.connectedDevice = await this.ohosBluetoothClassic.connect(device);
console.log('Connected:', this.connectedDevice.deviceName());
} catch (err) {
console.error('Connection error:', err);
}
}
import { OhosNetwork } from '@psdk/ohos-network';
import { ConnectedDevice } from '@psdk/frame-father';
private ohosNetwork = new OhosNetwork();
async connectWifi(ip: string, port: number) {
try {
this.connectedDevice = await this.ohosNetwork.connect({
address: ip,
port: port
});
console.log('Connected to:', ip);
} catch (err) {
console.error('Connection error:', err);
}
}
import { OhosUsb } from '@psdk/ohos-usb';
import { ConnectedDevice } from '@psdk/frame-father';
private ohosUsb = new OhosUsb();
async connectUsb() {
try {
const devices = await this.ohosUsb.getDevices();
if (devices.length > 0) {
this.connectedDevice = await this.ohosUsb.connect(devices[0]);
console.log('USB connected');
}
} catch (err) {
console.error('USB connection error:', err);
}
}

CPCL 指令用于便携式标签打印机。详细指令说明请参考 CPCL 指令文档

import { CPCL, GenericCPCL } from '@psdk/cpcl';
import { Lifecycle } from '@psdk/frame-father';
const lifecycle = new Lifecycle(this.connectedDevice);
const cpcl: GenericCPCL = CPCL.generic(lifecycle);
const psdk = cpcl
.page({ width: 576, height: 400 })
.text({ x: 50, y: 50, content: '商品标签' })
.barcode({ x: 50, y: 150, content: '1234567890' })
.print();
await psdk.write();
import { CPCL } from '@psdk/cpcl';
import { Lifecycle } from '@psdk/frame-father';
const lifecycle = new Lifecycle(this.connectedDevice);
const cpcl = CPCL.generic(lifecycle);
const psdk = cpcl
.page({ width: 576, height: 400 })
// 标题
.center()
.setMag({ x: 2, y: 2 })
.text({ x: 0, y: 30, font: 24, content: '商品标签' })
.setMag({ x: 1, y: 1 })
// 商品信息
.left()
.line({ x1: 30, y1: 80, x2: 546, y2: 80, width: 2 })
.text({ x: 30, y: 100, font: 24, content: '名称: 有机苹果' })
.text({ x: 30, y: 140, font: 24, content: '规格: 500g/袋' })
.text({ x: 30, y: 180, font: 24, content: '产地: 山东烟台' })
.setBold(true)
.text({ x: 30, y: 220, font: 24, content: '价格: ¥25.90' })
.setBold(false)
// 条码
.barcode({
x: 100, y: 280,
type: '128',
ratio: 2,
height: 50,
content: '6901234567890'
})
// 二维码
.qrcode({
x: 400, y: 100,
content: 'https://example.com/product/123'
})
.print();
await psdk.write();

TSPL 指令用于标签条码打印机。详细指令说明请参考 TSPL 指令文档

import { TSPL, GenericTSPL } from '@psdk/tspl';
import { Lifecycle } from '@psdk/frame-father';
const lifecycle = new Lifecycle(this.connectedDevice);
const tspl: GenericTSPL = TSPL.generic(lifecycle);
const psdk = tspl
.page({ width: 60, height: 40 })
.gap(3)
.cls()
.text({ x: 50, y: 50, content: '标签内容' })
.print(1);
await psdk.write();
import { TSPL } from '@psdk/tspl';
import { Lifecycle } from '@psdk/frame-father';
const lifecycle = new Lifecycle(this.connectedDevice);
const tspl = TSPL.generic(lifecycle);
const psdk = tspl
.page({ width: 60, height: 40 })
.gap(3)
.direction('up')
.cls()
// 标题
.text({
x: 180, y: 30,
font: 'TSS24.BF2',
xMulti: 2, yMulti: 2,
content: '商品标签'
})
// 分隔线
.bar({ x: 30, y: 80, width: 420, height: 2 })
// 商品信息
.text({ x: 30, y: 100, font: 'TSS24.BF2', content: '品名: 有机苹果' })
.text({ x: 30, y: 140, font: 'TSS24.BF2', content: '规格: 500g/袋' })
.text({ x: 30, y: 180, font: 'TSS24.BF2', content: '价格: ¥25.90' })
// 条码
.barcode({
x: 100, y: 220,
type: '128',
height: 60,
content: '6901234567890'
})
// 二维码
.qrcode({
x: 350, y: 100,
cellWidth: 4,
content: 'https://example.com'
})
.print(1);
await psdk.write();

ESC 指令广泛用于热敏小票打印机。详细指令说明请参考 ESC 指令文档

import { ESC, GenericESC } from '@psdk/esc';
import { Lifecycle } from '@psdk/frame-father';
const lifecycle = new Lifecycle(this.connectedDevice);
const esc: GenericESC = ESC.generic(lifecycle);
const psdk = esc
.initialize()
.align('center')
.text('收银小票')
.align('left')
.text('商品A x1 ¥29.90')
.cut();
await psdk.write();
import { ESC } from '@psdk/esc';
import { Lifecycle } from '@psdk/frame-father';
const lifecycle = new Lifecycle(this.connectedDevice);
const esc = ESC.generic(lifecycle);
const psdk = esc
.initialize()
// 标题
.align('center')
.textSize({ width: 2, height: 2 })
.bold(true)
.text('XX 便利店')
.bold(false)
.textSize({ width: 1, height: 1 })
.text('订单号: 20240115001')
.text('================================')
// 商品列表
.align('left')
.text('可乐 500ml x2 ¥6.00')
.text('薯片 大包 x1 ¥8.50')
.text('--------------------------------')
// 合计
.textSize({ width: 1, height: 2 })
.text('合计: ¥14.50')
.textSize({ width: 1, height: 1 })
// 二维码
.align('center')
.qrcode({ content: 'https://shop.example.com', size: 5 })
.text('扫码关注店铺')
.feed(3)
.cut();
await psdk.write();

建议创建一个单例工具类来管理打印机连接:

import { CPCL, GenericCPCL } from '@psdk/cpcl';
import { ESC, GenericESC } from '@psdk/esc';
import { ConnectedDevice, Lifecycle } from '@psdk/frame-father';
import { GenericTSPL, TSPL } from '@psdk/tspl';
export class PrinterUtil {
static instance: PrinterUtil | null = null;
private _connectedDevice?: ConnectedDevice;
private _cpcl?: GenericCPCL;
private _tspl?: GenericTSPL;
private _esc?: GenericESC;
static getInstance() {
if (!PrinterUtil.instance) {
PrinterUtil.instance = new PrinterUtil();
}
return PrinterUtil.instance;
}
private constructor() {}
init(connectedDevice: ConnectedDevice) {
this._connectedDevice = connectedDevice;
const lifecycle = new Lifecycle(connectedDevice);
this._cpcl = CPCL.generic(lifecycle);
this._tspl = TSPL.generic(lifecycle);
this._esc = ESC.generic(lifecycle);
}
isConnected(): boolean {
return this._connectedDevice != null;
}
connectedDevice(): ConnectedDevice | undefined {
return this._connectedDevice;
}
cpcl(): GenericCPCL {
if (!this._connectedDevice)
throw Error('The device is not connected');
return this._cpcl!;
}
tspl(): GenericTSPL {
if (!this._connectedDevice)
throw Error('The device is not connected');
return this._tspl!;
}
esc(): GenericESC {
if (!this._connectedDevice)
throw Error('The device is not connected');
return this._esc!;
}
}

使用工具类:

// 连接时初始化
this.connectedDevice = await this.ohosBluetoothLe.connect(device);
PrinterUtil.getInstance().init(this.connectedDevice);
// 打印时使用
const cpcl = PrinterUtil.getInstance().cpcl();
const psdk = cpcl
.page({ width: 576, height: 400 })
.text({ x: 50, y: 50, content: '测试打印' })
.print();
await psdk.write();

对于大数据量的打印任务,建议使用分包发送:

async safeWrite(psdk: PSDK<GenericTSPL> | PSDK<GenericCPCL> | PSDK<GenericESC>) {
try {
// 不分包发送(适用于数据量小的情况)
const report = await psdk.write();
// 或者分包发送(推荐)
// const report = await psdk.write({
// enableChunkWrite: true,
// chunkSize: 20
// });
console.log('Print report:', report);
} catch (e) {
console.error('Print error:', e);
}
}
import { OhosBluetoothLe } from '@psdk/ohos-bluetooth-le';
import { ConnectedDevice } from '@psdk/frame-father';
import { JluetoothDevice } from '@psdk/device-bluetooth-traits';
import { PrinterUtil } from '../common/PrinterUtil';
import ble from '@ohos.bluetooth.ble';
import promptAction from '@ohos.promptAction';
@Entry
@Component
struct PrinterPage {
@State discoveredDevices: Array<JluetoothDevice<ble.ScanResult>> = [];
@State connectionState: string = 'Not connected';
@State connectedDevice?: ConnectedDevice = undefined;
private ohosBluetoothLe = new OhosBluetoothLe({
allowNoName: false,
allowedWriteCharacteristic: '49535343-8841-43F4-A8D4-ECBE34729BB3',
allowedReadCharacteristic: '49535343-1e4d-4bd9-ba61-23c647249616'
});
aboutToAppear() {
this.ohosBluetoothLe.discovered((devices) => {
devices.forEach(device => {
const isDuplicate = this.discoveredDevices.find(
item => item.deviceId === device.deviceId
);
if (!isDuplicate) {
this.discoveredDevices.push(device);
}
});
return Promise.resolve();
});
}
async discovery() {
this.discoveredDevices = [];
await this.ohosBluetoothLe.startDiscovery();
}
async onConnect(device: JluetoothDevice<ble.ScanResult>) {
try {
promptAction.showToast({ message: 'Connecting...', duration: 2000 });
this.connectedDevice = await this.ohosBluetoothLe.connect(device);
PrinterUtil.getInstance().init(this.connectedDevice);
this.connectionState = `${this.connectedDevice.deviceName()} connected`;
promptAction.showToast({
message: 'Connected successfully',
duration: 2000
});
} catch (err) {
promptAction.showToast({ message: 'Connection failed', duration: 2000 });
}
}
async onPrint() {
if (!this.connectedDevice) {
promptAction.showToast({ message: 'Please connect device', duration: 2000 });
return;
}
try {
const cpcl = PrinterUtil.getInstance().cpcl();
const psdk = cpcl
.page({ width: 576, height: 400 })
.center()
.text({ x: 0, y: 50, font: 24, content: 'Test Print' })
.print();
await psdk.write({ enableChunkWrite: true, chunkSize: 20 });
promptAction.showToast({ message: 'Print success', duration: 2000 });
} catch (e) {
promptAction.showToast({ message: 'Print failed', duration: 2000 });
}
}
onDisconnect() {
if (this.connectedDevice) {
this.connectedDevice.disconnect();
this.connectionState = 'Not connected';
this.connectedDevice = undefined;
promptAction.showToast({ message: 'Disconnected', duration: 2000 });
}
}
build() {
Column() {
Text(this.connectionState)
.fontSize(20)
.margin(20)
Button('Start Discovery')
.onClick(() => this.discovery())
.margin(10)
List() {
ForEach(this.discoveredDevices, (device: JluetoothDevice<ble.ScanResult>) => {
ListItem() {
Text(device.name)
.onClick(() => this.onConnect(device))
}
})
}
.height('40%')
Button('Print Test')
.onClick(() => this.onPrint())
.margin(10)
Button('Disconnect')
.onClick(() => this.onDisconnect())
.margin(10)
}
.width('100%')
.height('100%')
}
}
try {
await psdk.write();
console.log('Print success');
} catch (error) {
console.error('Print error:', error.message);
promptAction.showToast({
message: `Print failed: ${error.message}`,
duration: 2000
});
}

完整示例请参考: