图片需要通过 canvas进行处理 预先在canvas上绘制然后读取图片
打印途中报 10007 特性不支持 解决方案 可以看文章最后的描述

设置图片

chooseImage() {
      this.clearCanvas();
      const ctx = uni.createCanvasContext("secondCanvas", this);
      uni.downloadFile({
        // uni.chooseImage({
        url: "http://xxx.Jpeg", //仅为示例,并非真实的资源
        // url: pointDataUrl,
        success: (res) => {
          console.log("res", res);
          if (res.statusCode === 200) {
            // const tempFilePath = res.tempFilePaths[0];
            const tempFilePath = res.tempFilePath;
            uni.getImageInfo({
              src: tempFilePath,
              success: (res) => {
                // 打印宽度须是8的整数倍,这里处理掉多余的,使得宽度合适,不然有可能乱码
                // paperWidth 用于设置打印机的宽度 最大打印宽度 产品说明书上有写 我的是576点
                const mw = this.paperWidth % 8; 
                // 计算出宽度以及高度
                const w = mw === 0 ? this.paperWidth : this.paperWidth - mw;
                // 等比算出图片的高度
                const h = Math.floor((res.height * w) / res.width);
                // 设置canvas宽高
                this.img = tempFilePath;
                // 这边是进行设置画布高度
                this.canvasHeight = h;
                this.canvasWidth = w;
                console.log("this.canvasHeight", this.canvasHeight);
                console.log("this.canvasWidth", this.canvasWidth);
                // 使用nextTick 让canvas宽度生效
                this.$nextTick(() => {
                  // 在canvas 画一张图片
                  ctx.fillStyle = "rgba(255,255,255,1)";
                  ctx.clearRect(0, 0, w, h);
                  ctx.fillRect(0, 0, w, h);
                  // 进行绘画图片
                  ctx.drawImage(tempFilePath, 0, 0, w, h);
                  ctx.draw(false, () => {});
                });
              },
              fail: (res) => {},
            });
          }
        },
      });
    },

设置完图片后 我们就可以开始连接蓝牙打印机了

连接打印机

function uniAsyncPromise(name, options) {
  return new Promise((resolve, reject) => {
    uni[name]({
      ...(options || {}),
      // ...options,
      success: (res) => {
        resolve(res);
      },
      fail: (err) => {
        reject(err);
      },
    });
  });
}
function openBlue() {
  return uniAsyncPromise("openBluetoothAdapter");
}

function startBluetoothDevicesDiscovery(option) {
  console.log("开始蓝牙扫描");
  uniAsyncPromise("startBluetoothDevicesDiscovery", option).then((res) => {
    // console.log("正在搜寻蓝牙设备", res);
  });
}

function getConnectedBluetoothDevices(option) {
  console.log("开始获取已连接设备");
  return uniAsyncPromise("getConnectedBluetoothDevices", option);
}
function stopBlueDevicesDiscovery() {
  //监听寻找到新设备的事件
  console.log("停止蓝牙扫描");
  return uniAsyncPromise("stopBluetoothDevicesDiscovery").then((res) => {
    // console.log("停止搜寻蓝牙设备", res);
  });
}
function createBLEConnection(deviceId, sucess, fail) {
  //连接蓝牙设备
  console.log("连接蓝牙设备", deviceId);
  uniAsyncPromise("createBLEConnection", {
    deviceId,
  })
    .then((res) => {
      //连接成功可选择停止搜索蓝牙
      //stopBlueDevicesDiscovery();
      console.log("连接成功");
      sucess &&
        sucess({
          res: res,
        });
    })
    .catch((res) => {
      console.log("连接设备异常" + res);
      fail &&
        fail({
          res: res,
        });
    });
}
function getBLEDeviceServices(deviceId, success, fail) {
  console.log("获取ServiceId", deviceId);
  //加延迟避免取不到service
  setTimeout(() => {
    uniAsyncPromise("getBLEDeviceServices", {
      deviceId: deviceId,
    })
      .then((res) => {
        console.log("服务", res);
        success &&
          success({
            serviceId: res.services,
          });
      })
      .catch((res) => {
        //getBLEDeviceServices(deviceId, success, fail);
        console.log("获取ServiceId异常" + res);
        fail &&
          fail({
            res: res,
          });
      });
  }, 1000);
}
function getDeviceCharacteristics(deviceId, services, success, fail) {
  //services = services.slice(0);
  console.log("获取Characteristics", deviceId, services);
  if (services.length) {
    const serviceId = services.shift().uuid;
    console.log("ServceID ", serviceId);
    uniAsyncPromise("getBLEDeviceCharacteristics", {
      deviceId,
      serviceId,
    })
      .then((res) => {
        let finished = false;
        let write = false;
        let notify = false;
        let indicate = false;
        var readId;
        var writeId;
        for (var i = 0; i < res.characteristics.length; i++) {
          if (!notify) {
            notify = res.characteristics[i].properties.notify;
            if (notify) readId = res.characteristics[i].uuid;
          }
          if (!indicate) {
            indicate = res.characteristics[i].properties.indicate;
            if (indicate) readId = res.characteristics[i].uuid;
          }
          if (!write) {
            write = res.characteristics[i].properties.write;
            writeId = res.characteristics[i].uuid;
          }
          if ((notify || indicate) && write) {
            /* 获取蓝牙特征值uuid */
            success &&
              success({
                serviceId,
                writeId: writeId,
                readId: readId,
              });
            finished = true;
            break;
          }
        }

        if (!finished) {
          getDeviceCharacteristics(deviceId, services, success, fail);
        }
      })
      .catch((res) => {
        getDeviceCharacteristics(deviceId, services, success, fail);
      });
  } else {
    fail && fail();
  }
}

思路就是
1.先扫描蓝牙 选中具体的 deviceId后 进行连接蓝牙
2.连接蓝牙后 进行连接服务
3.服务连接后 进行获取特性

分为三步 三步完事后 基本上就连上蓝牙了

连完蓝牙 画完图片之后呢 我们就可以获取到具体的点位信息了

处理图片信息

//获取画布里的图片数据
      uni.canvasGetImageData({
        canvasId: "secondCanvas", // canvas 唯一id
        x: 0,
        y: 0,
        width: canvasWidth, // 之前保存的画布宽高
        height: canvasHeight,
        success: (res) => {
          const pix = res.data;
          const opt = { // 获取蓝牙配置文件
            deviceId: this.deviceId, // 设备id
            serviceId: this.serviceId, // 设备服务
            characteristicId: this.characteristicId, // 设备特性
            printAlign: "left",
            lineByLine: true,
            onProgress: (percentage) => {
              self.percentage = percentage; // 回调的进度
            },
            lasterSuccess: () => {
              console.log("laster success");
              self.printing = false;
              self.percentage = 0;
              uni.showModal({
                title: "提示",
                content: "数据已发送完,请检查打印的内容是否正常",
                showCancel: false,
                confirmText: "好的",
              });
            },
          };
          this.printing = true;
          //打印图片
          printImage(
            opt,
            // 将图片数据转成位图数据
            overwriteImageData({
              threshold: threshold[0],
              imageData: pix,
              width: canvasWidth,
              height: canvasHeight,
            })
          );
        },
        complete: () => {},
      });

打印

获取完图片后 调用打印 打印代码如下:

// 将打印内容进行转换 区分逐行打印和整体打印
function getImageCommandArray(opt = {}, imageInfo = {}) {
  const lineByLine =
    typeof opt.lineByLine !== "boolean" ? true : opt.lineByLine;
  const width = imageInfo.width;
  const h = imageInfo.height;
  const xl = width % 256;
  const xh = (width - xl) / 256;
  const yl = h % 256;
  const yh = (h - yl) / 256;
  //打印图片的十进制指令数组
  let command = [];

  if (lineByLine) {
    // 分段逐行的指令
    command = command.concat([29, 118, 48, 0, xl, xh, 1, 0]);
  } else {
    // 非分段逐行的指令
    command = command.concat([29, 118, 48, 0, xl, xh, yl, yh]);
  }

  return command;
}
// 获取图片信息
function overwriteImageData(data) {
  let sendWidth = data.width,
    sendHeight = data.height;
  const threshold = data.threshold || 180;
  let sendImageData = new ArrayBuffer((sendWidth * sendHeight) / 8);
  sendImageData = new Uint8Array(sendImageData);
  let pix = data.imageData;
  const part = [];
  let index = 0;
  for (let i = 0; i < pix.length; i += 32) {
    //横向每8个像素点组成一个字节(8位二进制数)。
    for (let k = 0; k < 8; k++) {
      const grayPixle1 = grayPixle(pix.slice(i + k * 4, i + k * 4 + (4 - 1)));
      //阈值调整
      if (grayPixle1 > threshold) {
        //灰度值大于128位   白色 为第k位0不打印
        part[k] = 0;
      } else {
        part[k] = 1;
      }
    }
    let temp = 0;
    for (let a = 0; a < part.length; a++) {
      temp += part[a] * Math.pow(2, part.length - 1 - a);
    }
    //开始不明白以下算法什么意思,了解了字节才知道,一个字节是8位的二进制数,part这个数组存的0和1就是二进制的0和1,传输到打印的位图数据的一个字节是0-255之间的十进制数,以下是用相权相加法转十进制数,理解了这个就用上面的for循环替代了
    // const temp =
    //   part[0] * 128 +
    //   part[1] * 64 +
    //   part[2] * 32 +
    //   part[3] * 16 +
    //   part[4] * 8 +
    //   part[5] * 4 +
    //   part[6] * 2 +
    //   part[7] * 1;
    sendImageData[index++] = temp;
  }
  return {
    array: Array.from(sendImageData),
    width: sendWidth / 8,
    height: sendHeight,
  };
}

function getPrintImageWriteArray(opt = {}, imageInfo = {}) {
  const lineByLine =
    typeof opt.lineByLine !== "boolean" ? true : opt.lineByLine;
  const width = imageInfo.width;
  let arr = imageInfo.array;
  let writeArray = [];
  const iniTcommand = []
    .concat(printCommand.clear)
    .concat(printCommand[opt.printAlign || "left"]);
  const command = getImageCommandArray(opt, imageInfo);
  writeArray.push(new Uint8Array(iniTcommand));
  // 分段逐行打印的数据
  if (lineByLine) {
    for (let i = 0; i < arr.length / width; i++) {
      const subArr = arr.slice(i * width, i * width + width);
      const tempArr = command.concat(subArr);
      writeArray.push(new Uint8Array(tempArr));
    }
  } else {
    // 非逐行打印
    writeArray.push(new Uint8Array(command.concat(arr)));
  }
  // writeArray.push(new Uint8Array([27, 74, 3]));
  return writeArray;
}

function sendDataToDevice(options) {
  let byteLength = options.value.byteLength;
  // 这里默认一次20个字发送
  const speed = options.onceByleLength || 20;
  options.reTry = options.reTry || 20;
  if (byteLength > 0) {
    uni.writeBLECharacteristicValue({
      ...options,
      // writeType:"writeNoResponse",
      value: options.value.slice(0, byteLength > speed ? speed : byteLength),
      success: function (res) {
        if (byteLength > speed) {
          sendDataToDevice({
            ...options,
            value: options.value.slice(speed, byteLength),
          });
        } else {
          options.lasterSuccess && options.lasterSuccess();
        }
      },
      fail: function (res) {
				// console.log('options.nowTry', options.nowTry);
        options.nowTry = options.nowTry ? (options.nowTry += 1) : 1;
        if (options.nowTry < options.reTry && res.errCode === 10007) {
          if (byteLength > speed) {
            sendDataToDevice({
              ...options,
              value: options.value.slice(speed, byteLength),
            });
          } else {
            options.lasterSuccess && options.lasterSuccess();
          }
        }
        options.onError && options.onError(res);
      },
    });
  }
}


export function sendDataToPrint(options, allUint8Array) {
  const allLenth = allUint8Array.length;
  const writeArrayCopyer = allUint8Array.slice(0);
  const print = (options, writeArray) => {
    if (writeArray.length) {
      sendDataToDevice({
        ...options,
        value: writeArray.shift().buffer,
        lasterSuccess: () => {
          if (writeArray.length) {
            print(options, writeArray);
          } else {
            options.lasterSuccess && options.lasterSuccess();
          }
          // console.log("writeArray.length", writeArray.length);
          options.onProgress &&
              options.onProgress(Math.floor(((allLenth - writeArray.length) / allLenth) * 100));
        },
      });
    }
  };
  print(options, writeArrayCopyer);
}



function printImage(opt, imageInfo) {
  sendDataToPrint(
    opt,
    getPrintImageWriteArray(
      {
        printAlign: opt.printAlign,
        lineByLine: opt.lineByLine,
      },
      imageInfo
    )
  );
}

遇到部分错误解决方法

  1. 如果设备特性 write=true 的打印中途 有报错 10007 进行重新发当前数据包(有很多种原因会导致失败 如果重新发数据包可以继续 那么就可以忽略这个报错) 并限制重试次数;
  2. 如果遇到查找不到蓝牙设备 可以尝试开启定位在重试,因为安卓版本高了需要开启定位;

案例参照:https://juejin.cn/post/7002878986472652830

Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐