Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Crystal-lang Accessing Serial port

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.

like image 214
girish946 Avatar asked Jun 27 '18 18:06

girish946


1 Answers

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

like image 195
Jarrod Funnell Avatar answered Sep 19 '22 15:09

Jarrod Funnell