月月月子喵~

佳博标签打印机二次开发

2020-03-17

佳博标签打印机二次开发#

最近在做一个家用的 WMS(仓库管理系统), 给我的收集品,, 书以及杂七杂八的东西进行整理分类. 为了方便录入信息, 以及查询物品信息, 需要设计一套标签系统, 效果如下

设备信息#

我这里购买的是 2120TU(网口版本)

只记录了关键参数

  • 打印方式: 热敏
  • 分辨率: 203 DPI
  • 打印宽度: 56 mm
  • 通讯接口: USB + Wifi
  • 条码: 看自己需求(东西太多懒得打), 有Qrcode 需要的记得要看看(也可以不需要)
  • 图形: 单色BMP, PCX 图像
  • 字符集: 这个看你自己需求, 可以无关系
  • 纸卷: 80mm (max)
  • Flash: 80kb

其他似乎就没啥了

纸卷是 30mm X 40mm

开发文档#

我问网店要的 开发文档, 有提供开发示例, 你配这文档看我下面的示例就行

下面的开发只涉及 网口版本, 使用TCP连接发送指令打印

初始化设备#

下载 GPETHERNET 这个工具, 百度google 都有

工具下载 + 视频指引(官方)

这个沙雕设备默认是固定IP的, 192.168.1.200, 找一根网线, 一头插电脑一头插打印机, 然后吧电脑的IP设置为

  • IP 192.168.1.202
  • MASK 255.255.255.0

然后打开那个软件, 连接设置

如果后面不知道IP是多少的话, 先把打印机关机, 按住 <走纸> 按键不松开然后开启打印机, 5秒后松手就会打印自检页, 里面有写

连接设备#

这里选择的开发语言是 Typescript +Nodejs (本来打算用Rust的, 但是自己用的东西开发速度要快就没纠结了)

开个 TCP 连接 刚刚设置好的 IP 的 9100 端口即可, 返回Promise简单点

打印机设备同时只能处理一个连接的 TCP, 如果没有连上可能是已被占用. 打印完毕记得释放连接

import { createConnection, Socket} from "net";

async open_port(host: string, port: number = 9100) {
    return new Promise((s, j) => {
      this.socket = createConnection(port, host, () => {
        console.log('链接成功')
        s()
      });
    })
  }

发送命令#

所有的打印操作都是通过指令的方式进行提交的, 格式为

<命令> [参数1],[参数2]\n, 例如 SIZE 40mm,30mm\n

参数之间用 , 分割, 结尾用 \n

这里要注意一个问题, 中文需要转 GBK2312 字符集, 这里使用了 iconv-lite

这个函数就是用来发送指令的, 下面都会用到这个, 你也可以不用, 自己手写命令

参数是 字符串或者 buffer 数组, 用处就是 改变字符集, 自动加上 \n 发送

import { encode } from 'iconv-lite'

async send_command(buffer: string | string []) {
    if (Array.isArray(buffer)) {
      for (let i = 0; i < buffer.length; i++) {
        await this.send_command(buffer[i])
      }
    } else {
      return new Promise((s, j) => {
        console.log('[CMD]' + buffer)
        this.socket.write(encode(buffer + '\n', 'gbk'), err => {
          if (err) {
            return j();
          } else {
            return s();
          }
        })
      })
    }
  }

发送完毕记得调用 PRINT <打印张数>,<重复数量>

初始化设备#

要注意单位, 有公制和英制区别, 下面统一使用公制单位 mm

以下指令在文档中都有

  • 设置标签大小 (SIZE <宽度>mm,<高度>mm)
  • 设置打印速度 (SPEED <等级>)
  • 设置打印机浓度 (DENSITY <浓度>) 0-15,
  • 设置打印机两张标签间的距离 (GAP <距离>mm)
// 设置标签大小为 40 毫米 * 30 毫米
await this.send_command('SIZE 40mm,30mm')
// 设置速度等级为 4
await this.send_command('SPEED 4')
// 设置浓度为 2
await this.send_command('DENSITY 2')
// 设置标签偏移距离
await this.send_command('GAP 2mm')

对照表#

DPI dots
200 8
300 12

这个差不多就对应像素的意思, 200DPI下, 每1mm有8个像素(dots), 300DPI就是12像素(dots)

可以预估位置, 或者按照这个大小创建 canvas 画板

我的 40mm * 30mm 的标签就是 320px * 240px 的画板

设计标签#

这里采用的办法是 Canvas 画图导出 单色BMP图片, 为什么不推荐内置指令呢

  • 编写麻烦
  • 字体要命, 需要自己做点阵字体
  • 测试麻烦, canvas 导图片 不用重复打印 (虽然耗材便宜随便造)
  • 打印起来有点糊, 不是很好调整

总体来讲就是麻烦, 如果不是特殊场景, 算力不够之类的, 就不要使用 指令一个个弄了, 合成个 BMP 传去即可, 不过要注意, 这里的 BMP 最好是单色的, 大小小于80K(你打印机 Flash 的大小)

// 这个函数和 send_command 一致, 但是这个不进行转码
async write(buffer) {
    return new Promise((s, j) => {
      this.socket.write(buffer, err => {
        if (err) {
          return j();
        } else {
          return s();
        }
      })
    })
  }

const bmp_data = readFileSync('./test.bmp')

// 下载图片到打印机
// DOWNLOAD "文件名",<数据段长度>
await this.write(
  Buffer.concat([
    new Buffer('DOWNLOAD "TEMP.BMP",' +  bmp_data.length + ','),
    bmp_data,
    new Buffer('\n')
  ])
)

// 画图像 PUTBMP <X>,<Y>,<文件名>
await printer.send_command('PUTBMP 0,0,"TEST.BMP"')

Nodejs PNG 转 单色BMP#

这个就比较套娃了

  • 先 Canvas 生成图片, png 格式
  • jimp 吧 png 转 bmp
  • 然后 bmp-ts 吧 bmp 转为1位调色板的, 去除多的颜色缩小文件体积

虽然有个库可以直接话 bmp 但是要求字体是点阵字体比较麻烦

// 以上图片省略

const bmp = await Jimp.read(canvas.toBuffer('image/png'))
const bmpData = BMP.encode({
  data: bmp.bitmap.data,
  bitPP: 1,
  width: 320,
  height: 240
} as any );
await printer.write(
  Buffer.concat([
    new Buffer('DOWNLOAD "TEST.BMP",' +  bmpData.data.length + ','),
    bmpData.data,
    new Buffer('\n')
  ])
)

后~#

有啥问题可以留言问~

除另有声明外,本博客文章均采用 知识共享(Creative Commons) 署名-非商业性使用-相同方式共享 3.0 中国大陆许可协议 进行许可。