Swift-nio for serial ports/other devices?

Thank you for a really useful library. I used it in a web project before and now I'm cribbing from it to improve some serial port code.

I'm curious, is there an existing example of making swift-nio work with any old path to a device? Maybe something that starts with an NIOFileHandle rather than a channel of some sort? I don't need protocol support of any kind. Just file descriptor management and that beautiful ByteBuffer...

(Or is this a really bad idea given the size of the dependency and I should just stick with rolling my own)

1 Like

So you can certainly do this, but the devil is in the details. How does your serial port manifest? What system call flow would you use if you weren't using NIO?

I think a colleague has gotten that to work before. Basically open /dev/ttyS0 or whatever it is, then dup(fd) the file descriptor so you've got a pair of file descriptors. Then use NIOPipeBootstrap and pass one of them as the input descriptor and the other one as the output descriptor. NIO will take ownership of those two file descriptors so don't close or do anything else to them.

It's a bit of a crutch that you have to dup the file descriptor but at this point in time, NIO only has bootstraps for sockets (Client/ServerBootstrap) and pipes (which are uni-directional). NIO could totally add a SerialBootstrap which would be exactly the same as NIOPipeBootstrap but only take one file descriptor.


How does your serial port manifest?:

tty.usbmodem, cu.usbmodem, any given device will have custom code with a custom protocol. One day it would be nice to add I2C, SPI, MIDI, etc. but those would be layers on top of this layer anyway. (note: FWIW, a MIDI device would not be a usbmodem)

What system call flow would you use if you weren't using NIO?:

I have a working minimum for what I need in a package with a C module handling the posix code (I'll only be checking Ubuntu and Mac) and a Swift module handling the accessors / data hand off. I use both non-blocking calls and blocking calls wrapped in async accessors (well only one of those really so far)

My next step is to provide the data as Async Sequences, for which I thought ByteBuffer would be helpful. But honestly my protocol will only be a handful of bytes a time in either direction, and doesn't need to be lightning fast (probably 9600 or 57600 baud). I can get done what I need for this project without it.

Why bother with NIO then?

I'll be sharing this code with a group of folks who use lots of dev boards on lots of platforms in lots of interesting ways and swift-nio's cross platform support / builtin large buffer support / overall file descriptor management is much nicer than what I'd be able to bang out in the time allotted for sure. This is a "roll your own protocol" crowd, so if I can get them the bytes, or the bytes nominally chunked on delimiters (start bytes/stop bytes) as a kindness, they're good.

So possible? Worth it? Or was it really written too specifically for network calls to detangle? (which would be understandable).

That's interesting. I'll spend some time looking at that pipe bootstrap!

FWIW: I'm using a pair of InputStream and OutputStream to communicate with serial device nodes (such as an FTDI-compliant OBD2 adapter).

1 Like

FTDI compliant is exactly what most of these device are. Perfect. But what does that stream code look like? I don't see those as a types in the repo. Do you mean AsyncThrowingStream's like in NIOAsyncChannelInboundStream.swift?

He is talking about Foundation NSInputStream/NSOutputStream, it is completely unrelated to async/await and NIO. NSStream | Apple Developer Documentation

Indeed, what @Helge_Hess1 said. For a task simple as that, direct access via the Foundation-builtin Stream classes works fine[1] ­– I'd consider NIO overkill for that.

1: Even with the ... uhm... suboptimal... state swift-coreutils-foundation is in.

Thanks @Helge_Hess1 / @mickeyl !

Honestly I didn't even think to look at Foundation for device interfaces. It really is giant isn't it? I assumed anything like this was in I/OKit or DriverKit and therefore off limits to my cross-platform aspirations. If it's in Foundation, does that mean the (eventual) plan is to have it work on Linux & Windows too at some point?

I agree that swift-nio is overkill for my particular demo project, but I want to show off Swift-talking-to-hardware to a very particular group of people who maybe currently think of Swift as the language to use to make an iOS app if you don't already know Unity. swift-nio is a solid piece of work. And if they'd be working on an IoT project they'd likely pull it in anyway? Maybe? That's the thought.

Maybe having a slide or two comparing all three approaches (custom lib, swift-nio, Streams) would be a nice thing to have. Food for thought for sure. :+1:

EDITED: I/O types in Foundation related conversation

Foundation is pretty big, yes. Today Stream is available on Linux, but I wonder whether it will eventually go away on the Linux side? It is really really old and kinda awkward to use (for concurrent operation you kinda have to spin an own thread and runloop etc etc).

It is relevant in the device interface context because Apple's own ExternalAccessory.framework is using it (I think that's also the primary reason why @mickeyl is using it, Linux code sharing w/ macOS/iOS code).

P.S.: Not sure, but this may also be interesting to you: GitHub - uraimo/SwiftyGPIO: A Swift library for hardware projects on Linux/ARM boards with support for GPIOs/SPI/I2C/PWM/UART/1Wire..

1 Like

Got it. Stream looks handy, though. I hope it gets an update instead. If Stream goes away maybe we could prevail upon the swift-nio team to make to make it a PuTTY clone instead of a Netty one ;)

Yes! Getting SwiftyGPIO up and running is very much on my todo list. I'm also keeping an eye on the SwifIO project, (https://madmachine.io).

Going from chip to client GUI in Swift would be very very nice.

For the UI part @Cosmo did some fancy things on raw hardware: GitHub - Cosmo/OpenSwiftUI: WIP — OpenSwiftUI is an OpenSource implementation of Apple's SwiftUI DSL.

1 Like

CC @rauhul

Actually, just noticed that the dup is unnecessary, there's already takingOwnershipOfDescriptor(inputOutput:) which takes one file descriptor for both input & output.

And the docs even mention serial lines:

This method is useful for specialilsed use-cases where you want to use NIOPipeBootstrap for say a serial line.

My memory really seems to be fading, apparently this method existed since the day I added NIOPipeBootstrap (under the old name withInputOutputDescriptor).

1 Like

Cool! I won't be able to try this out until about a week from now but I'm looking forward to it!