OpenHarmony SDK
The OpenHarmony SDK supports HarmonyOS NEXT and OpenHarmony applications, connecting to printers over Bluetooth Classic, BLE, Wi-Fi, or USB.
Installation
Section titled “Installation”All packages are published on OHPM with the @psdk/ prefix.
Add dependencies to 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" }}Then install them:
ohpm installAvailable packages
Section titled “Available packages”Core packages
Section titled “Core packages”| Package name | Description |
|---|---|
@psdk/frame-father | Core framework |
@psdk/frame-imageb | Image processing |
Command packages
Section titled “Command packages”| Package name | Description |
|---|---|
@psdk/cpcl | CPCL commands |
@psdk/esc | ESC commands |
@psdk/tspl | TSPL commands |
Device packages
Section titled “Device packages”| Package name | Description |
|---|---|
@psdk/ohos-bluetooth-le | Bluetooth Low Energy (BLE) |
@psdk/ohos-bluetooth-classic | Bluetooth Classic |
@psdk/ohos-bluetooth-classic-raw | Bluetooth Classic raw mode |
@psdk/ohos-network | Wi-Fi network |
@psdk/ohos-usb | USB connection |
@psdk/device-bluetooth-traits | Bluetooth device traits |
Permission configuration
Section titled “Permission configuration”Add the required permissions to module.json5:
Bluetooth permissions
Section titled “Bluetooth permissions”{ "module": { "requestPermissions": [ { "name": "ohos.permission.ACCESS_BLUETOOTH" }, { "name": "ohos.permission.DISCOVER_BLUETOOTH" }, { "name": "ohos.permission.MANAGE_BLUETOOTH" }, { "name": "ohos.permission.APPROXIMATELY_LOCATION" } ] }}Network permissions
Section titled “Network permissions”{ "module": { "requestPermissions": [ { "name": "ohos.permission.INTERNET" } ] }}Quick start - BLE
Section titled “Quick start - BLE”1. Import dependencies
Section titled “1. Import dependencies”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. Initialize Bluetooth
Section titled “2. Initialize Bluetooth”private ohosBluetoothLe = new OhosBluetoothLe({ allowNoName: false, allowedWriteCharacteristic: '49535343-8841-43F4-A8D4-ECBE34729BB3', allowedReadCharacteristic: '49535343-1e4d-4bd9-ba61-23c647249616'});3. Scan for devices
Section titled “3. Scan for devices”@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. Connect to a device
Section titled “4. Connect to a device”@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. Disconnect
Section titled “5. Disconnect”async onDisconnect() { if (this.connectedDevice) { this.connectedDevice.disconnect(); this.connectedDevice = undefined; }}Quick start - Bluetooth Classic
Section titled “Quick start - Bluetooth Classic”1. Import dependencies
Section titled “1. Import dependencies”import { OhosBluetoothClassic } from '@psdk/ohos-bluetooth-classic';import { ConnectedDevice } from '@psdk/frame-father';import { JluetoothDevice } from '@psdk/device-bluetooth-traits';2. Initialize Bluetooth
Section titled “2. Initialize Bluetooth”private ohosBluetoothClassic = new OhosBluetoothClassic({ allowNoName: false});3. Scan and connect
Section titled “3. Scan and connect”@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); }}Quick start - Wi-Fi network
Section titled “Quick start - Wi-Fi network”1. Import dependencies
Section titled “1. Import dependencies”import { OhosNetwork } from '@psdk/ohos-network';import { ConnectedDevice } from '@psdk/frame-father';2. Connect to the printer
Section titled “2. Connect to the printer”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); }}Quick start - USB
Section titled “Quick start - USB”1. Import dependencies
Section titled “1. Import dependencies”import { OhosUsb } from '@psdk/ohos-usb';import { ConnectedDevice } from '@psdk/frame-father';2. Connect to a USB device
Section titled “2. Connect to a USB device”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 command usage
Section titled “CPCL command usage”CPCL commands are used by portable label printers. See the CPCL command reference for details.
Basic printing
Section titled “Basic printing”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();Complete label example
Section titled “Complete label example”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 }) // Title .center() .setMag({ x: 2, y: 2 }) .text({ x: 0, y: 30, font: 24, content: '商品标签' }) .setMag({ x: 1, y: 1 }) // Product information .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 .barcode({ x: 100, y: 280, type: '128', ratio: 2, height: 50, content: '6901234567890' }) // QR code .qrcode({ x: 400, y: 100, content: 'https://example.com/product/123' }) .print();
await psdk.write();TSPL command usage
Section titled “TSPL command usage”TSPL commands are used by label and barcode printers. See the TSPL command reference for details.
Basic printing
Section titled “Basic printing”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();Complete label example
Section titled “Complete label example”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() // Title .text({ x: 180, y: 30, font: 'TSS24.BF2', xMulti: 2, yMulti: 2, content: '商品标签' }) // Separator line .bar({ x: 30, y: 80, width: 420, height: 2 }) // Product information .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 .barcode({ x: 100, y: 220, type: '128', height: 60, content: '6901234567890' }) // QR code .qrcode({ x: 350, y: 100, cellWidth: 4, content: 'https://example.com' }) .print(1);
await psdk.write();ESC command usage
Section titled “ESC command usage”ESC commands are widely used by thermal receipt printers. See the ESC command reference for details.
Basic printing
Section titled “Basic printing”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();Receipt printing example
Section titled “Receipt printing example”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() // Title .align('center') .textSize({ width: 2, height: 2 }) .bold(true) .text('XX 便利店') .bold(false) .textSize({ width: 1, height: 1 }) .text('订单号: 20240115001') .text('================================') // Product list .align('left') .text('可乐 500ml x2 ¥6.00') .text('薯片 大包 x1 ¥8.50') .text('--------------------------------') // Total .textSize({ width: 1, height: 2 }) .text('合计: ¥14.50') .textSize({ width: 1, height: 1 }) // QR code .align('center') .qrcode({ content: 'https://shop.example.com', size: 5 }) .text('扫码关注店铺') .feed(3) .cut();
await psdk.write();Printer helper class
Section titled “Printer helper class”Use a singleton helper class to manage the printer connection:
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!; }}Use the helper class:
// Initialize on connectionthis.connectedDevice = await this.ohosBluetoothLe.connect(device);PrinterUtil.getInstance().init(this.connectedDevice);
// Use when printingconst cpcl = PrinterUtil.getInstance().cpcl();const psdk = cpcl .page({ width: 576, height: 400 }) .text({ x: 50, y: 50, content: '测试打印' }) .print();
await psdk.write();Chunked transfer
Section titled “Chunked transfer”For large print jobs, send data in chunks:
async safeWrite(psdk: PSDK<GenericTSPL> | PSDK<GenericCPCL> | PSDK<GenericESC>) { try { // Send without chunking (For small payloads) const report = await psdk.write();
// Or send in chunks (Recommended) // const report = await psdk.write({ // enableChunkWrite: true, // chunkSize: 20 // });
console.log('Print report:', report); } catch (e) { console.error('Print error:', e); }}Complete page example
Section titled “Complete page example”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%') }}Error handling
Section titled “Error handling”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 });}Example projects
Section titled “Example projects”For complete examples, see:
Related documentation
Section titled “Related documentation”- CPCL command reference
- TSPL command reference
- ESC command reference
- JavaScript SDK - Similar syntax
- Dart SDK - Cross-platform reference