Skip to content

JavaScript SDK

The JavaScript SDK supports WeChat Mini Programs, UniApp, Taro, and similar frontend frameworks, connecting to printers over Bluetooth Low Energy (BLE).

All packages are published on npm with the @psdk/ prefix.

Terminal window
npm install @psdk/frame-father @psdk/cpcl @psdk/device-ble-uniapp
Package nameDescription
@psdk/frame-fatherCore framework
Package nameDescription
@psdk/cpclCPCL commands
@psdk/escESC commands
@psdk/tsplTSPL commands
Package nameDescription
@psdk/device-ble-uniappUniApp BLE
@psdk/device-ble-wechatWeChat Mini Programs BLE
@psdk/device-ble-taroTaro BLE
import { UniappBleBluetooth } from "@psdk/device-ble-uniapp";
import { ConnectedDevice, Lifecycle, Raw } from '@psdk/frame-father';
const bluetooth = new UniappBleBluetooth({
allowNoName: false, // Allow devices without names
});
// Listen for discovered devices
bluetooth.discovered(async (devices) => {
if (!devices.length) return;
console.log('Device found:', devices);
// Connect when the target device is found
const targetDevice = devices.find(d => d.name.includes('Printer'));
if (targetDevice) {
await connectDevice(targetDevice);
}
});
// Start scanning
await bluetooth.startDiscovery();
let connectedDevice;
async function connectDevice(device) {
try {
connectedDevice = await bluetooth.connect(device);
console.log('Connected:', connectedDevice.name);
} catch (error) {
console.error('Connection failed:', error);
}
}
await bluetooth.disconnect();

CPCL commands are used by portable label printers. See the CPCL command reference for details.

import { CPCL } from '@psdk/cpcl';
import { Lifecycle } from '@psdk/frame-father';
const lifecycle = new Lifecycle(connectedDevice);
const cpcl = CPCL.generic(lifecycle);
const reporter = await cpcl
.page({ width: 576, height: 400 })
.text({ x: 50, y: 50, content: '商品标签' })
.barcode({ x: 50, y: 150, content: '1234567890' })
.print()
.write({ chunkSize: 512 });
console.log('Print result:', reporter);
import { CPCL } from '@psdk/cpcl';
import { Lifecycle } from '@psdk/frame-father';
const lifecycle = new Lifecycle(connectedDevice);
const cpcl = CPCL.generic(lifecycle);
await cpcl
.page({ width: 576, height: 400 })
// Title
.center()
.text({
x: 0, y: 30,
font: 4, size: 2,
content: '商品标签',
})
// Product information
.left()
.text({ x: 30, y: 100, content: '名称: 有机苹果' })
.text({ x: 30, y: 140, content: '规格: 500g/袋' })
.text({ x: 30, y: 180, content: '价格: ¥25.90' })
// Barcode
.barcode({
x: 100, y: 230,
type: '128',
height: 60,
content: '6901234567890',
})
// QR code
.qrcode({
x: 400, y: 100,
content: 'https://example.com',
})
.print()
.write({ chunkSize: 512 });

ESC commands are widely used by thermal receipt printers. See the ESC command reference for details.

import { ESC } from '@psdk/esc';
import { Lifecycle } from '@psdk/frame-father';
const lifecycle = new Lifecycle(connectedDevice);
const esc = ESC.generic(lifecycle);
await esc
.initialize()
.align('center')
.text('收银小票')
.align('left')
.text('商品A x1 ¥29.90')
.cut()
.write({ chunkSize: 512 });
import { ESC } from '@psdk/esc';
import { Lifecycle } from '@psdk/frame-father';
const lifecycle = new Lifecycle(connectedDevice);
const esc = ESC.generic(lifecycle);
await 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()
.write({ chunkSize: 512 });

TSPL commands are used by label and barcode printers. See the TSPL command reference for details.

import { TSPL } from '@psdk/tspl';
import { Lifecycle } from '@psdk/frame-father';
const lifecycle = new Lifecycle(connectedDevice);
const tspl = TSPL.generic(lifecycle);
await tspl
.page({ width: 60, height: 40 })
.cls()
.text({ x: 50, y: 50, content: '标签内容' })
.print(1)
.write({ chunkSize: 512 });
import { TSPL } from '@psdk/tspl';
import { Lifecycle } from '@psdk/frame-father';
const lifecycle = new Lifecycle(connectedDevice);
const tspl = TSPL.generic(lifecycle);
await 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, content: '品名: 有机苹果' })
.text({ x: 30, y: 140, content: '规格: 500g/袋' })
.text({ x: 30, y: 180, 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)
.write({ chunkSize: 512 });
import { Raw } from '@psdk/frame-father';
// Send raw binary commands
const reporter = await cpcl
.raw(Raw.binary(new Uint8Array([0x10, 0xff, 0xbf])))
.write({ chunkSize: 512 });
import { WechatBleBluetooth } from "@psdk/device-ble-wechat";
import { CPCL } from '@psdk/cpcl';
import { Lifecycle } from '@psdk/frame-father';
Page({
data: {
deviceList: [],
connectedDevice: null,
},
onLoad() {
this.bluetooth = new WechatBleBluetooth();
// Listen for devices
this.bluetooth.discovered((devices) => {
this.setData({ deviceList: devices });
});
},
// Start scanning
async startScan() {
await this.bluetooth.startDiscovery();
},
// Connect to a device
async connectDevice(e) {
const device = e.currentTarget.dataset.device;
try {
const connectedDevice = await this.bluetooth.connect(device);
this.setData({ connectedDevice });
wx.showToast({ title: 'Connected' });
} catch (error) {
wx.showToast({ title: 'Connection failed', icon: 'error' });
}
},
// Print
async print() {
const { connectedDevice } = this.data;
if (!connectedDevice) return;
const lifecycle = new Lifecycle(connectedDevice);
const cpcl = CPCL.generic(lifecycle);
await cpcl
.page({ width: 576, height: 400 })
.text({ x: 50, y: 50, content: '小程序打印测试' })
.print()
.write({ chunkSize: 512 });
wx.showToast({ title: 'Printing complete' });
},
});
import { TaroBleBluetooth } from "@psdk/device-ble-taro";
import { CPCL } from '@psdk/cpcl';
import { Lifecycle } from '@psdk/frame-father';
// Initialize as with UniApp Same
const bluetooth = new TaroBleBluetooth();
// Scan for devices
bluetooth.discovered((devices) => {
console.log('Device found:', devices);
});
await bluetooth.startDiscovery();
// Connect to a device
const connectedDevice = await bluetooth.connect(device);
// Print
const lifecycle = new Lifecycle(connectedDevice);
const cpcl = CPCL.generic(lifecycle);
await cpcl
.page({ width: 576, height: 400 })
.text({ x: 50, y: 50, content: 'Taro 打印测试' })
.print()
.write({ chunkSize: 512 });
try {
await cpcl.write({ chunkSize: 512 });
console.log('Print successful');
} catch (error) {
if (error.code === 'BLE_DISCONNECTED') {
console.error('Bluetooth disconnected');
} else if (error.code === 'BLE_TIMEOUT') {
console.error('Bluetooth timeout');
} else {
console.error('Printing failed:', error.message);
}
}

For complete examples, see: