Crystal-lang Accessing Serial port
Asked Answered
P

1

6

I want to access the serial port using Crystal lang.

I have following code in python. I want to write the equivalent Crystal-lang code for a pet project.

import serial

def readSerData():

    s = ser.readline()
    if s:
        print(s)
        result = something(s) #do other stuff
        return result

if __name__ == '__main__':

    ser = serial.Serial("/dev/ttyUSB0", 9600)
    while True:
        data = readSerData()
        #do something with data

I couldn't find any library for accessing the serial port.

What is the proper way for accessing serial port in crystal-lang?

Thanks in advance.

Punish answered 27/6, 2018 at 18:47 Comment(0)
I
9

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 :)

Independence answered 1/7, 2018 at 3:7 Comment(3)
Reading a com port as a file after settings the speed through stty has one big issue. 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
It is a massive can of worms to get into the difficulties one experiences when wrestling with *nix, IO, and nonblocking operations on different handles.. But in short, crystal uses nonblocking IO in order to make cooperative threads more.. cooperative. So this probably works as expected. If not, try messing with VTIME+VMIN, as well as crystal's blocking=true flag on FileDescriptor objectsIndependence
I've found that I can set a timeout and then rescue the exception that gets thrown and sleep for a few ms. Doesn't seem like the most elegant way to yield to the next fiber while waiting though.Fernery

© 2022 - 2024 — McMap. All rights reserved.