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" }}然后执行安装:
ohpm install| 包名 | 说明 |
|---|---|
@psdk/frame-father | 核心框架 |
@psdk/frame-imageb | 图片处理 |
| 包名 | 说明 |
|---|---|
@psdk/cpcl | CPCL 指令 |
@psdk/esc | ESC 指令 |
@psdk/tspl | TSPL 指令 |
| 包名 | 说明 |
|---|---|
@psdk/ohos-bluetooth-le | 低功耗蓝牙 (BLE) |
@psdk/ohos-bluetooth-classic | 经典蓝牙 |
@psdk/ohos-bluetooth-classic-raw | 经典蓝牙原始模式 |
@psdk/ohos-network | Wi-Fi 网络 |
@psdk/ohos-usb | USB 连接 |
@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" } ] }}快速开始 - BLE 蓝牙
Section titled “快速开始 - BLE 蓝牙”1. 引入依赖
Section titled “1. 引入依赖”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';2. 初始化蓝牙
Section titled “2. 初始化蓝牙”private ohosBluetoothLe = new OhosBluetoothLe({ allowNoName: false, allowedWriteCharacteristic: '49535343-8841-43F4-A8D4-ECBE34729BB3', allowedReadCharacteristic: '49535343-1e4d-4bd9-ba61-23c647249616'});3. 搜索设备
Section titled “3. 搜索设备”@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); }}4. 连接设备
Section titled “4. 连接设备”@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); }}5. 断开连接
Section titled “5. 断开连接”async onDisconnect() { if (this.connectedDevice) { this.connectedDevice.disconnect(); this.connectedDevice = undefined; }}快速开始 - 经典蓝牙
Section titled “快速开始 - 经典蓝牙”1. 引入依赖
Section titled “1. 引入依赖”import { OhosBluetoothClassic } from '@psdk/ohos-bluetooth-classic';import { ConnectedDevice } from '@psdk/frame-father';import { JluetoothDevice } from '@psdk/device-bluetooth-traits';2. 初始化蓝牙
Section titled “2. 初始化蓝牙”private ohosBluetoothClassic = new OhosBluetoothClassic({ allowNoName: false});3. 搜索和连接
Section titled “3. 搜索和连接”@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); }}快速开始 - Wi-Fi 网络
Section titled “快速开始 - Wi-Fi 网络”1. 引入依赖
Section titled “1. 引入依赖”import { OhosNetwork } from '@psdk/ohos-network';import { ConnectedDevice } from '@psdk/frame-father';2. 连接打印机
Section titled “2. 连接打印机”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); }}快速开始 - USB
Section titled “快速开始 - USB”1. 引入依赖
Section titled “1. 引入依赖”import { OhosUsb } from '@psdk/ohos-usb';import { ConnectedDevice } from '@psdk/frame-father';2. 连接 USB 设备
Section titled “2. 连接 USB 设备”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 指令使用
Section titled “CPCL 指令使用”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();完整标签示例
Section titled “完整标签示例”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 指令使用
Section titled “TSPL 指令使用”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();完整标签示例
Section titled “完整标签示例”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 指令使用
Section titled “ESC 指令使用”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();打印小票示例
Section titled “打印小票示例”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();打印工具类封装
Section titled “打印工具类封装”建议创建一个单例工具类来管理打印机连接:
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); }}完整页面示例
Section titled “完整页面示例”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@Componentstruct 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 });}完整示例请参考:
- CPCL 指令详解
- TSPL 指令详解
- ESC 指令详解
- JavaScript SDK - 语法相似
- Dart SDK - 跨平台参考