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 ( ) ;

Screenshot of a serial port prompt on a website
User prompt for selecting a BBC micro:bit

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.

Photo of an aeroplane factory
Globe War 2 Castle Bromwich Aeroplane Mill

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.

Screenshot of the internal page for debugging the Web Serial API.
Internal page in Chrome for debugging the Spider web Series API.

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

hamiltonfainceir.blogspot.com

Source: https://web.dev/serial/

0 Response to "How to Continuously Read Serial Port in Visual Studio C#"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel