Communicate with the serial port from client web browser.
Asked Answered
H

3

13

In my web application(sencha extjs 5) I have a user requirement to read/write data to the client PC serial port.

I am aware of the client browser can not access local machine hardware without installing some binaries on the local machine(Native app, Windows Service, etc..).

I have seen the same question is discussed few years back in stackoverflow forums. But I need to know what is the best way of doing this today with the available technologies?

Haywood answered 8/5, 2015 at 1:45 Comment(3)
Please note that Chrome apps will be discontinued in 2018. More info here: developer.chrome.com/apps/migrationGautama
Have a look at the Web Serial API (examples here). Also see the browser support table.Omalley
related: https://mcmap.net/q/904361/-how-to-read-from-serial-port-in-a-webpage, https://mcmap.net/q/611813/-how-can-a-web-page-read-from-the-user-39-s-serial-portOmalley
E
9

Well, One way to do this is develop a chrome app. You can use chrome.serial API.

https://developer.chrome.com/apps/serial

Sample Code,

In your manifest.json,

{
  "name": "Serial Sample",
  "description": "Read/Write from/to serial port.",
  "version": "1.0",
  "manifest_version": 2,
  "permissions": ["serial"],
  "app": {
    "background": {
      "scripts": ["background.js"]
    }
  }
}

In your background.js,

const DEVICE_PATH = 'COM1';
const serial = chrome.serial;
var dataRecieved="";

/* Interprets an ArrayBuffer as UTF-8 encoded string data. */
var ab2str = function(buf) {
    var bufView = new Uint8Array(buf);
    var encodedString = String.fromCharCode.apply(null, bufView);
    return decodeURIComponent(escape(encodedString));
};

/* Converts a string to UTF-8 encoding in a Uint8Array; returns the array buffer. */
var str2ab = function(str) {
    var encodedString = unescape(encodeURIComponent(str));
    var bytes = new Uint8Array(encodedString.length);
    for (var i = 0; i < encodedString.length; ++i) {
        bytes[i] = encodedString.charCodeAt(i);
    }
    return bytes.buffer;
};


var SerialConnection = function() {
    this.connectionId = -1;
    this.lineBuffer = "";
    this.boundOnReceive = this.onReceive.bind(this);
    this.boundOnReceiveError = this.onReceiveError.bind(this);
    this.onConnect = new chrome.Event();
    this.onReadLine = new chrome.Event();
    this.onError = new chrome.Event();
};

SerialConnection.prototype.onConnectComplete = function(connectionInfo) {
    if (!connectionInfo) {
        log("Connection failed.");
        return;
    }
    this.connectionId = connectionInfo.connectionId;
    chrome.serial.onReceive.addListener(this.boundOnReceive);
    chrome.serial.onReceiveError.addListener(this.boundOnReceiveError);
    this.onConnect.dispatch();
};

SerialConnection.prototype.onReceive = function(receiveInfo) {
    if (receiveInfo.connectionId !== this.connectionId) {
        return;
    }

    this.lineBuffer += ab2str(receiveInfo.data);

    var index;
    while ((index = this.lineBuffer.indexOf('\n')) >= 0) {
        var line = this.lineBuffer.substr(0, index + 1);
        this.onReadLine.dispatch(line);
        this.lineBuffer = this.lineBuffer.substr(index + 1);
    }
};

SerialConnection.prototype.onReceiveError = function(errorInfo) {
    if (errorInfo.connectionId === this.connectionId) {
        this.onError.dispatch(errorInfo.error);
    }
};

SerialConnection.prototype.connect = function(path) {
    serial.connect(path, this.onConnectComplete.bind(this))
};

SerialConnection.prototype.send = function(msg) {
    if (this.connectionId < 0) {
        throw 'Invalid connection';
    }
    serial.send(this.connectionId, str2ab(msg), function() {});
};

SerialConnection.prototype.disconnect = function() {
    if (this.connectionId < 0) {
        throw 'Invalid connection';
    }
    serial.disconnect(this.connectionId, function() {});
};


var connection = new SerialConnection();

connection.onConnect.addListener(function() {
    //console.log('connected to: ' + DEVICE_PATH);
});

connection.onReadLine.addListener(function (line) {
    //Serial port data recieve event.
    dataRecieved = dataRecieved +line;
});

connection.connect(DEVICE_PATH);

Once you create the chrome app to communicate with the serial port the next thing is to allow your external web page to communicate with the chrome app using JavaScript.

For this on your manifest.json file add,

"externally_connectable": {
"matches": ["*://*.example.com/*"]
}

This will allow external webpage on your example.com domain communicate with your chrome app.

In your webpage,

    // The ID of the extension we want to talk to.
    var editorExtensionId = "nboladondmajlaalmcdupihoilpcketyl";

   // Make a simple request:
   chrome.runtime.sendMessage(editorExtensionId, 
   { data: "data to pass to the chrome app" },  
   function (response)
   {
    alert(response);
   });

In your chrome app,

chrome.runtime.onMessageExternal.addListener(
  function (request, sender, sendResponse) {
        sendResponse("Send serial port data to the web page");
  });

https://developer.chrome.com/apps/messaging

Emogeneemollient answered 20/5, 2015 at 1:2 Comment(0)
C
15

Using Web Serial API. I am using this to ONLY read the data from my Weight Scale with RS232 Serial Interface

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Web Serial</title>
</head>
<body>

  <div class="serial-scale-div">
        <button class="btn" id="connect-to-serial">Connect with Serial Device</button>
  </div>

  <button id="get-serial-messages">Get serial messages</button>
  
  <div id="serial-messages-container">
    <div class="message"></div>
  </div>

  <script>
    "use strict";
    class SerialScaleController {
        constructor() {
            this.encoder = new TextEncoder();
            this.decoder = new TextDecoder();
        }
        async init() {
            if ('serial' in navigator) {
                try {
                    const port = await navigator.serial.requestPort();
                    await port.open({ baudRate: 9600 });
                    this.reader = port.readable.getReader();
                    let signals = await port.getSignals();
                    console.log(signals);
                }
                catch (err) {
                    console.error('There was an error opening the serial port:', err);
                }
            }
            else {
                console.error('Web serial doesn\'t seem to be enabled in your browser. Try enabling it by visiting:');
                console.error('chrome://flags/#enable-experimental-web-platform-features');
                console.error('opera://flags/#enable-experimental-web-platform-features');
                console.error('edge://flags/#enable-experimental-web-platform-features');
            }
        }
        async read() {
            try {
                const readerData = await this.reader.read();
                console.log(readerData)
                return this.decoder.decode(readerData.value);
            }
            catch (err) {
                const errorMessage = `error reading data: ${err}`;
                console.error(errorMessage);
                return errorMessage;
            }
        }
    }

    const serialScaleController = new SerialScaleController();
    const connect = document.getElementById('connect-to-serial');
    const getSerialMessages = document.getElementById('get-serial-messages');

    connect.addEventListener('pointerdown', () => {
      serialScaleController.init();
    });

    getSerialMessages.addEventListener('pointerdown', async () => {
      getSerialMessage();
    });

    async function getSerialMessage() {
      document.querySelector("#serial-messages-container .message").innerText += await serialScaleController.read()
    }

  </script>
</body>
</html>

Checkout this demo and this code for a more descriptive example.

You might need to turn on the Serial API feature on your browser. Following is the quote from References

As you can imagine, this is API is only supported by modern Chromium based desktop browsers right now (April 2020) but hopefully support will improve in the near future. At this moment you need to enable your browser's Experimental Web Platform Features, just copy and paste the right URL:

chrome://flags/#enable-experimental-web-platform-features opera://flags/#enable-experimental-web-platform-features edge://flags/#enable-experimental-web-platform-features

References:

https://dev.to/unjavascripter/the-amazing-powers-of-the-web-web-serial-api-3ilc

https://github.com/UnJavaScripter/web-serial-example

Cartie answered 20/7, 2020 at 1:38 Comment(1)
this is the correct answer. it's up to date and works no problem on chrome. cant wait for it to get enabled by default.Ophthalmology
E
9

Well, One way to do this is develop a chrome app. You can use chrome.serial API.

https://developer.chrome.com/apps/serial

Sample Code,

In your manifest.json,

{
  "name": "Serial Sample",
  "description": "Read/Write from/to serial port.",
  "version": "1.0",
  "manifest_version": 2,
  "permissions": ["serial"],
  "app": {
    "background": {
      "scripts": ["background.js"]
    }
  }
}

In your background.js,

const DEVICE_PATH = 'COM1';
const serial = chrome.serial;
var dataRecieved="";

/* Interprets an ArrayBuffer as UTF-8 encoded string data. */
var ab2str = function(buf) {
    var bufView = new Uint8Array(buf);
    var encodedString = String.fromCharCode.apply(null, bufView);
    return decodeURIComponent(escape(encodedString));
};

/* Converts a string to UTF-8 encoding in a Uint8Array; returns the array buffer. */
var str2ab = function(str) {
    var encodedString = unescape(encodeURIComponent(str));
    var bytes = new Uint8Array(encodedString.length);
    for (var i = 0; i < encodedString.length; ++i) {
        bytes[i] = encodedString.charCodeAt(i);
    }
    return bytes.buffer;
};


var SerialConnection = function() {
    this.connectionId = -1;
    this.lineBuffer = "";
    this.boundOnReceive = this.onReceive.bind(this);
    this.boundOnReceiveError = this.onReceiveError.bind(this);
    this.onConnect = new chrome.Event();
    this.onReadLine = new chrome.Event();
    this.onError = new chrome.Event();
};

SerialConnection.prototype.onConnectComplete = function(connectionInfo) {
    if (!connectionInfo) {
        log("Connection failed.");
        return;
    }
    this.connectionId = connectionInfo.connectionId;
    chrome.serial.onReceive.addListener(this.boundOnReceive);
    chrome.serial.onReceiveError.addListener(this.boundOnReceiveError);
    this.onConnect.dispatch();
};

SerialConnection.prototype.onReceive = function(receiveInfo) {
    if (receiveInfo.connectionId !== this.connectionId) {
        return;
    }

    this.lineBuffer += ab2str(receiveInfo.data);

    var index;
    while ((index = this.lineBuffer.indexOf('\n')) >= 0) {
        var line = this.lineBuffer.substr(0, index + 1);
        this.onReadLine.dispatch(line);
        this.lineBuffer = this.lineBuffer.substr(index + 1);
    }
};

SerialConnection.prototype.onReceiveError = function(errorInfo) {
    if (errorInfo.connectionId === this.connectionId) {
        this.onError.dispatch(errorInfo.error);
    }
};

SerialConnection.prototype.connect = function(path) {
    serial.connect(path, this.onConnectComplete.bind(this))
};

SerialConnection.prototype.send = function(msg) {
    if (this.connectionId < 0) {
        throw 'Invalid connection';
    }
    serial.send(this.connectionId, str2ab(msg), function() {});
};

SerialConnection.prototype.disconnect = function() {
    if (this.connectionId < 0) {
        throw 'Invalid connection';
    }
    serial.disconnect(this.connectionId, function() {});
};


var connection = new SerialConnection();

connection.onConnect.addListener(function() {
    //console.log('connected to: ' + DEVICE_PATH);
});

connection.onReadLine.addListener(function (line) {
    //Serial port data recieve event.
    dataRecieved = dataRecieved +line;
});

connection.connect(DEVICE_PATH);

Once you create the chrome app to communicate with the serial port the next thing is to allow your external web page to communicate with the chrome app using JavaScript.

For this on your manifest.json file add,

"externally_connectable": {
"matches": ["*://*.example.com/*"]
}

This will allow external webpage on your example.com domain communicate with your chrome app.

In your webpage,

    // The ID of the extension we want to talk to.
    var editorExtensionId = "nboladondmajlaalmcdupihoilpcketyl";

   // Make a simple request:
   chrome.runtime.sendMessage(editorExtensionId, 
   { data: "data to pass to the chrome app" },  
   function (response)
   {
    alert(response);
   });

In your chrome app,

chrome.runtime.onMessageExternal.addListener(
  function (request, sender, sendResponse) {
        sendResponse("Send serial port data to the web page");
  });

https://developer.chrome.com/apps/messaging

Emogeneemollient answered 20/5, 2015 at 1:2 Comment(0)
M
3

I set up a website and a simple example for running a serial terminal in your browser. You should host it on a https server.

The serial terminal features are now available in chrome 88.

Live demo https://www.SerialTerminal.com Full source. https://github.com/mmiscool/serialTerminal.com/blob/main/index.html

Mcwilliams answered 19/3, 2021 at 0:46 Comment(2)
dude that's crazy ... can you make that in react ?Title
Feel free to create a react version. It is less than 200 lines of code including js, html and css.Mcwilliams

© 2022 - 2024 — McMap. All rights reserved.