WEB Serial API를 사용하여 바코드 스캐너 연동하기

NameJM·2024년 7월 8일

설명

바코드 스캐너(물리적 장비)를 사용하여 사용자의 QR코드인식 기능 개발

필요 라이브러리 import

<script th:src="@{/static/js/webserial-barcode-scanner.umd.js}"></script>
<script th:src="@{/static/js/event-emitter.js}"></script>
<script th:src="@{/static/js/main.js}"></script>

라이브러리

아래 라이브러리는 파일 첨부가 안되서 코드로 올림

webserial-barcode-scanner.esm.js

class t{constructor(t){this._events={}}on(t,e){this._events[t]=this._events[t]||[],this._events[t].push(e)}emit(t,...e){let n=this._events[t];n&&n.forEach((t=>{t(...e)}))}}class e{constructor(e){this._internal={emitter:new t,port:null,reader:null,options:Object.assign({baudRate:9600,bufferSize:255,dataBits:8,flowControl:"none",parity:"none",stopBits:1},e)},navigator.serial.addEventListener("disconnect",(t=>{this._internal.port==t.target&&this._internal.emitter.emit("disconnected")}))}async connect(){try{let t=await navigator.serial.requestPort();t&&await this.open(t)}catch(t){console.log("Could not connect! "+t)}}async reconnect(t){if(!t.vendorId||!t.productId)return;let e=(await navigator.serial.getPorts()).filter((e=>{let n=e.getInfo();return n.usbVendorId==t.vendorId&&n.usbProductId==t.productId}));1==e.length&&await this.open(e[0])}async disconnect(){this._internal.port&&(this._internal.reader.releaseLock(),await this._internal.port.close(),this._internal.port=null,this._internal.reader=null,this._internal.emitter.emit("disconnected"))}async open(t){this._internal.port=t,await this._internal.port.open(this._internal.options);let e=this._internal.port.getInfo();this._internal.emitter.emit("connected",{type:"serial",vendorId:e.usbVendorId||null,productId:e.usbProductId||null});let n="";for(;t.readable;){this._internal.reader=t.readable.getReader();try{for(;;){const{value:t,done:e}=await this._internal.reader.read();if(e){this._internal.reader.releaseLock();break}if(t)for(let e=0;e<t.length;e++){let r=t[e];13!==r?n+=String.fromCharCode(r):(this._internal.emitter.emit("barcode",{value:n}),n="")}}}catch(t){n=""}}}addEventListener(t,e){this._internal.emitter.on(t,e)}}export{e as default};

webserial-barcode-scanner.umd.js

!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).WebSerialBarcodeScanner=t()}(this,(function(){"use strict";class e{constructor(e){this._events={}}on(e,t){this._events[e]=this._events[e]||[],this._events[e].push(t)}emit(e,...t){let n=this._events[e];n&&n.forEach((e=>{e(...t)}))}}return class{constructor(t){this._internal={emitter:new e,port:null,reader:null,options:Object.assign({baudRate:9600,bufferSize:255,dataBits:8,flowControl:"none",parity:"none",stopBits:1},t)},navigator.serial.addEventListener("disconnect",(e=>{this._internal.port==e.target&&this._internal.emitter.emit("disconnected")}))}async connect(){try{let e=await navigator.serial.requestPort();e&&await this.open(e)}catch(e){console.log("Could not connect! "+e)}}async reconnect(e){if(!e.vendorId||!e.productId)return;let t=(await navigator.serial.getPorts()).filter((t=>{let n=t.getInfo();return n.usbVendorId==e.vendorId&&n.usbProductId==e.productId}));1==t.length&&await this.open(t[0])}async disconnect(){this._internal.port&&(this._internal.reader.releaseLock(),await this._internal.port.close(),this._internal.port=null,this._internal.reader=null,this._internal.emitter.emit("disconnected"))}async open(e){this._internal.port=e,await this._internal.port.open(this._internal.options);let t=this._internal.port.getInfo();this._internal.emitter.emit("connected",{type:"serial",vendorId:t.usbVendorId||null,productId:t.usbProductId||null});let n="";for(;e.readable;){this._internal.reader=e.readable.getReader();try{for(;;){const{value:e,done:t}=await this._internal.reader.read();if(t){this._internal.reader.releaseLock();break}if(e)for(let t=0;t<e.length;t++){let r=e[t];13!==r?n+=String.fromCharCode(r):(this._internal.emitter.emit("barcode",{value:n}),n="")}}}catch(e){n=""}}}addEventListener(e,t){this._internal.emitter.on(e,t)}}}));

event-emitter.js

!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self).WebSerialBarcodeScanner=t()}(this,(function(){"use strict";class e{constructor(e){this._events={}}on(e,t){this._events[e]=this._events[e]||[],this._events[e].push(t)}emit(e,...t){let n=this._events[e];n&&n.forEach((e=>{e(...t)}))}}return class{constructor(t){this._internal={emitter:new e,port:null,reader:null,options:Object.assign({baudRate:9600,bufferSize:255,dataBits:8,flowControl:"none",parity:"none",stopBits:1},t)},navigator.serial.addEventListener("disconnect",(e=>{this._internal.port==e.target&&this._internal.emitter.emit("disconnected")}))}async connect(){try{let e=await navigator.serial.requestPort();e&&await this.open(e)}catch(e){console.log("Could not connect! "+e)}}async reconnect(e){if(!e.vendorId||!e.productId)return;let t=(await navigator.serial.getPorts()).filter((t=>{let n=t.getInfo();return n.usbVendorId==e.vendorId&&n.usbProductId==e.productId}));1==t.length&&await this.open(t[0])}async disconnect(){this._internal.port&&(this._internal.reader.releaseLock(),await this._internal.port.close(),this._internal.port=null,this._internal.reader=null,this._internal.emitter.emit("disconnected"))}async open(e){this._internal.port=e,await this._internal.port.open(this._internal.options);let t=this._internal.port.getInfo();this._internal.emitter.emit("connected",{type:"serial",vendorId:t.usbVendorId||null,productId:t.usbProductId||null});let n="";for(;e.readable;){this._internal.reader=e.readable.getReader();try{for(;;){const{value:e,done:t}=await this._internal.reader.read();if(t){this._internal.reader.releaseLock();break}if(e)for(let t=0;t<e.length;t++){let r=e[t];13!==r?n+=String.fromCharCode(r):(this._internal.emitter.emit("barcode",{value:n}),n="")}}}catch(e){n=""}}}addEventListener(e,t){this._internal.emitter.on(e,t)}}}));

main.js

class WebSerialBarcodeScanner {
  constructor(options) {
    this._internal = {
      emitter: new EventEmitter(),
      port: null,
      reader: null,
      isConnected: false,
      usbVendorId: null,
      productId: null,
      options: Object.assign(
        {
          baudRate: 9600,
          bufferSize: 255,
          dataBits: 8,
          flowControl: "none",
          parity: "none",
          stopBits: 1,
        },
        options,
      ),
    };

    navigator.serial.addEventListener("disconnect", (event) => {
      if (this._internal.port == event.target) {
        this._internal.emitter.emit("disconnected");
        this._internal.isConnected = false;
      }
    });
  }

  async connect() {
    try {
      let port = await navigator.serial.requestPort();

      if (port) {
        await this.open(port);
      }
    } catch (error) {
      console.error("Could not connect! " + error);
    }
  }

  async reconnect(previousPort, callback) {
    if (!previousPort.vendorId || !previousPort.productId) {
      return;
    }

    let ports = await navigator.serial.getPorts();

    let matches = ports.filter((port) => {
      let info = port.getInfo();
      return (
        info.usbVendorId == previousPort.vendorId &&
        info.usbProductId == previousPort.productId
      );
    });

    if (matches.length === 1) {
      await this.open(matches[0], callback);
    }
  }

  async disconnect() {
    if (!this._internal.port) {
      return;
    }

    this._internal.reader.releaseLock();
    await this._internal.port.close();

    this._internal.port = null;
    this._internal.reader = null;

    this._internal.emitter.emit("disconnected");
  }

  async open(port, callback) {
    this._internal.port = port;

    await this._internal.port.open(this._internal.options);

    let info = this._internal.port.getInfo();

    this._internal.emitter.emit("connected", {
      type: "serial",
      vendorId: info.usbVendorId || null,
      productId: info.usbProductId || null,
    });

    localStorage.setItem("usbVendorId", info.usbVendorId);
    localStorage.setItem("usbProductId", info.usbProductId);

    let buffer = "";

    this._internal.isConnected = true;
    while (port.readable) {
      this._internal.reader = port.readable.getReader();

      try {
        while (true) {
          const { value, done } = await this._internal.reader.read();
          const reconstituted = String.fromCharCode.apply(null, value);
          console.log("reconstituted", reconstituted);
          callback(reconstituted);

          if (done) {
            this._internal.reader.releaseLock();
            break;
          }
          if (value) {
            for (let i = 0; i < value.length; i++) {
              let character = value[i];

              if (character !== 13) {
                buffer += String.fromCharCode(character);
              } else {
                this._internal.emitter.emit("barcode", {
                  value: buffer,
                });

                buffer = "";
              }
            }
          }
        }
      } catch (error) {
        buffer = "";
      }
    }
  }

  addEventListener(n, f) {
    this._internal.emitter.on(n, f);
  }
}

html 파일

	const barcodeScanner = new WebSerialBarcodeScanner();
	let lastUsedDevice = {
		type: "serial",
		vendorId: localStorage.getItem("usbVendorId") || null,
		productId: localStorage.getItem("usbProductId") || null,
	};

	function onStartQR() {
		setInterval(async function () {
			if (lastUsedDevice.productId &&
				lastUsedDevice.vendorId &&
              	!barcodeScanner._internal.isConnected) {
				await barcodeScanner.reconnect(lastUsedDevice, onQRCode);
			}
		}, 2000);
	}

    function onQRCode(value){
		// QR스캔시 실행할 로직
    }

    window.onload = function () {
     	onStartQR();
    }

시리얼 같은 경우 브라우저를 켤때마다 물어보는데 이전에 설정했던 시리얼을 사용자에게 물어보지 않고 사용하고싶으면 크롬/엣지 실행시 아규먼트에 --user-data-dir="C:\path\to\your\chrome_profile" 추가

0개의 댓글