/baud

Elixir Serial Port with Modbus RTU support

Primary LanguageElixirMIT LicenseMIT

baud

Elixir Serial Port with Modbus RTU.

Installation and Usage

  1. Add baud to your list of dependencies in mix.exs:
def deps do
  [{:baud, "~> MAJOR.MINOR"}]
end
  1. Enumerate your serial ports.
["COM1", "/dev/ttyUSB0", "/dev/tty.usbserial-FTVFV143"] = Baud.Enum.list()
  1. Interact with your serial port.
# this echo sample requires a loopback plug
tty =
  case :os.type() do
    {:unix, :darwin} -> "/dev/tty.usbserial-FTYHQD9MA"
    {:unix, :linux} -> "/dev/ttyUSB0"
    {:win32, :nt} -> "COM5"
  end

{:ok, pid} = Baud.start_link(device: tty)

Baud.write(pid, "01234\n56789\n98765\n43210")
{:ok, "01234\n"} = Baud.readln(pid)
{:ok, "56789\n"} = Baud.readln(pid)
{:ok, "98765\n"} = Baud.readln(pid)
{:to, "43210"} = Baud.readln(pid)

Baud.write(pid, "01234\r56789\r98765\r43210")
{:ok, "01234\r"} = Baud.readcr(pid)
{:ok, "56789\r"} = Baud.readcr(pid)
{:ok, "98765\r"} = Baud.readcr(pid)
{:to, "43210"} = Baud.readcr(pid)

Baud.write(pid, "01234\n56789\n98765\n43210")
{:ok, "01234\n"} = Baud.readn(pid, 6)
{:ok, "56789\n"} = Baud.readn(pid, 6)
{:ok, "98765\n"} = Baud.readn(pid, 6)
{:to, "43210"} = Baud.readn(pid, 6)
{:ok, ""} = Baud.readn(pid, 0)

Baud.write(pid, "01234\n")
Baud.write(pid, "56789\n")
Baud.write(pid, "98765\n")
Baud.write(pid, "43210")
:timer.sleep(100)
{:ok, "01234\n56789\n98765\n43210"} = Baud.readall(pid)

Baud.stop(pid)
  1. Connect the RTU master to the testing RTU slave:
# run with: mix slave
alias Baud.Slave
alias Baud.Master

tty0 =
  case :os.type() do
    {:unix, :darwin} -> "/dev/tty.usbserial-FTYHQD9MA"
    {:unix, :linux} -> "/dev/ttyUSB0"
    {:win32, :nt} -> "COM5"
  end

tty1 =
  case :os.type() do
    {:unix, :darwin} -> "/dev/tty.usbserial-FTYHQD9MB"
    {:unix, :linux} -> "/dev/ttyUSB1"
    {:win32, :nt} -> "COM6"
  end

# start your slave with a shared model
model = %{
  0x50 => %{
    {:c, 0x5152} => 0,
    {:i, 0x5354} => 0,
    {:i, 0x5355} => 1,
    {:hr, 0x5657} => 0x6162,
    {:ir, 0x5859} => 0x6364,
    {:ir, 0x585A} => 0x6566
  }
}

{:ok, slave} = Slave.start_link(model: model, device: tty0, speed: 115_200)
{:ok, master} = Master.start_link(device: tty1, speed: 115_200)

# read input
{:ok, [0, 1]} = Master.exec(master, {:ri, 0x50, 0x5354, 2})
# read input registers
{:ok, [0x6364, 0x6566]} = Master.exec(master, {:rir, 0x50, 0x5859, 2})

# toggle coil and read it back
:ok = Master.exec(master, {:fc, 0x50, 0x5152, 0})
{:ok, [0]} = Master.exec(master, {:rc, 0x50, 0x5152, 1})
:ok = Master.exec(master, {:fc, 0x50, 0x5152, 1})
{:ok, [1]} = Master.exec(master, {:rc, 0x50, 0x5152, 1})

# increment holding register and read it back
{:ok, [0x6162]} = Master.exec(master, {:rhr, 0x50, 0x5657, 1})
:ok = Master.exec(master, {:phr, 0x50, 0x5657, 0x6163})
{:ok, [0x6163]} = Master.exec(master, {:rhr, 0x50, 0x5657, 1})

:ok = Master.stop(master)
:ok = Slave.stop(slave)

Testing

# test with socat ttys
./test.sh
# test with custom ttys (null modem)
export TTY0="/dev/ttyUSB0"
export TTY1="/dev/ttyUSB1"
mix test
#in Windows
#requires COM98/COM99 com0com ports
test.bat

Development

Windows

Install Visual C++ 2015 Build Tools by one of the following methods:

From the Windows run command launch cmd /K c:\Users\samuel\Desktop\baud\setenv.bat adjusting your code location accordingly.

Ubuntu

Give yourself access to serial ports with sudo gpasswd -s samuel dialout. Follow the official Elixir installation instructions and install build-essential erlang-dev as well.

MacOS

Give yourself access to serial ports with sudo dseditgroup -o edit -a samuel -t user wheel.

Roadmap

Future

  • Remote compile mix task to Linux & Windows

0.6.1

  • Added test.bat for Windows com0com ports

0.6.0

  • Using now the transport and protocol behaviors defined in modbus package

0.5.7

  • Updated to sniff 0.1.7
  • Updated to modbus 0.3.9
  • Added RTU slave for testability
  • Master module rewrite for readability
  • Baud module rewrite for readability

0.5.6

  • Updated to sniff 0.1.6 (bug fix)
  • Test with fake socat ttys
  • Full posix tty paths required
  • Pass test serial port thru environment variables

0.5.4

  • Updated to sniff 0.1.4

0.5.3

  • Updated to sniff 0.1.3
  • Document Windows/Ubuntu dependencies

0.5.2

  • Updated to sniff 0.1.2 need for ´iex -S mix´ to find native library in Elixir 1.5.1
  • Updated to modbus 0.3.7

0.5.1

  • Updated to sniff 0.1.1
  • Extract NIF to its own repo (sniff)

0.5.0

  • Posix NIF implementation
  • Win32 NIF implementation
  • Baud api simplification
  • Refactored to NIF for improved speed and test isolation
  • Removed sock/loop (will be implemented as part of forward)

0.4.3

  • Add wait4ch to handle non standard line/packet terminators

0.4.2

  • Port proxy added to ensure proper closing on exit status msg
  • Kill tests added for both baud and sock
  • Refactored from genserver to actor

0.4.1

  • Updated Makefile for Windows 10

0.4.0

  • Added sample script (1 y 2)
  • Improved documentation
  • Update to modbus 0.2.0 (refactoring required)

0.3.0

  • Integration test script for modport
  • Added test.sh to isolate tests run
  • RTU master, slave, and tcpgw loop modes
  • Serial port export to socket in raw, text, and modbus mode
  • RTU API matched to modbus package (1,2,3,4,5,6,15,16)
  • Improved timeout handling for shorter test times

0.2.0

  • Interactive RTU: read/write up to 8 coils at once

0.1.0

  • Cross platform native serial port (mac, win, linux)
  • Modbus (tcu-rtu), raw and text loop mode

Research

  • Support udoo neo
  • Support beaglebone black
  • Support raspberry pi 3 B
  • Assess rewriting the windows native code using the win32 api
  • Assess rewriting the windows native code using c#/.net
  • Split into a core package and OS dependant packages holding native code
  • loop* tests still show data corruption when run all at once
  • Implement Modbus ASCII support (no available device)
  • Implement DTR/RTS control and CTS/DSR monitoring
  • Implement separate discard for input and output buffers
  • Unit test 8N1 7E1 7O1 and baud rate setup against a confirmed gauge
  • Get port names required for unit testing from environment variables
  • Implement a clean exit to loop mode for a timely port close and to ensure test isolation
  • Improve debugging: stderr messages are interlaced in mix test output
  • Improve debugging: dev/test/prod conditional output
  • Move from polling to overlapped on Windows
  • Research why interchar timeout is applied when reading a single byte even having many already in the input buffer. Happens on MAC.
  • Research how to bypass the 0.1s minimum granularity on posix systems
  • Research higher baud rates support for posix and win32
  • Research Mix unit test isolation (OS resources cleanup)
  • Research printf to embed variable sized arrays as hex strings