uni-app에서 블루투스를 통해 열전사 프린터 제어하기

uni-app을 사용하여 저전력 블루투스(BLE) 방식의 열전사 프린터와 연결하고 데이터를 송신하는 과정은 일련의 단계로 구성된다. 아래는 실제 하드웨어와 통신하기 위한 핵심 흐름과 코드 구현 예시이다.

주요 처리 순서:

  1. 블루투스 어댑터 초기화 (openBluetoothAdapter) – 실패 시 상태 변경 이벤트 리스너 등록
  2. 주변 BLE 장치 검색 시작 (startBluetoothDevicesDiscovery)
  3. 새 장치 발견 시 콜백 처리 (onBluetoothDeviceFound) – 특정 이름 또는 서비스로 필터링
  4. <4>장치와 연결 생성 (createBLEConnection) – 동시에 검색 중단 <5>연결된 장치의 서비스 목록 조회 (getBLEDeviceServices) <6>특정 서비스 내 특성값(Characteristic) 가져오기 (getBLEDeviceCharacteristics) <7>데이터 수신 감지 리스너 설정 (onBLECharacteristicValueChange) <8>프린터로 바이너리 명령 전송 (writeBLECharacteristicValue)

블루투스 초기화 및 자동 검색

initBluetooth() {
  uni.openBluetoothAdapter({
    success: () => {
      console.log('블루투스 어댑터 초기화 성공');
      this.startDeviceSearch();
    },
    fail: (err) => {
      console.error('블루투스 초기화 실패:', err);
      if (err.errCode === 10001) {
        uni.showToast({ title: '블루투스를 활성화하세요', icon: 'none' });
      }
      // 상태 변화 감지를 통해 나중에 재시도 가능
      uni.onBluetoothAdapterStateChange((state) => {
        if (state.available) this.initBluetooth();
      });
    }
  });
}

주변 기기 탐색 시작

startDeviceSearch() {
  uni.startBluetoothDevicesDiscovery({
    success: () => {
      console.log('기기 탐색 시작');
      this.listenForDevices();
    },
    fail: (err) => {
      console.error('탐색 시작 실패:', err);
    }
  });
}

기기 발견 이벤트 리스닝

listenForDevices() {
  uni.onBluetoothDeviceFound((result) => {
    result.devices.forEach(device => {
      if (device.name && device.name.includes('Printer')) { // 장치명 기반 필터링
        const deviceId = device.deviceId;
        uni.setStorageSync('PRINTER_DEVICE_ID', deviceId);
        console.log(`매칭된 프린터: ${device.name} (${deviceId})`);
        
        this.stopDeviceSearch(); // 성능 최적화를 위해 검색 중단
        this.connectToDevice(deviceId); // 연결 시도
      }
    });
  });
}

검색 중단 및 연결 설정

stopDeviceSearch() {
  uni.stopBluetoothDevicesDiscovery({
    success: () => console.log('기기 검색 중단 완료'),
    fail: (err) => console.warn('검색 중단 실패:', err)
  });
}

connectToDevice(deviceId) {
  uni.createBLEConnection({
    deviceId,
    success: () => {
      console.log('BLE 연결 성공');
      this.fetchServiceList(deviceId);
    },
    fail: (err) => {
      console.error('연결 실패:', err);
    }
  });
}

서비스 및 특성값 조회

연결 후에는 디바이스가 제공하는 서비스 UUID 목록을 가져와야 하며, 이 작업은 비동기적이므로 지연 처리가 필요하다.

fetchServiceList(deviceId) {
  setTimeout(() => { // 안정적인 응답을 위해 딜레이 추가
    uni.getBLEDeviceServices({
      deviceId,
      success: (res) => {
        const services = res.services.filter(s => s.uuid.includes('FFE0'));
        if (services.length > 0) {
          const serviceId = services[0].uuid;
          uni.setStorageSync('SERVICE_UUID', serviceId);
          this.fetchCharacteristics(deviceId, serviceId);
        }
      },
      fail: (err) => {
        console.error('서비스 조회 실패:', err);
      }
    });
  }, 1000);
}

fetchCharacteristics(deviceId, serviceId) {
  uni.getBLEDeviceCharacteristics({
    deviceId,
    serviceId,
    success: (res) => {
      const writableChar = res.characteristics.find(c => c.properties.write);
      if (writableChar) {
        uni.setStorageSync('CHARACTERISTIC_UUID', writableChar.uuid);
        uni.showToast({ title: '프린터 준비 완료', icon: 'success' });
      }
    },
    fail: (err) => {
      console.error('특성값 조회 실패:', err);
    }
  });
}

프린터로 데이터 전송

실제 인쇄 명령은 16진수 형식의 ArrayBuffer로 전송되어야 한다. 아래는 간단한 출력 예시:

sendPrintCommand() {
  const dataArrays = [
    new Uint8Array([0x1B, 0x40]),           // 프린터 초기화
    new Uint8Array([0x50, 0x72, 0x69, 0x6E, 0x74, 0x20, 0x54, 0x65, 0x73, 0x74]), // "Print Test"
    new Uint8Array([0x0A, 0x0A, 0x0A])      // 줄 바꿈 + 용지 절단
  ];

  const deviceId = uni.getStorageSync('PRINTER_DEVICE_ID');
  const serviceId = uni.getStorageSync('SERVICE_UUID');
  const charId = uni.getStorageSync('CHARACTERISTIC_UUID');

  dataArrays.forEach((data, index) => {
    const buffer = new ArrayBuffer(data.length);
    const view = new Uint8Array(buffer);
    view.set(data);

    uni.writeBLECharacteristicValue({
      deviceId,
      serviceId,
      characteristicId: charId,
      value: buffer,
      success: () => {
        console.log(`${index + 1}번째 명령 전송 성공`);
      },
      fail: (err) => {
        console.error('데이터 전송 실패:', err);
        this.handleErrorCode(err.errCode);
      }
    });
  });
}

에러 코드 처리

handleErrorCode(code) {
  const errorMessages = {
    10000: '블루투스 어댑터 미초기화',
    10001: '블루투스 어댑터 비활성화',
    10002: '장치를 찾을 수 없음',
    10003: '연결 실패',
    10004: '서비스 없음',
    10005: '특성값 없음',
    10006: '연결 끊김'
  };

  const message = errorMessages[code] || '알 수 없는 오류';
  uni.showToast({ title: message, icon: 'none' });

  if ([10000, 10001].includes(code)) {
    this.initBluetooth(); // 재시도 트리거
  }
}

페이지 로드 시 최초로 initBluetooth()를 호출하면 전체 흐름이 시작되며, 사용자가 인쇄 요청을 할 때마다 sendPrintCommand()를 실행하면 된다. 필요한 스토리지 키들은 사전에 정의하고, 실제 장치 사양에 맞게 UUID와 바이너리 포맷을 조정해야 한다.

태그: uni-app Bluetooth BLE thermal printer JavaScript

7월 29일 19:31에 게시됨