Introduction
PSDK
是一套设计完备的热敏打印机交互开发包. 核心设计按功能层次进行了合理的拆分. 且提供简单易用的 API, 并且做到了所有平台统一接口命名实现.
Command
于图形打印机不同. 热敏打印机编程通常都是采用特定的指令形式进行交互. 目前常见的指令包括:
不同指令架构主要区别在于指令的构造上. 也会有部分功能在其他指令架构中不存在的情况. 具体需要根据打印机固件版本决定.
注意:
- 由于打印机硬件条件限制, 目前对于中文的支持采用的是 GBK 编码方式, 任何发送给打印机的中文都需要使用 GBK 编码.
CPCL
文字形式指令 (部分机型固件会同时搭配 esc/pos 指令), 表现形式为指令头于参数. 指令头后用空格于参数隔开. 参数于参数之间也用空格隔开.
字符串需要用 "
左右包起来, 如果文字中也有 "
需要转义成 ["]
.
具体可以参考文档 CPCL.pdf
ESC
二进制形式指令. 指令需要使用 16 进制数字形式, 来拼凑完整的打印指令. 完整的指令文档请参考 ESC.pdf
ESC/POS
与 ESC
指令类似, 详细文档可参考 ESCPOS.pdf
TSPL
与 CPCL 指令类似, 一个明显的差别是 TSPL 指令中, 参数于参数之间使用 ,
隔开.
具体可参考 TSPL.pdf
Programming
目前我们SDK支持如下开发语言:
Dart
Dart SDK 均发布至 pub.dev, 可以在你的项目中直接依赖.
- psdk_frame_ota
- psdk_fruit_cpcl
- psdk_frame_father
- psdk_device_adapter
- psdk_bluetooth_traits
- psdk_bluetooth_classic
- psdk_fruit_esc
- psdk_bluetooth_ble
- psdk_fruit_tspl
- psdk_fruit_wifi
- psdk_bluetooth_windows
- psdk_network
- psdk_usb_windows
- psdk_usb_standard
在 iOS 系统中, 需要使用低功耗蓝牙 psdk_bluetooth_ble, 在 Android 中 psdk_bluetooth_ble 与 psdk_bluetooth_classic 均可使用, 但是 Android 中建议使用 classic 以获得更好的性能.
如果你需要开发一个基于 ESC 指令的应用, 那么, 可以包含下述依赖.
- psdk_fruit_esc * 必选
- psdk_bluetooth_ble - 可选, 根据应用实际情况, ble 主要用于 iOS/Mac
- psdk_bluetooth_classic - 可选, 根据应用实际情况
- psdk_frame_father - 可选, 此包已被 psdk_fruit_esc 依赖, 如果你需要这里面的类型, 再添加
使用方式:
-
开启蓝牙发现服务
await ClassicBluetooth().startDiscovery(disconnectConnectedDevice: false);
-
监听蓝牙搜索回调
await ClassicBluetooth().discovered().listen((event) { // your code });
-
连接蓝牙
await ClassicBluetooth().connect(result);
此外, 可以参考 demo
Java
集成
Java SDK 发布至 gitlab maven registry, 可以透过在你的项目中引入 repository 来引用 SDK. 或者直接使用demo里的jar包来集成开发(推荐). Maven
<repositories>
<repository>
<id>gitlab-maven</id>
<url>https://gitlab.com/api/v4/projects/35052067/packages/maven</url>
</repository>
</repositories>
Grandle
maven {
url 'https://gitlab.com/api/v4/projects/35052067/packages/maven'
}
Kotlin
maven("https://gitlab.com/api/v4/projects/35052067/packages/maven")
接下来直接在你的项目中添加对应指令的依赖即可.
CPCL指令
implementation 'com.printer.psdk:fat-generic-cpcl-bluetooth-classic:0.1.8-GA'
ESC指令
implementation 'com.printer.psdk:fat-generic-esc-bluetooth-classic:0.1.8-GA'
TSPL指令
implementation 'com.printer.psdk:fat-generic-tspl-bluetooth-classic:0.1.8-GA'
同时需要多种指令
implementation 'com.printer.psdk:fat-generic-tspl-bluetooth-classic:0.1.8-GA'
最新发布的版本可以从此处查看: package-center
使用
蓝牙部分示例代码
//可以直接通过蓝牙扫描获取到BluetoothDevice
BluetoothDevice device = getIntent().getParcelableExtra("device");
//或者通过传mac地址来获取BluetoothDevice
BluetoothDevice device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(bluetoothAddress);
//根据BluetoothDevice创建连接对象
Connection connection = ClassicBluetooth.getInstance().createConnection(device, new ConnectListener() {
@Override
public void onConnectSuccess(ConnectedDevice connectedDevice) {
//设备连接成功的回调,根据对应指令生成对应指令的对象 如cpcl:
GenericCPCL cpcl = CPCL.generic(connectedDevice);
//tspl:
GenericTSPL tspl = TSPL.generic(connectedDevice);
//esc:
GenericESC esc = ESC.generic(connectedDevice);
//如果想监听打印机回传的数据可以调用
DataListener.with(connectedDevice).listen(new ListenAction() {
@Override
public void action(byte[] bytes) {
//打印机回传的数据处理
}
});
}
@Override
public void onConnectFail(String errMsg, Throwable e) {
}
@Override
public void onConnectionStateChanged(BluetoothDevice device, int state) {
String msg;
switch (state) {
case Connection.STATE_CONNECTING:
msg = "连接中";
break;
case Connection.STATE_PAIRING:
msg = "配对中...";
break;
case Connection.STATE_PAIRED:
msg = "配对成功";
break;
case Connection.STATE_CONNECTED:
msg = "连接成功";
break;
case Connection.STATE_DISCONNECTED:
msg = "连接断开";
break;
case Connection.STATE_RELEASED:
msg = "连接已销毁";
break;
default:
msg = "";
}
}
});
//连接是个耗时操作可以开个线程
new Thread(new Runnable() {
@Override
public void run() {
//调用连接方法连接设备 连接成功会走onConnectSuccess回调
connection.connect(null);
}
}).start();
CPCL指令示例代码
//使用指令对象cpcl来拼接指令
//一个完整的打印指令一定要有page()开头和print()结尾
GenericCPCL _gcpcl = cpcl.page(CPage.builder().width(608).height(1040).copies(sampleNumber).build())
.box(CBox.builder().topLeftX(0).topLeftY(1).bottomRightX(598).bottomRightY(664).lineWidth(2).build())
.line(CLine.builder().startX(0).startY(88).endX(598).endY(88).lineWidth(2).build())
.bar(CBar.builder().x(120).y(88 + 12).lineWidth(1).height(80).content("1234567890").build())
.text(CText.builder().textX(120 + 12).textY(88 + 20 + 76).font(Font.TSS24).content("1234567890").build())
.print(CPrint.builder().build());
//最后使用write()方法把指令写入打印机
_gcpcl.write();
TSPL指令示例代码
//使用指令对象tspl来拼接指令
//一个完整的打印指令一定要有page()开头和print()结尾
GenericTSPL _gtspl = tspl.page(TPage.builder().width(100).height(180).build())
.direction(TDirection.builder().direction(TDirection.Direction.UP_OUT).mirror(TDirection.Mirror.NO_MIRROR).build())
.gap(true)
.cut(true)
.speed(6)
.density(6)
.cls()
.bar(TBar.builder().x(300).y(10).width(4).height(90).build())
.text(TText.builder().x(400).y(25).font(Font.TSS24).xmulti(3).ymulti(3).content("上海浦东").build())
.circle(TCircle.builder().x(670).y(1170).width(6).radius(100).build())
.qrcode(TQRCode.builder().x(620).y(620).correctLevel(CorrectLevel.H).cellWidth(4).content("www.qrprt.com").build())
.print(1);
//最后使用write()方法把指令写入打印机
_gtspl.write();
ESC指令示例代码
//使用指令对象esc来拼接指令
GenericESC _gesc = esc.enable()
.wakeup()
.location(ELocation.builder().location(Location.LEFT).build())
.image(EImage.builder()
.image(bitmap2Bytes(bitmap))
.compress(true)
.build())
.lineDot(30)
.stopJob();
//最后使用write()方法把指令写入打印机
_gesc.write();
此外, 可以参考 demo 或者 参考手册 来查看如何使用 SDK.
JavaScript
JavaScript SDK 已发布至 npm. 以 @psdk/
为包前缀.
目前已支持的蓝牙实现包括 wechat/uniapp/taro.
使用方式 (以 cpcl 和 uniapp 为例):
安装依赖
npm i @psdk/cpcl @psdk/device-ble-uniapp
import {UniappBleBluetooth} from "@psdk/device-ble-uniapp";
import {ConnectedDevice, Lifecycle, Raw} from '@psdk/frame-father';
import {CPCL, GenericCPCL} from '@psdk/cpcl';
// 构造 uniapp bluetooth 对象
const bluetooth = new UniappBleBluetooth({
allowNoName: false,
});
let connectedDevice;
bluetooth.discovered(async (devices) => {
// 发现新设备
if (!devices.length) return;
if (devices[0].name.indexOf('test')) {
// 连接设备
connectedDevice = await bluetooth.connect(devices[0]);
// ==========
const lifecycle = new Lifecycle(connectedDevice);
const cpcl = CPCL.generic(lifecycle);
const reporter = await cpcl
.raw(Raw.binary(new Uint8Array([0x10, 0xff, 0xbf])))
.write({chunkSize: 512});
console.log(reporter);
}
});
// 开启搜索
await vm.bluetooth.startDiscovery();
此外, 可以参考 demo来查看如何使用 SDK.
Objective-C
1-OC Framework下载地址:
2-导入 framework
-
必须导入:adapter.framework、father.framework
-
按需导入:cpcl.framework、esc.framework、tspl.framework。
3-根据打印机选择指令:
- ESC 指令 导入 IbridgeBleApi;
- CPCL 指令 导入 FscBleApi;
- TSPL 指令 导入 applebleApi.
4.Demo 下载地址
demo
5.How to use
5.1-TSPL
#import <tspl/tspl.h>
#import <tspl/GenericTSPL.h>
@interface TSPLVC ()
@property (nonatomic , strong) CBPeripheral *currentConnectedPeripheral;
@property (nonatomic , strong) BasicTSPL *basicTspl;
@property (nonatomic , strong) Lifecycle *lifeCycle;
@end
@implementation TSPLVC
- (void)viewDidLoad {
[super viewDidLoad];
// 初始化
_lifeCycle = [[Lifecycle alloc]init];
_lifeCycle.connectedDevice(self.device);
_basicTspl = [[BasicTSPL alloc]init];
_basicTspl.BasicTSPL(_lifeCycle, TSPLPrinter_GENERIC);
__weak typeof (self) weakSelf = self;
// 接收打印机回传
_basicTspl.read = ^(NSData * _Nonnull revData, NSError * _Nullable error) {
NSLog(@"----:%@",revData);
};
// 连接成功
_basicTspl.bleDidConnectPeripheral = ^(CBPeripheral * _Nonnull peripheral) {};
// 连接断开
_basicTspl.bleDidDisconnectPeripheral = ^(CBPeripheral * _Nonnull peripheral, NSError * _Nonnull error) {};
// 连接失败
_basicTspl.bleDidFailToConnectPeripheral = ^(CBPeripheral * _Nonnull peripheral, NSError * _Nonnull error) {};
}
// 打印
-(void)printModelAction{
//使用指令对象tspl来拼接指令
//一个完整的打印指令一定要有page()开头和print()结尾
_basicTspl.page([[TPage alloc]init].width(100).height(180))
.direction([[TDirection alloc]init].direction(UP_OUT).mirror(NO_MIRROR))
.gap(true)
.cut(true)
.speed(6)
.density(6)
.cls()
.bar([[TBar alloc]init].x(300).y(10).width(4).height(90))
.bar([[TBar alloc]init].x(30).y(100).width(740).height(4))
.bar([[TBar alloc]init].x(30).y(880).width(740).height(4))
.bar([[TBar alloc]init].x(30).y(1300).width(740).height(4))
.text([[TText alloc]init].x(400).y(25).font(TSS24).xmulti(3).ymulti(3).content(@"上海浦东"))
.text([[TText alloc]init].x(30).y(120).font(TSS24).xmulti(1).ymulti(1).content(@"发 件 人:张三 (电话 874236021)"))
.text([[TText alloc]init].x(30).y(150).font(TSS24).xmulti(1).ymulti(1).content(@"发件人地址:广州省 深圳市 福田区 思创路123号\"工业园\"1栋2楼"))
.text([[TText alloc]init].x(30).y(200).font(TSS24).xmulti(1).ymulti(1).content(@"收 件 人:李四 (电话 13899658435)"))
.text([[TText alloc]init].x(30).y(230).font(TSS24).xmulti(1).ymulti(1).content(@"收件人地址:上海市 浦东区 太仓路司务小区9栋1105室"))
.text([[TText alloc]init].x(30).y(700).font(TSS16).xmulti(1).ymulti(1).content(@"各類郵件禁寄、限寄的範圍,除上述規定外,還應參閱「中華人民共和國海關對"))
.text([[TText alloc]init].x(30).y(720).font(TSS16).xmulti(1).ymulti(1).content(@"进出口邮递物品监管办法”和国家法令有关禁止和限制邮寄物品的规定,以及邮"))
.text([[TText alloc]init].x(30).y(740).font(TSS16).xmulti(1).ymulti(1).content(@"寄物品的规定,以及邮电部转发的各国(地区)邮 政禁止和限制。"))
.text([[TText alloc]init].x(30).y(760).font(TSS16).xmulti(1).ymulti(1).content(@"寄件人承诺不含有法律规定的违禁物品。"))
.barcode([[TBarCode alloc]init].x(80).y(300).codeType(CODE_128).height(90).showType(SHOW_CENTER).cellwidth(4).content(@"873456093465"))
.barcode([[TBarCode alloc]init].x(550).y(910).codeType(CODE_128).height(50).showType(SHOW_CENTER).cellwidth(2).content(@"873456093465"))
.box([[TBox alloc]init].startX(40).startY(500).endX(340).endY(650).width(4).radius(20))
.text([[TText alloc]init].x(60).y(520).font(TSS24).xmulti(1).ymulti(1).content(@"寄件人签字:"))
.text([[TText alloc]init].x(130).y(625).font(TSS32).xmulti(1).ymulti(1).content(@"2015-10-30 09:09"))
.text([[TText alloc]init].x(50).y(1000).font(TSS32).xmulti(2).ymulti(3).content(@"广东 ---- 上海浦东"))
.circle([[TCircle alloc]init].startX(670).startY(1170).width(6).radius(100))
.text([[TText alloc]init].x(670).y(1170).font(TSS24).xmulti(3).ymulti(3).content(@"碎"))
.qrcode([[TQRCode alloc]init].x(620).y(620).correctLevel(H).cellWidth(4).content(@"www.qrprt.com www.qrprt.com www.qrprt.com"))
.print();
//最后使用write()方法把指令写入打印机
_basicTspl.write();
}
@end
5.2-CPCL
// 状态回调
{
_basicCPCL.read = ^(NSData * _Nonnull revData, NSError * _Nullable error) {};
// 连接成功
_basicCPCL.bleDidConnectPeripheral = ^(CBPeripheral * _Nonnull peripheral) {};
// 连接断开
_basicCPCL.bleDidDisconnectPeripheral = ^(CBPeripheral * _Nonnull peripheral, NSError * _Nonnull error) {};
// 连接失败
_basicCPCL.bleDidFailToConnectPeripheral = ^(CBPeripheral * _Nonnull peripheral, NSError * _Nonnull error) {};
}
// 打印
{
//使用指令对象cpcl来拼接指令
//一个完整的打印指令一定要有page()开头和print()结尾
_basicCPCL.page([[CPage alloc]init].width(608).height(1040).num(sampleNumber))
.box([[CBox alloc]init].topLeftX(0).topLeftY(1).bottomRightX(598).bottomRightY(664).width(2))
.line([[CLine alloc]init].startX(0).startY(88).endX(598).endY(88).lineWidth(2))
.line([[CLine alloc]init].startX(0).startY(88+128).endX(598).endY(88+128).lineWidth(2) )
.line([[CLine alloc]init].startX(0).startY(88+128+80).endX(598).endY(88+128+80).lineWidth(2) )
.line([[CLine alloc]init].startX(0).startY(88+128+80+144).endX(598-56-16).endY(88+128+80+144).lineWidth(2) )
.line([[CLine alloc]init].startX(0).startY(88+128+80+144+128).endX(598-56-16).endY(88+128+80+144+128).lineWidth(2) )
.line([[CLine alloc]init].startX(52).startY(88+128+80).endX(52).endY(88+128+80+144+128).lineWidth(2) )
.line([[CLine alloc]init].startX(598-56-16).startY(88+128+80).endX(598-56-16).endY(664).lineWidth(2) )
.bar([[CBar alloc]init].x(120).y(100).lineWidth(1).height(80).content(@"1234567890").codeType(CODE128).codeRotation(CCODEROTATION_0))
.text([[CText alloc]init].textX(120+12).textY(88+20+76).font(TSS24).content(@"1234567890") )
.text([[CText alloc]init].textX(12).textY(88 +128 + 80 +32).font(TSS24).content(@"收") )
.text([[CText alloc]init].textX(12).textY(88 +128 + 80 +96).font(TSS24).content(@"件") )
.text([[CText alloc]init].textX(12).textY(88+128+80+144+32).font(TSS24).content(@"发") )
.text([[CText alloc]init].textX(12).textY(88+128+80+144+80).font(TSS24).content(@"件") )
.text([[CText alloc]init].textX(52+20).textY(88+128+80+144+128+16).font(TSS24).content(@"签收人/签收时间") )
.text([[CText alloc]init].textX(430).textY(88+128+80+144+128+36).font(TSS24).content(@"月") )
.text([[CText alloc]init].textX(490).textY(88+128+80+144+128+36).font(TSS24).content(@"日") )
.text([[CText alloc]init].textX(52+20).textY(88+128+80+24).font(TSS24).content(@"收姓名 13777777777") )
.text([[CText alloc]init].textX(52+20).textY(88 +128+80+24+32).font(TSS24).content(@"南京市浦口区威尼斯水城七街区七街区") )
.text([[CText alloc]init].textX(52+20).textY(88+128+80+144+24).font(TSS24).content(@"名字 13777777777") )
.text([[CText alloc]init].textX(52+20).textY(88+128+80+144+24+32).font(TSS24).content(@"南京市浦口区威尼斯水城七街区七街区") )
.text([[CText alloc]init].textX(598-56-5).textY(88 +128+80+104).font(TSS24).content(@"派") )
.text([[CText alloc]init].textX(598-56-5).textY(88 +128+80+160).font(TSS24).content(@"件") )
.text([[CText alloc]init].textX(598-56-5).textY(88 +128+80+208).font(TSS24).content(@"联") )
.box([[CBox alloc]init].topLeftX(0).topLeftY(1).bottomRightX(598).bottomRightY(968).width(2) )
.line([[CLine alloc]init].startX(0).startY(696+80).endX(598).endY(696+80).lineWidth(2) )
.line([[CLine alloc]init].startX(0).startY(696+80+136).endX(598-56-16).endY(696+80+136).lineWidth(2) )
.line([[CLine alloc]init].startX(52).startY(80).endX(52).endY(696+80+136).lineWidth(2) )
.line([[CLine alloc]init].startX(598-56-16).startY(80).endX(598-56-16).endY(968).lineWidth(2) )
.bar([[CBar alloc]init].x(320).y(696-4).lineWidth(1).height(56).content(@"1234567890").codeType(CODE128).codeRotation(CCODEROTATION_0) )
.text([[CText alloc]init].textX(320+8).textY(696+54).font(TSS16).content(@"1234567890") )
.text([[CText alloc]init].textX(12).textY(696+80+35).font(TSS24).content(@"发") )
.text([[CText alloc]init].textX(12).textY(696+80+84).font(TSS24).content(@"件") )
.text([[CText alloc]init].textX(52+20).textY(696+80+28).font(TSS24).content(@"名字 13777777777") )
.text([[CText alloc]init].textX(52+20).textY(696+80+28+32).font(TSS24).content(@"南京市浦口区威尼斯水城七街区七街区") )
.text([[CText alloc]init].textX(598-56-5).textY(696+80+50).font(TSS24).content(@"客") )
.text([[CText alloc]init].textX(598-56-5).textY(696+80+82).font(TSS24).content(@"户") )
.text([[CText alloc]init].textX(598-56-5).textY(696+80+106).font(TSS24).content(@"联") )
.text([[CText alloc]init].textX(12+8).textY(696+80+136+22-5).font(TSS24).content(@"物品:几个快递 12kg") )
.box([[CBox alloc]init].topLeftX(598-56-16-120).topLeftY(696+80+136+11).bottomRightX(598-56-16-16).bottomRightY(968-11).width(2) )
.text([[CText alloc]init].textX(598-56-16-120+17).textY(696+80+136+11+6).font(TSS24).content(@"已验视") )
.print([[CPrint alloc]init] ).feed();
//最后使用write()方法把指令写入打印机
_basicCPCL.write();
}
5.3-ESC
// 状态回调
{
// 连接成功
_basicESC.bleDidConnectPeripheral = ^(CBPeripheral * _Nonnull peripheral) {};
// 连接断开
_basicESC.bleDidDisconnectPeripheral = ^(CBPeripheral * _Nonnull peripheral, NSError * _Nonnull error) {};
// 连接失败
_basicESC.bleDidFailToConnectPeripheral = ^(CBPeripheral * _Nonnull peripheral, NSError * _Nonnull error) {};
}
// 写入数据
{
//使用指令对象esc来拼接指令
//最后使用write()方法把指令写入打印机
_basicESC.batteryVolume().write();
ReadOptions *options = [[ReadOptions alloc]init].timeout(1000);
options.callBack = ^(NSData * _Nonnull revData, NSError * _Nullable error) {
if(error) return;
[self readData:revData];
};
[_basicESC read:options];
}