It is easier to answer this question in multiple parts to really cover it all:
Q: How do I access a serial port on linux/bsd?
A: Open it as a file. On linux/bsd a serial connection is established the moment a device is plugged in, and is then listed somewhere under /dev/
(these days, usually as /dev/ttyUSB0). In order to access this connection you simply open it like you would a regular file. Sometimes this is actually good enough to start communicating with the device as modern hardware typically works with all baud rates and default flags.
Q: How do I configure a serial/tty device on linux/bsd?
A: Set termios flags on the file. If you do need to configure your connection to set things like baud rate, IXON/IXOFF etc, you can do it before even running your program using stty if it is available. Eg. to set the baud rate you could run: stty -F /dev/ttyUSB0 9600
. And after this is set up you can just open it as a file and start using it.
You can spawn stty
from crystal using Process.run
if you wanted an easy way to configure the device from your app. I would probably recommend this approach over the next solution..
Q: How do I set termios flags from crystal, without using stty?
A: Use the termios posix functions directly.
Crystal actually provides FileDescriptor handles with a few common termios settings such as cooked
, which means it has minimal termios bindings already. We can start by using the existing code for our inspiration:
require "termios" # See above link for contents
#Open the file
serial_file = File.open("/dev/ttyACM0")
raise "Oh no, not a TTY" unless serial_file.tty?
# Fetch the unix FD. It's just a number.
fd = serial_file.fd
# Fetch the file's existing TTY flags
raise "Can't access TTY?" unless LibC.tcgetattr(fd, out mode) == 0
# `mode` now contains a termios struct. Let's enable, umm.. ISTRIP and IXON
mode.c_iflag |= (Termios::InputMode::ISTRIP | Termios::InputMode::IXON).value
# Let's turn off IXOFF too.
mode.c_iflag &= ~Termios::InputMode::IXOFF.value
# Unfun discovery: Termios doesn't have cfset[io]speed available
# Let's add them so changing baud isn't so difficult.
lib LibC
fun cfsetispeed(termios_p : Termios*, speed : SpeedT) : Int
fun cfsetospeed(termios_p : Termios*, speed : SpeedT) : Int
end
# Use the above funcs to set the ispeed and ospeed to your nominated baud rate.
LibC.cfsetispeed(pointerof(mode), Termios::BaudRate::B9600)
LibC.cfsetospeed(pointerof(mode), Termios::BaudRate::B9600)
# Write your changes to the FD.
LibC.tcsetattr(fd, Termios::LineControl::TCSANOW, pointerof(mode))
# Done! Your serial_file handle is ready to use.
To set any other flags, refer to the termios manual, or this nice serial guide I just found.
Q: Is there a library to do all this for me?
A: No :( . Not that I can see, but it would be great if someone made it. It's probably not much work for someone to make one if they had a vested interest :)
read_byte
,read_line
,gets
etc aren't blocking calls as one would expect. When reading from a unixsocket, tcp, udp read commands block exactly as you would expect. – Fernery