How to Build an IMEI Scanner in HTML5

The International Mobile Equipment Identity (IMEI) is a numeric identifier, usually unique, for 3GPP and iDEN mobile phones, as well as some satellite phones. It is usually found printed inside the battery compartment of the phone but can also be displayed on-screen on most phones by entering *#06# MMI Supplementary Service code on the dial pad, or alongside other system information in the settings menu on smartphone operating systems.1 If the phone supports dual sim, then it can have two IMEI numbers.

Example

Since it is a unique value, it is often used in scenarios like managing phones in the inventory and tracking a network device. We can capture the value by reading the barcodes or by recognizing the text. In this article, we are going to build an IMEI scanner in HTML5 using the following SDKs:

Online demo

Build an IMEI Scanner in HTML5

Let’s do this in steps.

Basic HTML File

Create a new HTML file with the following content:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>IMEI Scanner</title>
  <style>
    .home {text-align: center;}
  </style>
</head>
<body>
  <div class="app">
    <div class="home">
      <h1>IMEI Scanner</h1>
      <div>
        Mode:
        <select class="mode" onchange="onModeChanged()">
          <option value="0">Read Barcodes</option>
          <option value="1">Recognize Text</option>
        </select>
      </div>
      <div class="status"></div>
      <button class="start-btn" onclick="startScan();" disabled>Start Scan</button>
      <div class="result"></div>
    </div>
  </div>
  <script>
    function startScan(){
      console.log("start scan");
    }
    
    function onModeChanged(){
      let mode = document.getElementsByClassName("mode")[0].selectedOptions[0].value;
      console.log(mode);
    }
  </script>
</body>
</html>

The page has a select element to select whether to read barcodes or recognize text and a button to start scanning.

home page

Access the Camera

We use Dynamsoft Camera Enhancer to access the camera.

  1. Include the Dynamsoft Camera Enhancer library with the following code in the head.

    <script src="https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@3.2.0/dist/dce.js"></script>
    
  2. Initialize an instance of camera enhancer.

    let camera;
    Dynamsoft.DCE.CameraEnhancer.engineResourcePath = "https://cdn.jsdelivr.net/npm/dynamsoft-camera-enhancer@3.2.0/dist/";
    async function init(){
      camera = await Dynamsoft.DCE.CameraEnhancer.createInstance();
    }
    
  3. Add the following elements to a new scanner container.

    <div class="scanner" style="display:none;">
      <svg class="dce-bg-loading" viewBox="0 0 1792 1792"><path d="M1760 896q0 176-68.5 336t-184 275.5-275.5 184-336 68.5-336-68.5-275.5-184-184-275.5-68.5-336q0-213 97-398.5t265-305.5 374-151v228q-221 45-366.5 221t-145.5 406q0 130 51 248.5t136.5 204 204 136.5 248.5 51 248.5-51 204-136.5 136.5-204 51-248.5q0-230-145.5-406t-366.5-221v-228q206 31 374 151t265 305.5 97 398.5z"/></svg>
      <svg class="dce-bg-camera" viewBox="0 0 2048 1792"><path d="M1024 672q119 0 203.5 84.5t84.5 203.5-84.5 203.5-203.5 84.5-203.5-84.5-84.5-203.5 84.5-203.5 203.5-84.5zm704-416q106 0 181 75t75 181v896q0 106-75 181t-181 75h-1408q-106 0-181-75t-75-181v-896q0-106 75-181t181-75h224l51-136q19-49 69.5-84.5t103.5-35.5h512q53 0 103.5 35.5t69.5 84.5l51 136h224zm-704 1152q185 0 316.5-131.5t131.5-316.5-131.5-316.5-316.5-131.5-316.5 131.5-131.5 316.5 131.5 316.5 316.5 131.5z"/></svg>
      <div class="dce-video-container"></div>
      <div class="dce-scanarea">
      </div>
      <div class="sel-container">
        <select class="dce-sel-camera"></select>
        <select class="dce-sel-resolution"></select>
      </div>
      <button class="close-btn" onclick="stopScan();">Close</button>
    </div>
    

    The styles are like the following:

    @keyframes dce-rotate{from{transform:rotate(0turn);}to{transform:rotate(1turn);}}
    @keyframes dce-scanlight{from{top:0;}to{top:97%;}}
    .scanner{width:100%;height:100%;min-width:100px;min-height:100px;background:#eee;position:absolute;left:0;top:0;}
    .dce-bg-loading{animation:1s linear infinite dce-rotate;width:40%;height:40%;position:absolute;margin:auto;left:0;top:0;right:0;bottom:0;fill:#aaa;}
    .dce-bg-camera{display:none;width:40%;height:40%;position:absolute;margin:auto;left:0;top:0;right:0;bottom:0;fill:#aaa;}
    .dce-video-container{position:absolute;left:0;top:0;width:100%;height:100%;}
    .dce-scanarea{position:absolute;left:0;top:0;width:100%;height:100%;pointer-events:none;}
    .sel-container{position: absolute;left: 0;top: 0;}
    .sel-container .dce-sel-camera{display:block;}
    .sel-container .dce-sel-resolution{display:block;margin-top:5px;}
    .sel-container {display:block;margin-top:5px;}
    .close-btn{position: absolute;right: 0;top: 0;}
    
  4. Bind the element to the camera enhancer.

    await camera.setUIElement(document.getElementsByClassName("scanner")[0]);
    
  5. Open the camera in the startScan function and close the camera in the stopScan function.

    function startScan(){
      camera.open(true);
    }
    
    function stopScan(){
      camera.close(true);
    }
    
  6. Set a scan region so that only a limited region will be used for further use.

    setScanRegion(0,25,100,35);
       
    function onModeChanged(){
      let mode = document.getElementsByClassName("mode")[0].selectedOptions[0].value;
      if (mode === "0") {
        setScanRegion(0,25,100,35);
      }else{
        setScanRegion(25,25,75,35);
      }
    }
       
    function setScanRegion(left,top,right,bottom){
      camera.setScanRegion({
        regionLeft:left,
        regionTop:top,
        regionRight:right,
        regionBottom:bottom,
        regionMeasuredByPercentage: 1
      });
    }
    

    The region for recognizing text is a bit narrower so that we can avoid reading non-IMEI characters.

Read the Barcode

We use Dynamsoft Barcode Reader to scan barcodes from the camera.

  1. Include the Dynamsoft Barcode Reader library with the following code in the head.

    <script src="https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@9.6.2/dist/dbr.js"></script>
    
  2. Initialize an instance of barcode reader. We also need to set the engine resource path and the license. You can apply for a trial license here.

    let reader;
    Dynamsoft.DBR.BarcodeScanner.license = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; //one-day public trial
    Dynamsoft.DBR.BarcodeScanner.engineResourcePath = "https://cdn.jsdelivr.net/npm/dynamsoft-javascript-barcode@9.6.2/dist/";
    async function init(){
      reader = await Dynamsoft.DBR.BarcodeReader.createInstance();
    }
    
  3. Update the runtime settings of the barcode reader so that it only reads barcodes in the Code128 format since IMEI barcodes use this format.

    let settings = await reader.getRuntimeSettings();
    settings.barcodeFormatIds = Dynamsoft.DBR.EnumBarcodeFormat.BF_CODE_128;
    await reader.updateRuntimeSettings(settings);
    
  4. Start a processing loop to read barcodes from camera frames.

    let interval;
    let processing = false; 
    function startScan(){
      camera.open(true);
      let mode = document.getElementsByClassName("mode")[0].selectedOptions[0].value;
      let isBarcode = (mode === "0") ? true : false;
      console.log(isBarcode);
      startProcessingLoop(isBarcode);
    }
    
    function stopScan(){
      stopProcessingLoop();
      camera.close(true);
    }
    
    function startProcessingLoop(isBarcode){
      stopProcessingLoop();
      if (isBarcode) {
        interval = setInterval(captureAndDecode,100); // read barcodes
      } else {
        interval = setInterval(captureAndRecognize,500); // recognize text
      }
    }
    
    function stopProcessingLoop(){
      if (interval) {
        clearInterval(interval);
      }
      processing = false;
    }
    
    async function captureAndDecode() {
      if (!camera || !reader) {
        return
      }
      if (camera.isOpen() === false) {
        return;
      }
      if (processing == true) {
        return;
      }
      let frame = camera.getFrame();
      if (frame) {
        processing = true; // set decoding to true so that the next frame will be skipped if the decoding has not completed.
        let results = await reader.decode(frame);
        console.log(results);
        for (let index = 0; index < results.length; index++) {
          const result = results[index];
           if (IMEIValid(result.barcodeText)) {
            document.getElementsByClassName("result")[0].innerHTML = result.barcodeText;
            stopScan();
            break;
           }
        }
        processing = false;
      }
    };
    

    If a barcode is found and the code is a valid IMEI, then stop scanning and save the result.

Demo video:

Recognize the Text

We use Dynamsoft Label Recognizer to recognize the text.

  1. Include the Dynamsoft Label Recognizer library with the following code in the head.

    <script src="https://cdn.jsdelivr.net/npm/dynamsoft-label-recognizer@2.2.11/dist/dlr.js"></script>
    
  2. Initialize an instance of label recognizer. We also need to set the engine resource path and the license. You can apply for a trial license here.

    let recognizer;
    Dynamsoft.DLR.LabelRecognizer.license = "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="; //one-day public trial
    async function init(){
      recognizer = await Dynamsoft.DLR.LabelRecognizer.createInstance();
    }
    
  3. Update the runtime settings of the label recognizer to recognize numbers only.

    await recognizer.updateRuntimeSettingsFromString("number");
    
  4. Start a processing loop to recognize text from camera frames. (The shared code is not pasted here.)

    async function captureAndRecognize() {
      if (!camera || !recognizer) {
        return
      }
      if (camera.isOpen() === false) {
        return;
      }
      if (processing == true) {
        return;
      }
      let frame = camera.getFrame();
      if (frame) {
        processing = true; // set decoding to true so that the next frame will be skipped if the decoding has not completed.
        let results = await recognizer.recognize(frame);
        console.log(results);
        for (let i = 0; i < results.length; i++) {
          const result = results[i];
          for (let j = 0; j < result.lineResults.length; j++) {
            const lineResult = result.lineResults[j];
            let text = whiteSpacesRemoved(lineResult.text);
            if (IMEIValid(text)) {
              document.getElementById("scan-result").innerText = text;
              document.getElementById("modal").className += " active"; //display the modal
              stopProcessingLoop();
              break;
            }
          }
        }
        processing = false;
      }
    };
    
    function whiteSpacesRemoved(str){
      str = str.split(" ").join("");
      return str;
    }
    

    If the text of one line recognized is a valid IMEI, then stop scanning and display a modal for the user to confirm the result. Since the OCR may have errors, user intervention is needed.

    The followings are the codes related to the modal.

    HTML:

    <div id="modal" class="modal-window">
      <div class="overflow">
        <pre id="scan-result"></pre>
      </div>
      <button id="correct-btn" onclick="correct()">Correct</button>
      <button id="rescan-btn" onclick="rescan()">Rescan</button>
    </div>
    

    CSS:

    .modal-window {
      display: none;
      position: absolute;
      left: 50%;
      max-width: 80%;
      transform: translateX(-50%);
      z-index: 99999;
      background: #fff;
      padding: 20px;
      border: thick double black;
      border-radius: 5px;
      font-family: sans-serif;
      top: 50px;  
    }
    .modal-window.active {
        display: block;
    }
    .overflow {
      overflow: auto;
    }
    

    JavaScript:

    function correct(){
      let modal = document.getElementById("modal");
      modal.className = modal.className.replace("active", "");
      document.getElementsByClassName("result")[0].innerHTML = document.getElementById("scan-result").innerText;
      stopScan();
    }
    
    function rescan(){
      let modal = document.getElementById("modal");
      modal.className = modal.className.replace("active", "");
      startProcessingLoop(false);
    }
    

Demo video:

Verify the IMEI Result

An IMEI is a string of numbers with a length between 15 and 17. It also has a check digit following the Luhn algorithm.

We can use the following code to verify the IMEI result.

function IMEIValid(imei) {
  if (isNaN(imei)) {
    return false;
  } 
  if (imei.length >= 15 && imei.length <= 17) {
    if (verifyCheckDigit(imei)) {
      return true;
    }
  }
  return false;
}

//https://en.wikipedia.org/wiki/International_Mobile_Equipment_Identity#Check_digit_computation
function verifyCheckDigit(imei){
  let checkDigit = imei.substring(imei.length-1);
  let rest = imei.substring(0, imei.length-1);
  let digits = rest.split("");
  let sum = 0;
  for (let i = 0; i < digits.length; i++) {
    const digit = parseInt(digits[i]);
    let numbers;
    if ((i+1) % 2 === 0) {
      let doubled = (digit * 2).toString();
      numbers = doubled.split("");
    }else{
      numbers = [digit];
    }

    for (let j = 0; j < numbers.length; j++) {
      const number = numbers[j];
      sum = sum + parseInt(number);
    }
  }

  if ((sum + parseInt(checkDigit)) % 10 === 0) {
    return true;
  }
  return false;
}

Source Code

You can find the source code of the demo in the following repo: https://github.com/tony-xlh/IMEI-scanner/

References