How to Continuously Read Serial Port in Visual Studio C#
Read from and write to a serial port
The Web Serial API allows websites to communicate with serial devices.
— Updated
What is the Web Serial API? #
A serial port is a bidirectional advice interface that allows sending and receiving data byte by byte.
The Spider web Serial API provides a mode for websites to read from and write to a serial device with JavaScript. Serial devices are connected either through a serial port on the user'due south system or through removable USB and Bluetooth devices that emulate a serial port.
In other words, the Web Serial API bridges the web and the concrete world by allowing websites to communicate with serial devices, such as microcontrollers and 3D printers.
This API is also a great companion to WebUSB as operating systems require applications to communicate with some series ports using their higher-level serial API rather than the low-level USB API.
Suggested use cases #
In the educational, hobbyist, and industrial sectors, users connect peripheral devices to their computers. These devices are frequently controlled by microcontrollers via a serial connection used by custom software. Some custom software to control these devices is built with spider web technology:
- Arduino Create
- Betaflight Configurator
- Espruino Web IDE
- Microsoft MakeCode
In some cases, websites communicate with the device through an amanuensis application that users installed manually. In others, the application is delivered in a packaged application through a framework such every bit Electron. And in others, the user is required to perform an additional step such every bit copying a compiled application to the device via a USB flash bulldoze.
In all these cases, the user experience will exist improved by providing directly advice betwixt the website and the device that it is controlling.
Current status #
Using the Spider web Serial API #
Characteristic detection #
To check if the Web Serial API is supported, use:
if ( "serial" in navigator) {
// The Web Serial API is supported.
}
Open up a serial port #
The Web Serial API is asynchronous by pattern. This prevents the website UI from blocking when pending input, which is important because series data can be received at any time, requiring a way to listen to it.
To open up a serial port, get-go admission a SerialPort
object. For this, y'all can either prompt the user to select a single serial port by calling navigator.serial.requestPort()
in response to a user gesture such as touch or mouse click, or pick one from navigator.serial.getPorts()
which returns a listing of series ports the website has been granted access to.
document. querySelector ( 'button' ) . addEventListener ( 'click' , async ( ) => {
// Prompt user to select whatever serial port.
const port = expect navigator.serial. requestPort ( ) ;
} ) ;
// Become all serial ports the user has previously granted the website access to.
const ports = await navigator.series. getPorts ( ) ;
The navigator.series.requestPort()
function takes an optional object literal that defines filters. Those are used to match any serial device continued over USB with a mandatory USB vendor (usbVendorId
) and optional USB product identifiers (usbProductId
).
// Filter on devices with the Arduino Uno USB Vendor/Product IDs.
const filters = [
{ usbVendorId: 0x2341 , usbProductId: 0x0043 } ,
{ usbVendorId: 0x2341 , usbProductId: 0x0001 }
] ; // Prompt user to select an Arduino Uno device.
const port = await navigator.series. requestPort ( { filters } ) ;
const { usbProductId, usbVendorId } = port. getInfo ( ) ;
Calling requestPort()
prompts the user to select a device and returns a SerialPort
object. Once you accept a SerialPort
object, calling port.open()
with the desired baud rate will open the serial port. The baudRate
dictionary member specifies how fast data is sent over a serial line. It is expressed in units of $.25-per-2d (bps). Check your device's documentation for the correct value equally all the data yous send and receive will be gibberish if this is specified incorrectly. For some USB and Bluetooth devices that emulate a serial port this value may be safely set to whatever value every bit information technology is ignored by the emulation.
// Prompt user to select whatever serial port.
const port = wait navigator.serial. requestPort ( ) ; // Expect for the series port to open.
await port. open up ( { baudRate: 9600 } ) ;
Y'all can likewise specify any of the options below when opening a serial port. These options are optional and have convenient default values.
-
dataBits
: The number of data bits per frame (either 7 or 8). -
stopBits
: The number of end bits at the end of a frame (either 1 or 2). -
parity
: The parity mode (either"none"
,"even"
or"odd"
). -
bufferSize
: The size of the read and write buffers that should be created (must be less than 16MB). -
flowControl
: The period control mode (either"none"
or"hardware"
).
Read from a serial port #
Input and output streams in the Web Series API are handled by the Streams API.
After the serial port connexion is established, the readable
and writable
properties from the SerialPort
object return a ReadableStream and a WritableStream. Those will be used to receive data from and send information to the serial device. Both use Uint8Array
instances for information transfer.
When new data arrives from the series device, port.readable.getReader().read()
returns two backdrop asynchronously: the value
and a done
boolean. If done
is truthful, the serial port has been closed or at that place is no more data coming in. Calling port.readable.getReader()
creates a reader and locks readable
to it. While readable
is locked, the series port can't be closed.
const reader = port.readable. getReader ( ) ; // Listen to information coming from the serial device.
while ( true ) {
const { value, done } = await reader. read ( ) ;
if (done) {
// Allow the serial port to be closed later.
reader. releaseLock ( ) ;
break ;
}
// value is a Uint8Array.
console. log (value) ;
}
Some not-fatal series port read errors can happen under some weather condition such as buffer overflow, framing errors, or parity errors. Those are thrown every bit exceptions and can be caught by adding some other loop on pinnacle of the previous one that checks port.readable
. This works because as long equally the errors are non-fatal, a new ReadableStream is created automatically. If a fatal error occurs, such as the series device beingness removed, then port.readable
becomes zip.
while (port.readable) {
const reader = port.readable. getReader ( ) ;
try {
while ( true ) {
const { value, washed } = look reader. read ( ) ;
if (done) {
// Permit the serial port to exist closed after.
reader. releaseLock ( ) ;
pause ;
}
if (value) {
console. log (value) ;
}
}
} take hold of (fault) {
// TODO: Handle non-fatal read error.
}
}
If the serial device sends text back, you can pipage port.readable
through a TextDecoderStream
every bit shown beneath. A TextDecoderStream
is a transform stream that grabs all Uint8Array
chunks and converts them to strings.
const textDecoder = new TextDecoderStream ( ) ;
const readableStreamClosed = port.readable. pipeTo (textDecoder.writable) ;
const reader = textDecoder.readable. getReader ( ) ;
// Heed to information coming from the serial device.
while ( true ) {
const { value, done } = wait reader. read ( ) ;
if (done) {
// Allow the serial port to be closed subsequently.
reader. releaseLock ( ) ;
pause ;
}
// value is a string.
console. log (value) ;
}
Write to a serial port #
To send data to a serial device, pass data to port.writable.getWriter().write()
. Calling releaseLock()
on port.writable.getWriter()
is required for the series port to be closed later.
const writer = port.writable. getWriter ( ) ; const information = new Uint8Array ( [ 104 , 101 , 108 , 108 , 111 ] ) ; // hi
await author. write (data) ;
// Allow the series port to be closed subsequently.
writer. releaseLock ( ) ;
Send text to the device through a TextEncoderStream
piped to port.writable
as shown beneath.
const textEncoder = new TextEncoderStream ( ) ;
const writableStreamClosed = textEncoder.readable. pipeTo (port.writable) ; const writer = textEncoder.writable. getWriter ( ) ;
expect writer. write ( "hi" ) ;
Shut a serial port #
port.shut()
closes the serial port if its readable
and writable
members are unlocked, meaning releaseLock()
has been chosen for their respective reader and writer.
await port. close ( ) ;
However, when continuously reading data from a serial device using a loop, port.readable
will always be locked until it encounters an error. In this case, calling reader.cancel()
will forcefulness reader.read()
to resolve immediately with { value: undefined, done: true }
and therefore assuasive the loop to telephone call reader.releaseLock()
.
// Without transform streams. let keepReading = truthful ;
let reader;
async function readUntilClosed ( ) {
while (port.readable && keepReading) {
reader = port.readable. getReader ( ) ;
try {
while ( true ) {
const { value, done } = await reader. read ( ) ;
if (done) {
// reader.cancel() has been called.
break ;
}
// value is a Uint8Array.
console. log (value) ;
}
} catch (mistake) {
// Handle error...
} finally {
// Allow the serial port to exist airtight later.
reader. releaseLock ( ) ;
}
}
wait port. shut ( ) ;
}
const closedPromise = readUntilClosed ( ) ;
document. querySelector ( 'button' ) . addEventListener ( 'click' , async ( ) => {
// User clicked a push button to close the series port.
keepReading = simulated ;
// Forcefulness reader.read() to resolve immediately and later
// call reader.releaseLock() in the loop case higher up.
reader. cancel ( ) ;
await closedPromise;
} ) ;
Closing a serial port is more than complicated when using transform streams (like TextDecoderStream
and TextEncoderStream
). Call reader.cancel()
as before. So call writer.close()
and port.close()
. This propagates errors through the transform streams to the underlying serial port. Because error propagation doesn't happen immediately, you need to use the readableStreamClosed
and writableStreamClosed
promises created earlier to discover when port.readable
and port.writable
take been unlocked. Cancelling the reader
causes the stream to be aborted; this is why yous must grab and ignore the resulting error.
// With transform streams.
const textDecoder = new TextDecoderStream ( ) ;
const readableStreamClosed = port.readable. pipeTo (textDecoder.writable) ;
const reader = textDecoder.readable. getReader ( ) ;
// Mind to data coming from the serial device.
while ( true ) {
const { value, done } = await reader. read ( ) ;
if (done) {
reader. releaseLock ( ) ;
break ;
}
// value is a string.
panel. log (value) ;
}
const textEncoder = new TextEncoderStream ( ) ;
const writableStreamClosed = textEncoder.readable. pipeTo (port.writable) ;
reader. cancel ( ) ;
await readableStreamClosed. catch ( ( ) => { /* Ignore the error */ } ) ;
writer. shut ( ) ;
await writableStreamClosed;
await port. shut ( ) ;
Heed to connexion and disconnection #
If a serial port is provided by a USB device then that device may be connected or disconnected from the system. When the website has been granted permission to admission a series port, information technology should monitor the connect
and disconnect
events.
navigator.series. addEventListener ( "connect" , ( result ) => {
// TODO: Automatically open result.target or warn user a port is available.
} ) ; navigator.series. addEventListener ( "disconnect" , ( event ) => {
// TODO: Remove |event.target| from the UI.
// If the serial port was opened, a stream error would be observed equally well.
} ) ;
Handle signals #
Later on establishing the serial port connection, you can explicitly query and set signals exposed past the series port for device detection and menstruum control. These signals are defined every bit boolean values. For example, some devices such every bit Arduino volition enter a programming way if the Information Terminal Ready (DTR) indicate is toggled.
Setting output signals and getting input signals are respectively done by calling port.setSignals()
and port.getSignals()
. See usage examples below.
// Turn off Serial Break signal.
await port. setSignals ( { break : false } ) ; // Plow on Data Last Prepare (DTR) signal.
await port. setSignals ( { dataTerminalReady: true } ) ;
// Plow off Request To Send (RTS) point.
look port. setSignals ( { requestToSend: imitation } ) ;
const signals = look port. getSignals ( ) ;
console. log ( ` Clear To Send: ${signals.clearToSend} ` ) ;
panel. log ( ` Data Carrier Find: ${signals.dataCarrierDetect} ` ) ;
console. log ( ` Data Prepare Ready: ${signals.dataSetReady} ` ) ;
console. log ( ` Ring Indicator: ${signals.ringIndicator} ` ) ;
Transforming streams #
When yous receive data from the serial device, you lot won't necessarily get all of the data at once. Information technology may be arbitrarily chunked. For more than information, encounter Streams API concepts.
To deal with this, you can use some built-in transform streams such as TextDecoderStream
or create your own transform stream which allows y'all to parse the incoming stream and render parsed data. The transform stream sits between the series device and the read loop that is consuming the stream. It can apply an arbitrary transform before the data is consumed. Think of it like an associates line: as a widget comes down the line, each step in the line modifies the widget, so that by the fourth dimension it gets to its final destination, information technology'due south a fully performance widget.
For instance, consider how to create a transform stream form that consumes a stream and chunks it based on line breaks. Its transform()
method is called every fourth dimension new data is received by the stream. It tin can either enqueue the data or save information technology for later. The affluent()
method is called when the stream is airtight, and it handles whatsoever data that hasn't been candy yet.
To apply the transform stream grade, you demand to pipage an incoming stream through it. In the 3rd code instance under Read from a serial port, the original input stream was only piped through a TextDecoderStream
, so nosotros need to call pipeThrough()
to pipe it through our new LineBreakTransformer
.
form LineBreakTransformer {
constructor ( ) {
// A container for holding stream information until a new line.
this .chunks = "" ;
} transform ( chunk, controller ) {
// Append new chunks to existing chunks.
this .chunks += chunk;
// For each line breaks in chunks, send the parsed lines out.
const lines = this .chunks. divide ( "\r\n" ) ;
this .chunks = lines. pop ( ) ;
lines. forEach ( ( line ) => controller. enqueue (line) ) ;
}
flush ( controller ) {
// When the stream is closed, flush any remaining chunks out.
controller. enqueue ( this .chunks) ;
}
}
const textDecoder = new TextDecoderStream ( ) ;
const readableStreamClosed = port.readable. pipeTo (textDecoder.writable) ;
const reader = textDecoder.readable
. pipeThrough ( new TransformStream ( new LineBreakTransformer ( ) ) )
. getReader ( ) ;
For debugging series device advice problems, apply the tee()
method of port.readable
to separate the streams going to or from the serial device. The 2 streams created can exist consumed independently and this allows yous to print i to the panel for inspection.
const [appReadable, devReadable] = port.readable. tee ( ) ; // You may want to update UI with incoming data from appReadable
// and log incoming data in JS panel for inspection from devReadable.
Dev Tips #
Debugging the Web Serial API in Chrome is piece of cake with the internal page, about://device-log
where y'all can run across all serial device related events in one single place.
Codelab #
In the Google Developer codelab, you'll utilize the Spider web Serial API to interact with a BBC micro:fleck board to show images on its 5x5 LED matrix.
Browser support #
The Spider web Serial API is available on all desktop platforms (Chrome Bone, Linux, macOS, and Windows) in Chrome 89.
Polyfill #
On Android, support for USB-based serial ports is possible using the WebUSB API and the Serial API polyfill. This polyfill is express to hardware and platforms where the device is accessible via the WebUSB API because it has non been claimed by a built-in device driver.
Security and privacy #
The spec authors have designed and implemented the Web Series API using the core principles divers in Controlling Admission to Powerful Web Platform Features, including user control, transparency, and ergonomics. The ability to use this API is primarily gated by a permission model that grants admission to only a single serial device at a time. In response to a user prompt, the user must take active steps to select a particular serial device.
To understand the security tradeoffs, check out the security and privacy sections of the Web Series API Explainer.
Feedback #
The Chrome team would love to hear nigh your thoughts and experiences with the Spider web Serial API.
Tell us about the API blueprint #
Is in that location something about the API that doesn't piece of work equally expected? Or are at that place missing methods or properties that you need to implement your idea?
File a spec upshot on the Web Serial API GitHub repo or add your thoughts to an existing event.
Report a problem with the implementation #
Did you notice a bug with Chrome's implementation? Or is the implementation different from the spec?
File a bug at https://new.crbug.com. Be sure to include equally much item as you tin can, provide simple instructions for reproducing the problems, and have Components gear up to Blink>Serial
. Glitch works great for sharing quick and easy repros.
Show support #
Are you planning to use the Web Serial API? Your public support helps the Chrome squad prioritize features and shows other browser vendors how critical it is to support them.
Transport a tweet to @ChromiumDev using the hashtag #SerialAPI
and let u.s. know where and how yous're using it.
Helpful links #
- Specification
- Tracking bug
- ChromeStatus.com entry
- Glimmer Component:
Blink>Serial
Demos #
- Serial Terminal
- Espruino Web IDE
Acknowledgements #
Thanks to Reilly Grant and Joe Medley for their reviews of this article. Aeroplane factory photo by Birmingham Museums Trust on Unsplash.
Last updated: — Improve article
Return to all articles
Source: https://web.dev/serial/
0 Response to "How to Continuously Read Serial Port in Visual Studio C#"
Post a Comment