I'm trying to do an asynchronous read in usb with fork() from the child_process module in electron. That is to say, when I click a div (id="read") a message is sent from the renderer process to the main process, which then in response calls fork() and reads in data from USB asynchronously. To handle usb communications I'm using the node-serialport library.
index.html
<html>
<head>
<link rel="stylesheet" href="style.css">
<link rel="stylesheet" href="scroll.css">
<link href="https://fonts.googleapis.com/css?family=Questrial" rel="stylesheet">
</head>
<body>
<div id="ui-container">
<div id="top-bar" class="shadowed">
<div id="top-bar-left-decoration"></div>
<div id="close-app" class="top-bar-button"><div class="unselectable top-button-text">x</div></div>
<div id="maximize-app" class="top-bar-button"><div class="unselectable top-button-text">□</div></div>
<div id="minimize-app" class="top-bar-button"><div class="unselectable top-button-text">-</div></div>
</div>
<div id="navigation-bar">
<div id="deviceSelection" class ="unselectable navigation-button">
<img id="deviceSelection-image" class="navigation-image" src="Icons/selectDevice2.png">
</div>
<div id="info" class="unselectable navigation-button">
<img id="info-image" class="navigation-image" src="Icons/DeviceInfoNoBorder.png">
</div>
<div id="config" class=" unselectable navigation-button">
<img id="config-image" class="navigation-image" src="Icons/DeviceConfigNoBorder.png">
</div>
<div id="graph" class="unselectable navigation-button">
<img id="graph-image" class="navigation-image" src="Icons/DataViewNoBorder.png">
</div>
<div id="export" class="unselectable navigation-button">
<img id="export-image" class="navigation-image" src="Icons/Export.png">
</div>
<div id="options-container"><img id="options" src="Icons/options.png"></div>
</div>
<div id="information-data-view" class="unselectable">
<div id="data">
<div id="push-to-read"></div>
</div>
</div>
</div>
</body>
<script src="ipcRenderer.js" type="text/javascript"></script>
</html>
This is my index.js, responsible for creating the browser window
const electron = require('electron')
const ipcMain = require('electron').ipcMain
const url = require('url')
const path = require('path')
const child_process = require('child_process')
const app = electron.app
var windowFullScreen = false;
const BrowserWindow = electron.BrowserWindow
var MainWindow;
app.on('ready', function()
{
MainWindow = new BrowserWindow({
width: 1024,
height: 768,
backgroundColor : '123355',
frame: false,
resizable: true,
movable: true,
show: false
})
MainWindow.on('ready-to-show', () => {
MainWindow.show()
console.log('Ready to go!')
})
MainWindow.loadURL(url.format({
pathname: path.join(__dirname, 'index.html'),
protocol: 'file:',
slashes: true
}))
ipcMain.on("minimize", (event, arg) => {
MainWindow.minimize()
console.log("minimize")
})
ipcMain.on("maximize", (event, arg) => {
if (!windowFullScreen) {
MainWindow.maximize()
windowFullScreen = true;
}
else {
MainWindow.setSize(800, 600)
windowFullScreen = false;
}
console.log("maximize")
})
ipcMain.on("close", (event, arg) => {
MainWindow.close()
console.log("close")
})
ipcMain.on("read", (event, arg) => {
setInterval(()=>{console.log(usbcomm.read(1))}, 1)
const process = child_process.fork('./readUSB.js')
})
})
ipcRenderer.js
This is where I set up onclick event handlers for the DOM. The relevant part here is read.onclick() which sends "read" message to the main process telling it fork the process and run readUSB.js which I've included below.
const electron = require('electron');
const ipcRenderer = electron.ipcRenderer;
var minimize = document.getElementById("minimize-app");
var maximize = document.getElementById("maximize-app");
var read = document.getElementById("push-to-read");
var close = document.getElementById("close-app");
minimize.onclick = (event) => {
ipcRenderer.send("minimize", "an-argument")
console.log("minimize");
}
maximize.onclick = (event) => {
ipcRenderer.send("maximize", "an-argument")
console.log("maximize")
}
close.onclick = (event) => {
ipcRenderer.send("close", "an-argument")
console.log("close")
}
read.onclick = (event) => {
ipcRenderer.send("read", "an-argument")
console.log("read")
}
readUSB.js
this code should simply read a byte from USB and output it to console, what it does instead is to read nothing, and output null (usbcomm.read() returns null if it didn't read anything). This is just a proof of concept to make sure I can read() asynchronously, but things are obviously not working correctly.
const SerialPort = require('serialport')
const usbcomm = new SerialPort("COM4")
usbcomm.setEncoding('utf-8')
usb_rx();
function usb_rx()
{
console.log("running in child process!")
console.log(usbcomm.read(1))
}
if I run this code as an event handler in response to a "read" message from the renderer process, everything runs normally and I read correct responses from usb. I've confirmed this is not a problem with my usb device, and that in simple use cases usbcomm.read() behaves as expected.
I'm wondering now if node-serialport for whatever reason won't work with code run inside of fork().
You can make blocking calls from a renderer. As long as you keep them below 20-30ms and you don't swamp the process with blocking calls you'll be fine. We've used node-serialport
and node-ffi
extensively in the renderer process making dozens of calls per second transferring megabytes of data while keeping the UI responsive with no jerkiness.
Maybe you can't keep the blocking calls short enough? How about doing it in the main process? This is also a bad idea. Blocking the main process blocks IPC between the renderers and GPU process so you'll likely hang the UI this way too.
If you really have to make long blocking synchronous calls, make them in a hidden renderer process. You can block this as much as you like because it's not displaying any UI. To make this easier, you can use electron-remote - renderer-taskpool
feature to bump long running tasks onto another renderer process.