Introduction

PSDK 是一套设计完备的热敏打印机交互开发包. 核心设计按功能层次进行了合理的拆分. 且提供简单易用的 API, 并且做到了所有平台统一接口命名实现.

PSDK design

Command

于图形打印机不同. 热敏打印机编程通常都是采用特定的指令形式进行交互. 目前常见的指令包括:

不同指令架构主要区别在于指令的构造上. 也会有部分功能在其他指令架构中不存在的情况. 具体需要根据打印机固件版本决定.

注意:

  1. 由于打印机硬件条件限制, 目前对于中文的支持采用的是 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, 可以在你的项目中直接依赖.

在 iOS 系统中, 需要使用低功耗蓝牙 psdk_bluetooth_ble, 在 Android 中 psdk_bluetooth_blepsdk_bluetooth_classic 均可使用, 但是 Android 中建议使用 classic 以获得更好的性能.

如果你需要开发一个基于 ESC 指令的应用, 那么, 可以包含下述依赖.

使用方式:

  1. 开启蓝牙发现服务

    await ClassicBluetooth().startDiscovery(disconnectConnectedDevice: false);
    
  2. 监听蓝牙搜索回调

    await ClassicBluetooth().discovered().listen((event) {
      // your code
    });
    
  3. 连接蓝牙

    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下载地址:

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];
}

Swift