I have a React application, where I read data from a microcontroller using a UART cable. The microcontroller is continuously printing a JSON string. The application parses this string and displays its values.
The JSON string coming from my microcontroller looks like this:
{"addr":"00xxxxxxxxxxxxxx","1":{"type":"a","value":40.7}}
I followed this blog post to help me with this, and am using a TransformStream with the same LineBreakTransformer that is used in the blog post so that I can parse a complete JSON string. I have a button that triggers this all on click.
Here is the issue I am facing: If I plug in the USB and quickly press the button, everything works fine. I get the prompt asking me to select the correct COM port, I receive data, and am able to parse it. However, if I plug in the USB, and leave it for a while before pressing the button, I get the COM port prompt, but then get these errors:
Uncaught (in promise) DOMException: A buffer overrun has been detected.
Uncaught (in promise) TypeError: Failed to execute 'pipeTo' on 'ReadableStream': Cannot pipe a locked stream
Also, after I refresh the screen when I get this error, and quickly press the button, it works successfully.
Why is this happening, and how can I resolve this?
Thank you.
Source code:
import React, { useState } from 'react'
class LineBreakTransformer {
constructor() {
this.chunks = ''
}
transform(chunk, controller) {
this.chunks += chunk
const lines = this.chunks.split('\r\n')
this.chunks = lines.pop()
lines.forEach((line) => controller.enqueue(line))
}
flush(controller) {
controller.enqueue(this.chunks)
}
}
const App = () => {
const [macAddr, setMacAddr] = useState('')
const [sensors, setSensors] = useState([])
async function onButtonClick() {
const port = await navigator.serial.requestPort()
await port.open({ baudRate: 115200, bufferSize: 10000000 })
while (port.readable) {
// eslint-disable-next-line no-undef
const textDecoder = new TextDecoderStream()
port.readable.pipeTo(textDecoder.writable)
const reader = textDecoder.readable.pipeThrough(new TransformStream(new LineBreakTransformer())).getReader()
try {
while (true) {
const { value, done } = await reader.read()
if (done) {
reader.releaseLock()
break
}
if (value) {
const { addr, ...sensors } = JSON.parse(value)
setMacAddr(addr)
setSensors(sensors)
}
}
} catch (error) {}
}
}
return (
<div>
<h1>{`macAddr: ${macAddr}`}</h1>
{Object.keys(sensors).map((sensor) => (
<div key={sensor}>
<div>{`Channel: ${sensor}`}</div>
<div>{`Temp: ${sensors[sensor].value}`}</div>
</div>
))}
<button
onClick={async () => {
await onButtonClick()
}}
>
CLick
</button>
</div>
)
}
export default App
Alright guys, I figured out what I was doing wrong. The issue had nothing to do with waiting. The issue was actually on JSON.parse()
.
Since I am reading streams, occasionally I don't get a full packet, and only get the remaining part of the JSON string being sent from the microcontroller, (e.g. only :1}
instead of {"a":1}
. It's just less likely for this to happen when I begin reading immediately after plugging in the device.
When I parse it, my function errors out because it is not valid JSON. Since I have it on a while loop, it then fails at port.readable.pipeTo(textDecoder.writable)
because the reader is locked.
I fixed this by wrapping the parse()
in a try catch block.