郭金辉 發表於 2022-9-13 16:50:00

记录--uni-app实现蓝牙打印小票

<h3 id="tid-QnSCH7"><span class="ne-text">这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助</span></h3>
<p><span class="ne-text"><img src="https://img2020.cnblogs.com/blog/2149129/202107/2149129-20210719135854680-672185839.jpg" width="205" height="383"></span></p>
<div>
<div>
<h1 data-id="heading-0">说明</h1>
<p>基于uni-app开发,调用官方蓝牙相关api实现连接蓝牙与向蓝牙热敏打印机发送字节流,可打印文字,二维码,图片,调整字体大小等,本文提供大概思路</p>
<h1 data-id="heading-1">结构</h1>
<p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f634010634704db59563c5e0cd7d5afa~tplv-k3u1fbpfcp-zoom-in-crop-mark:3024:0:0:0.awebp" alt="image.png" class="medium-zoom-image" loading="lazy"></p>
<ul>
<li>bluetooth.js 蓝牙连接相关模块封装</li>
<li>commands.js 打印十六进制相关代码库</li>
<li>gbk.js 编码转换库地址</li>
<li>printerjobs.js 打印实现库</li>
</ul>
<h2 data-id="heading-2">bluetooth.js</h2>
<p>蓝牙连接相关封装代码</p>
</div>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">class Bluetooth {

    constructor() {
      this.isOpenBle = false;
      this.deviceId = "";
      this.serviceId = "";
      this.writeId = "";
      this.notifyId = "";
      this.BluetoothConnectStatus = false this.printStatus = false this.init()
    }
    init() {
      this.closeBluetoothAdapter().then(() = &gt;{
            console.log("init初始关闭蓝牙模块") this.openBluetoothAdapter().then(() = &gt;{
                console.log("init初始化蓝牙模块") this.reconnect() //自动连接蓝牙设备
            })

      })
    }

    showToast(title) {
      uni.showToast({
            title: title,
            icon: 'none',
            'duration': 2000
      });
    }

    openBluetoothAdapter() {
      return new Promise((resolve, reject) = &gt;{
            uni.openBluetoothAdapter({
                success: res = &gt;{
                  this.isOpenBle = true;
                  resolve(res);
                },
                fail: err = &gt;{
                  this.showToast(`蓝牙未打开`);
                  reject(err);
                },
            });
      });

    }

    startBluetoothDevicesDiscovery() {
      if (!this.isOpenBle) {
            this.showToast(`初始化蓝牙模块失败`) return;
      }

      let self = this;
      uni.showLoading({
            title: '蓝牙搜索中'
      }) return new Promise((resolve, reject) = &gt;{
            setTimeout(() = &gt;{
                uni.startBluetoothDevicesDiscovery({
                  success: res = &gt;{
                        resolve(res)
                  },
                  fail: res = &gt;{
                        self.showToast(`搜索设备失败` + JSON.stringify(err));
                        reject(err);
                  }
                })
            },
            300);
      });
    }

    stopBluetoothDevicesDiscovery() {
      let self = this;
      return new Promise((resolve, reject) = &gt;{
            uni.stopBluetoothDevicesDiscovery({
                success: e = &gt;{
                  uni.hideLoading();
                },
                fail: e = &gt;{
                  uni.hideLoading();
                  self.showToast(`停止搜索蓝牙设备失败` + JSON.stringify(err));
                }
            })
      });
    }

    createBLEConnection() {
      //设备deviceId
      let deviceId = this.deviceId;
      let self = this;

      // uni.showLoading({
      //         mask: true,
      //         title: '设别连接中,请稍候...'
      // })
      console.log(this.deviceId);
      return new Promise((resolve, reject) = &gt;{
            uni.createBLEConnection({
                deviceId,
                success: (res) = &gt;{
                  console.log("res:createBLEConnection " + JSON.stringify(res));
                  resolve(res)
                },
                fail: err = &gt;{
                  uni.hideLoading();
                  self.showToast(`停止搜索蓝牙设备失败` + JSON.stringify(err));
                  reject(err);
                }
            })
      });
    }

    //获取蓝牙设备所有服务(service)
    getBLEDeviceServices() {
      let _serviceList = [];
      let deviceId = this.deviceId;
      let self = this;

      return new Promise((resolve, reject) = &gt;{
            setTimeout(() = &gt;{
                uni.getBLEDeviceServices({
                  deviceId,
                  success: res = &gt;{
                        for (let service of res.services) {
                            if (service.isPrimary) {
                              _serviceList.push(service);
                            }
                        }
                        uni.hideLoading();
                        console.log("_serviceList: " + JSON.stringify(_serviceList));
                        resolve(_serviceList)
                  },
                  fail: err = &gt;{
                        uni.hideLoading();
                        // self.showToast(`获取设备Services` + JSON.stringify(err));
                        reject(err);
                  },
                })
            },
            500);
      });
    }

    //获取蓝牙设备某个服务中所有特征值(characteristic)
    getBLEDeviceCharacteristics() {
      // console.log("getBLEDeviceCharacteristics")
      let deviceId = this.deviceId;
      let serviceId = this.serviceId;

      let self = this;
      return new Promise((resolve, reject) = &gt;{
            uni.getBLEDeviceCharacteristics({
                deviceId,
                serviceId,
                success: res = &gt;{
                  for (let _obj of res.characteristics) {
                        //获取notify
                        if (_obj.properties.notify) {
                            self.notifyId = _obj.uuid;
                            uni.setStorageSync('notifyId', self.notifyId);
                        }
                        //获取writeId
                        if (_obj.properties.write) {
                            self.writeId = _obj.uuid;
                            uni.setStorageSync('writeId', self.writeId);
                        }
                  }

                  //console.log("res:getBLEDeviceCharacteristics " + JSON.stringify(res));
                  let result = {
                        'notifyId': self.notifyId,
                        'writeId': self.writeId
                  };
                  // self.showToast(`获取服务中所有特征值OK,${JSON.stringify(result)}`);
                  this.BluetoothStatus = true resolve(result)
                },
                fail: err = &gt;{
                  self.showToast(`getBLEDeviceCharacteristics` + JSON.stringify(err));
                  reject(err);
                }
            })
      });
    }

    //断开联链接
    closeBLEConnection() {
      let deviceId = this.deviceId;
      uni.closeBLEConnection({
            deviceId,
            success(res) {
                console.log("closeBLEConnection" + res)
            }
      })
    }

    notifyBLECharacteristicValue() {
      let deviceId = this.deviceId;
      let serviceId = this.serviceId;
      let characteristicId = this.notifyId;

      uni.notifyBLECharacteristicValueChange({
            state: true,
            // 启用 notify 功能
            deviceId,
            serviceId,
            characteristicId,
            success(res) {
                uni.onBLECharacteristicValueChange(function(res) {
                  console.log('onBLECharacteristicValueChange', res);
                });
            },
            fail(res) {
                console.log('notifyBLECharacteristicValueChange failed:' + res.errMsg);

            }
      });
    }

    writeBLECharacteristicValue(buffer) {
      let deviceId = this.deviceId;
      let serviceId = this.serviceId;
      let characteristicId = this.writeId;

      // console.log(deviceId);
      // console.log(serviceId);
      // console.log(characteristicId);
      return new Promise((resolve, reject) = &gt;{
            uni.writeBLECharacteristicValue({
                deviceId,
                serviceId,
                characteristicId,
                value: buffer,
                success(res) {
                  // console.log('message发送成功', JSON.stringify(res));
                  resolve(res);
                },
                fail(err) {
                  // console.log('message发送失败', JSON.stringify(err));
                  reject(err);
                }
            });
      });
    }

    closeBluetoothAdapter() {
      return new Promise((resolve, reject) = &gt;{
            uni.closeBluetoothAdapter({
                success: res = &gt;{
                  resolve()
                }
            });
      })

    }

    //若APP在之前已有搜索过某个蓝牙设备,并成功建立连接,可直接传入之前搜索获取的 deviceId 直接尝试连接该设备,无需进行搜索操作。
    reconnect() { (async() = &gt;{
            try {
                this.deviceId = this.deviceId || uni.getStorageSync("deviceId");
                this.serviceId = this.serviceId || uni.getStorageSync("serviceId");
                console.log("this.deviceId", this.deviceId) console.log("this.serviceId", this.serviceId) let result1 = await this.createBLEConnection();
                console.log("createBLEConnection: " + JSON.stringify(result1));

                let result2 = await this.getBLEDeviceServices();
                console.log("getBLEDeviceServices: " + JSON.stringify(result2));

                let result3 = await this.getBLEDeviceCharacteristics();
                console.log("getBLEDeviceCharacteristics: " + JSON.stringify(result3));
                this.BluetoothConnectStatus = true this.showToast("蓝牙打印设备连接成功")
                // this.writeId = uni.getStorageSync("writeId");
                // this.notifyId = uni.getStorageSync("notifyId");
            } catch(err) {
                console.log("err: " + err);
                // this.showToast("蓝牙打印设备连接失败")
            }

      })();
    }
}

export
default Bluetooth;</pre>
</div>
<h2 data-id="heading-3">commands.js</h2>
<p>打印机ESC/POS十六进制编码库</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">/**
* 修改自https://github.com/song940/node-escpos/blob/master/commands.js
* ESC/POS _ (Constants)
*/
var _ = {
LF: ,
FS: ,
FF: ,
GS: ,
DLE: ,
EOT: ,
NUL: ,
ESC: ,
EOL: '\n',
};

/**
*
* @type {Object}
*/
_.FEED_CONTROL_SEQUENCES = {
CTL_LF: ,   // Print and line feed
CTL_GLF: ,   // Print and feed paper (without spaces between lines)
CTL_FF: ,   // Form feed
CTL_CR: ,   // Carriage return
CTL_HT: ,   // Horizontal tab
CTL_VT: ,   // Vertical tab
};

_.CHARACTER_SPACING = {
CS_DEFAULT: ,
CS_SET:
};

_.LINE_SPACING = {
LS_DEFAULT: ,
LS_SET:
};

/**
*
* @type {Object}
*/
_.HARDWARE = {
HW_INIT: , // Clear data in buffer and reset modes
HW_SELECT: , // Printer select
HW_RESET: , // Reset printer hardware
Print: //Print and feed paper
};

/**
*
* @type {Object}
*/
_.CASH_DRAWER = {
CD_KICK_2: , // Sends a pulse to pin 2 []
CD_KICK_5: , // Sends a pulse to pin 5 []
};

/**
*
* @type {Object}
*/
_.MARGINS = {
BOTTOM: , // Fix bottom size
LEFT: , // Fix left size
RIGHT: , // Fix right size
};

/**
*
* @type {Object}
*/
_.PAPER = {
PAPER_FULL_CUT: , // Full cut paper
PAPER_PART_CUT: , // Partial cut paper
PAPER_CUT_A: , // Partial cut paper
PAPER_CUT_B: , // Partial cut paper
};

/**
*
* @type {Object}
*/
_.TEXT_FORMAT = {
TXT_NORMAL: , // Normal text
TXT_2HEIGHT: , // Double height text
TXT_2WIDTH: , // Double width text
TXT_4SQUARE: , // Double width &amp; height text

TXT_UNDERL_OFF: , // Underline font OFF
TXT_UNDERL_ON: , // Underline font 1-dot ON
TXT_UNDERL2_ON: , // Underline font 2-dot ON
TXT_BOLD_OFF: , // Bold font OFF
TXT_BOLD_ON: , // Bold font ON
TXT_ITALIC_OFF: , // Italic font ON
TXT_ITALIC_ON: , // Italic font ON

TXT_FONT_A: , // Font type A
TXT_FONT_B: , // Font type B
TXT_FONT_C: , // Font type C

TXT_ALIGN_LT: , // Left justification
TXT_ALIGN_CT: , // Centering
TXT_ALIGN_RT: , // Right justification
};

/**
*
* @type {Object}
*/
_.BARCODE_FORMAT = {
BARCODE_TXT_OFF: , // HRI barcode chars OFF
BARCODE_TXT_ABV: , // HRI barcode chars above
BARCODE_TXT_BLW: , // HRI barcode chars below
BARCODE_TXT_BTH: , // HRI barcode chars both above and below

BARCODE_FONT_A: , // Font type A for HRI barcode chars
BARCODE_FONT_B: , // Font type B for HRI barcode chars

BARCODE_HEIGHT: function (height) { // Barcode Height
    return ;
},
BARCODE_WIDTH: function (width) {   // Barcode Width
    return ;
},
BARCODE_HEIGHT_DEFAULT: , // Barcode height default:100
BARCODE_WIDTH_DEFAULT: , // Barcode width default:1

BARCODE_UPC_A: , // Barcode type UPC-A
BARCODE_UPC_E: , // Barcode type UPC-E
BARCODE_EAN13: , // Barcode type EAN13
BARCODE_EAN8: , // Barcode type EAN8
BARCODE_CODE39: , // Barcode type CODE39
BARCODE_ITF: , // Barcode type ITF
BARCODE_NW7: , // Barcode type NW7
BARCODE_CODE93: , // Barcode type CODE93
BARCODE_CODE128: , // Barcode type CODE128
};

/**
*
* @type {Object}
*/
_.IMAGE_FORMAT = {
S_RASTER_N: , // Set raster image normal size
S_RASTER_2W: , // Set raster image double width
S_RASTER_2H: , // Set raster image double height
S_RASTER_Q: , // Set raster image quadruple
};

/**
*
* @type {Object}
*/
_.BITMAP_FORMAT = {
BITMAP_S8: ,
BITMAP_D8: ,
BITMAP_S24: ,
BITMAP_D24:
};

/**
*
* @type {Object}
*/
_.GSV0_FORMAT = {
GSV0_NORMAL: ,
GSV0_DW: ,
GSV0_DH: ,
GSV0_DWDH:
};

/**
*
* @type {string}
*/
_.BEEP = ; // Printer Buzzer pre hex

/**
*
* @type {Object}
*/

_.COLOR = {
0: , // black
1: // red
};

/**
*
* @type {}
*/
module.exports = _;</pre>
</div>
<h2 data-id="heading-4">printerjobs.js</h2>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">const commands = require('./commands');
const gbk = require('./gbk');

const printerJobs = function() {
        this._queue = Array.from(commands.HARDWARE.HW_INIT);
        this._enqueue = function(cmd) {
                this._queue.push.apply(this._queue, cmd);
        }
};

/**
* 增加打印内容
* @param{string} content文字内容
*/
printerJobs.prototype.text = function(content) {
        if (content) {
                let uint8Array = gbk.encode(content);
                let encoded = Array.from(uint8Array);
                this._enqueue(encoded);
        }
        return this;
};

/**
* 打印文字
* @param{string} content文字内容
*/
printerJobs.prototype.print = function(content) {
        this.text(content);
        // this._enqueue(commands.LF);
        return this;
};
printerJobs.prototype.printL = function(content) {
        this.text(content);
        this._enqueue(commands.LF);
        return this;
};

printerJobs.prototype.printImage = function(content) {

        if (content) {
                const cmds = [].concat(, content);
                // console.log("cmds",cmds)
                this._enqueue(cmds);
                this._enqueue(commands.LF);
        }

        return this;
};


/**
* 打印文字并换行
* @param{string}content文字内容
*/
printerJobs.prototype.println = function(content = '') {
        return this.print(content + commands.EOL);
};

/**
* 设置对齐方式
* @param {string} align 对齐方式 LT/CT/RT
*/
printerJobs.prototype.setAlign = function(align) {
        this._enqueue(commands.TEXT_FORMAT['TXT_ALIGN_' + align.toUpperCase()]);
        return this;
};

/**
* 设置字体
* @param{string} family A/B/C
*/
printerJobs.prototype.setFont = function(family) {
        this._enqueue(commands.TEXT_FORMAT['TXT_FONT_' + family.toUpperCase()]);
        return this;
};

/**
* 设定字体尺寸
* @param{number} width 字体宽度 1~2
* @param{number} height 字体高度 1~2
*/
printerJobs.prototype.setSize = function(width, height) {
        if (2 &gt;= width &amp;&amp; 2 &gt;= height) {
                this._enqueue(commands.TEXT_FORMAT.TXT_NORMAL);
                if (2 === width &amp;&amp; 2 === height) {
                        this._enqueue(commands.TEXT_FORMAT.TXT_4SQUARE);
                } else if (1 === width &amp;&amp; 2 === height) {
                        this._enqueue(commands.TEXT_FORMAT.TXT_2HEIGHT);
                } else if (2 === width &amp;&amp; 1 === height) {
                        this._enqueue(commands.TEXT_FORMAT.TXT_2WIDTH);
                }
        }
        return this;
};

/**
* 设定字体是否加粗
* @param{boolean} bold
*/
printerJobs.prototype.setBold = function(bold) {
        if (typeof bold !== 'boolean') {
                bold = true;
        }
        this._enqueue(bold ? commands.TEXT_FORMAT.TXT_BOLD_ON : commands.TEXT_FORMAT.TXT_BOLD_OFF);
        return this;
};

/**
* 设定是否开启下划线
* @param{boolean} underline
*/
printerJobs.prototype.setUnderline = function(underline) {
        if (typeof underline !== 'boolean') {
                underline = true;
        }
        this._enqueue(underline ? commands.TEXT_FORMAT.TXT_UNDERL_ON : commands.TEXT_FORMAT.TXT_UNDERL_OFF);
        return this;
};

/**
* 设置行间距为 n 点行,默认值行间距是 30 点
* @param {number} n 0≤n≤255
*/
printerJobs.prototype.setLineSpacing = function(n) {
        if (n === undefined || n === null) {
                this._enqueue(commands.LINE_SPACING.LS_DEFAULT);
        } else {
                this._enqueue(commands.LINE_SPACING.LS_SET);
                this._enqueue();
        }
        return this;
};

/**
* 打印空行
* @param {number} n
*/
printerJobs.prototype.lineFeed = function(n = 1) {
        return this.print(new Array(n).fill(commands.EOL).join(''));
};

/**
*设置字体颜色,需要打印机支持
*@param{number} color - 0 默认颜色黑色 1 红色
*/
printerJobs.prototype.setColor = function(color) {
        this._enqueue(commands.COLOR);
        return this;
};

/**
* https://support.loyverse.com/hardware/printers/use-the-beeper-in-a-escpos-printers
* 蜂鸣警报,需要打印机支持
* @param{number} n    蜂鸣次数,1-9
* @param{number} t 蜂鸣长短,1-9
*/
printerJobs.prototype.beep = function(n, t) {
        this._enqueue(commands.BEEP);
        this._enqueue();
        return this;
};

/**
* 清空任务
*/
printerJobs.prototype.clear = function() {
        this._queue = Array.from(commands.HARDWARE.HW_RESET);
        // this._enqueue(commands.HARDWARE.Print);
        return this;
};

/**
* 返回ArrayBuffer
*/
printerJobs.prototype.buffer = function() {
        return new Uint8Array(this._queue).buffer;
};

module.exports = printerJobs;</pre>
</div>
<h1 data-id="heading-5">代码实现</h1>
<p>封装蓝牙连接,搜索,断开相关操作模块</p>
<h2 data-id="heading-6">蓝牙搜索</h2>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">startBluetoothDeviceDiscovery() {
    let self = this;
    self.tempDeviceList = [];

    uni.startBluetoothDevicesDiscovery({
      success: res = &gt;{
            uni.onBluetoothDeviceFound(devices = &gt;{
                // console.log("发现设备: " + JSON.stringify(devices));
                if (!self.tempDeviceList.some(item = &gt;{
                  return item.deviceId === devices.devices.deviceId || item.name === devices.devices.name
                })) {
                  // console.log("new", devices.devices)
                  self.tempDeviceList.push(devices.devices)
                }
            });

            this.connect = false this.$refs.popup.open()
      },
      fail: err = &gt;{
            uni.showToast({
                title: '搜索设备失败' + JSON.stringify(err),
                icon: 'none'
            })
      }
    })
},</pre>
</div>
<h2 data-id="heading-7">搜索完成选择设备</h2>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">async select_deviceId(item) {
    this.deviceId = item.deviceId;
    this.$bluetooth.deviceId = item.deviceId;
    uni.setStorageSync('deviceId', this.$bluetooth.deviceId);
    this.serviceList = [];

    try {
      //1.链接设备
      let result = await this.$bluetooth.createBLEConnection();
      //2.寻找服务
      let result2 = null setTimeout(async() = &gt;{
            result2 = await this.$bluetooth.getBLEDeviceServices();
            console.log("获取服务: " + JSON.stringify(result2));
            this.serviceList = result2;

            console.log("serviceList", this.serviceList.length)

            if (this.serviceList.uuid) {
                this.select_service(this.serviceList.uuid)
            } else {
                uni.showToast({
                  title: '不是打印设备',
                  icon: 'none'
                })
            }

      },
      1000)

    } catch(e) {
      //TODO handle the exception
      console.log("e: " + JSON.stringify(e));
    }
},</pre>
</div>
<h2 data-id="heading-8">选中服务</h2>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">async select_service(res) {
    console.log("select_service", res)

    this.$bluetooth.serviceId = res;
    console.log("this.$bluetooth.serviceId", this.$bluetooth.serviceId) uni.setStorageSync('serviceId', res);

    try {
      let result = await this.$bluetooth.getBLEDeviceCharacteristics();
      console.log("resultresult", result) this.$refs.popup.close()

      uni.showToast({
            title: "连接成功"
      })
      // this.pickUpOnce()
    } catch(e) {
      //TODO handle the exception
      console.log("e: " + JSON.stringify(e));
    }
},</pre>
</div>
<h2 data-id="heading-9">打印内容组合</h2>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">async writeBLECharacteristicValueTongzhishu() {
        let Qrcode_res = await this.get_Qrcode()
        let sign = await this.getSign()
        console.log("sign")
        let printerJobs = new PrinterJobs()
        let p = printerJobs.setAlign('ct')
                .setBold(true)
                .printL("打印测试")

        let buffer = printerJobs.buffer();
        this.printbuffs(buffer);
},</pre>
</div>
<h2 data-id="heading-10">打印内容组合</h2>
<p>主要是实现打印编码推送循环,手机蓝牙可能出现编码发送失败情况,这个时候就是要循环保证每个字节准确推送</p>
<div class="cnblogs_Highlighter">
<pre class="brush:csharp;gutter:true;">printbuffs(buffer, fun) {
    console.log("printbuffs", buffer.byteLength) const maxChunk = 8;
    let p = Promise.resolve();
    for (let i = 0, j = 0, length = buffer.byteLength; i &lt; length; i += maxChunk, j++) {
      let subPackage = buffer.slice(i, i + maxChunk &lt;= length ? (i + maxChunk) : length);
      p = p.then(() = &gt;{
            if (i == 0) {
                this.$bluetooth.printStatus = true this.$refs.loading.open();

            }
            if ((i + maxChunk) &gt;= length) {
                console.log("printEnd")

                setTimeout(() = &gt;{
                  this.$bluetooth.printStatus = false this.$refs.loading.close();
                },
                1000)
            }
            return this.printbuff(subPackage)
      })
    }
    p = p.then(() = &gt;{
      console.log("printEve") fun()
    })

},
async printbuff(buffer) {
    while (true) {
      try {
            await this.$bluetooth.writeBLECharacteristicValue(buffer);
            break;
      } catch(e) {}
    }
},</pre>
</div>
<h3 id="tid-A8dYk6"><span class="ne-text">本文转载于:</span></h3>
<h3 id="tid-SwZPdd">https://juejin.cn/post/7001752261722537991</h3>
<h3 id="tid-D8HBxE">如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。</h3>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/2149129/202107/2149129-20210719144042684-15122820.jpg" width="212" height="209"></p>
</div><br><br>
来源:https://www.cnblogs.com/smileZAZ/p/16689771.html
頁: [1]
查看完整版本: 记录--uni-app实现蓝牙打印小票