📜 ⬆️ ⬇️

Working with a COM port in a web project

Prologue


One of the clients of our web project wanted to use a barcode scanner to search for orders in the system. But, unfortunately, completely abandoned the idea of ​​working with them in keyboard imitation mode - only COM port emulation.
Solutions were not very many:

Fortunately, there is a way to solve the problem in the second way.


Chrome application


If anyone knows, the Chrome Application is a Chrome browser application written in JavaScript. An API for working with serial ports is available in these applications. This option is almost perfect for us.
The main problem is that although Chrome Application has the right tools, it cannot work directly with open pages. Here we come to the aid of extensions that have this opportunity.

Next, I will try to describe in more detail how to tie it all together so that it works.
')
COM port emulation
Unfortunately, I did not have the opportunity to work with a real scanner, so I had to emulate it.
For this, I used socat:
  1. Run:
    socat -d -d pty,raw,echo=0 pty,raw,echo=0 

  2. We receive the answer of a type:
      socat[1473] N PTY is /dev/ttys001 socat[1473] N PTY is /dev/ttys002 socat[1473] N starting data transfer loop with FDs [3,3] and [5,5] 

  3. In another window of the terminal we perform:
      cat > /dev/ttys001 

    instead of / dev / ttys001, specify the path that socat returned
    And write any messages.
  4. For verification, in the third window:
      cat < /dev/ttys002 

    / dev / ttys002 - the second path from socat.
    Having written the message in the second window - we will receive it in the third, if it has come - you can go further.




Create application

The documentation describes the process fairly well, one need only pay attention to the fact that we need access to work with serial ports. To do this in the file manifest.json specify:
  "permissions": [ "serial" ] 

The background.js file contains the code for the application itself:
Listing
  chrome.app.runtime.onLaunched.addListener(function() { chrome.serial.connect("/dev/ttys004", {bitrate: 115200}, onConnect); }); var stringReceived = ''; var onConnect = function(connectionInfo) { var connectionId = connectionInfo.connectionId; var onReceiveCallback = function(info) { if (info.connectionId == connectionId) { var str = arrayBufferToString(info.data); if (str.charAt(str.length-1) === '\n') { stringReceived += str.substring(0, str.length-1); chrome.runtime.sendMessage('dbmjhdcnjkeeopcmhbooojabanopplnd', { action: 'scanner', data: { barcode: stringReceived } }); stringReceived = ''; } else { stringReceived += str; } } }; chrome.serial.onReceive.addListener(onReceiveCallback); }; function arrayBufferToString (buffer) { var string = ''; var bytes = new Uint8Array( buffer ); var len = bytes.byteLength; for (var i = 0; i < len; i++) { string += String.fromCharCode( bytes[ i ] ) } return string; } 


We will analyze it in more detail.

chrome.app.runtime.onLaunched.addListener - adds a function to the list that runs when the application starts.
chrome.serial.connect ("/ dev / ttys001", {bitrate: 115200}, onConnect) - connect to the port we need, the connection will execute the onConnect function.
chrome.serial.onReceive.addListener (onReceiveCallback) - when a message is received - onReceiveCallback is called
chrome.runtime.sendMessage is a function that sends a message to another application / extension. The first argument - the unique ID of the extension to which we send the message - can be seen in the list of installed extensions ( chrome: // extensions / - the parser breaks the link ), the second argument is the data itself.

Creating an extension


Here, too, everything is easy and described in detail in the documentation.
Key settings from the manifest file:
  "permissions": [ "tabs", "file:///*" ], "content_scripts": [ { "matches": ["file:///*"], "js": ["action.js"] } ], "background": { "persistent": false, "scripts": ["js/background.js"] } 

permissions - indicates that we need access to the tabs, then specify which (for tests - all local files are listed)
content_scripts - describes which additional scripts to run on the pages.
background - describes the extension script that works in the background

Background.js contains code that is responsible for receiving a message and sending it to a specific tab.

background.js
  var onMessage = function(data) { switch (data.action) { case 'scanner': { chrome.tabs.query({url: "file:///*"}, function(tab) { for (var i = 0; i < tab.length; i++) { chrome.tabs.sendMessage(tab[i].id, data); } }); } } }; chrome.runtime.onMessageExternal.addListener(onMessage); 



chrome.tabs.query - makes a selection of tabs by the criterion, in our case it is url = "file: /// *"
There are 2 ways to execute js code on the page from the extension

I chose the second option. It is worth noting that any code executed in the tab from the extension is executed in a special environment. This means that it will have full access to the DOM elements, but will not have access to any variables created in the tab. More details.
The best way to transfer data from the extension to the tab code is to use CustomEvent.

In the action.js file, we just get the message from backgroud.js and create an event for the document.
action.js
  chrome.runtime.onMessage.addListener( function(data) { var event = new CustomEvent(data.action, {detail: data.data}); document.dispatchEvent(event); } ); 



Accept message


The simplest thing is to accept the message and do the desired actions with it, for example, simply insert it into the input
index.html
  <html> <head> <script type="text/javascript"> document.addEventListener("scanner", function(e) { document.getElementById('barcode').value = e.detail.barcode; }); </script> </head> <body> <input id="barcode"> </body> </html> 



Epilogue


In general, I was pleasantly surprised that chrome provides an API for working with hardware, including not only for reading, but also for writing.
Unfortunately, after almost everything was done, the client said that he would still put the scanners into keyboard simulation mode. Although in the end this was not useful to us - I hope this material will be useful to someone.

PS

If anyone is interested, I can tell you about how we created and support several one-page large projects using backbone, how we cache the entire layout on the client side.

Source: https://habr.com/ru/post/240775/


All Articles