Current section

Files

Jump to
circuits_uart test basic_uart_test.exs
Raw

test/basic_uart_test.exs

# SPDX-FileCopyrightText: 2016 Frank Hunleth
# SPDX-FileCopyrightText: 2017 John Careaga
# SPDX-FileCopyrightText: 2018 Adam Kohring
# SPDX-FileCopyrightText: 2018 Jon Carstens
# SPDX-FileCopyrightText: 2018 Tallak Tveide
# SPDX-FileCopyrightText: 2019 Robin Hilliard
# SPDX-FileCopyrightText: 2020 Tom Boland
# SPDX-FileCopyrightText: 2021 Chris D'Ambrosio
#
# SPDX-License-Identifier: Apache-2.0
#
Code.require_file("uart_test.exs", __DIR__)
defmodule BasicUARTTest do
use ExUnit.Case
alias Circuits.UART
setup do
UARTTest.common_setup()
end
defp test_send_and_receive(uart1, uart2, options) do
all_options = [{:active, false} | options]
assert :ok = UART.open(uart1, UARTTest.port1(), all_options)
assert :ok = UART.open(uart2, UARTTest.port2(), all_options)
# uart1 -> uart2
assert :ok = UART.write(uart1, "A")
assert {:ok, "A"} = UART.read(uart2)
# uart2 -> uart1
assert :ok = UART.write(uart2, "B")
assert {:ok, "B"} = UART.read(uart1)
UART.close(uart1)
UART.close(uart2)
end
test "serial ports exist" do
ports = UART.enumerate()
assert is_map(ports)
assert Map.has_key?(ports, UARTTest.port1()), "Can't find #{UARTTest.port1()}"
assert Map.has_key?(ports, UARTTest.port2()), "Can't find #{UARTTest.port2()}"
end
test "simple open and close", %{uart1: uart1} do
assert :ok = UART.open(uart1, UARTTest.port1(), speed: 9600)
assert :ok = UART.close(uart1)
assert :ok = UART.open(uart1, UARTTest.port2())
assert :ok = UART.close(uart1)
UART.close(uart1)
end
test "open same port twice", %{uart1: uart1, uart2: uart2} do
assert :ok = UART.open(uart1, UARTTest.port1())
assert {:error, _} = UART.open(uart2, UARTTest.port1())
UART.close(uart1)
end
test "write and read", %{uart1: uart1, uart2: uart2} do
test_send_and_receive(uart1, uart2, [])
end
test "write iodata", %{uart1: uart1, uart2: uart2} do
assert :ok = UART.open(uart1, UARTTest.port1(), active: false)
assert :ok = UART.open(uart2, UARTTest.port2(), active: false)
assert :ok = UART.write(uart1, ~c"B")
assert {:ok, "B"} = UART.read(uart2)
assert :ok = UART.write(uart1, [~c"AB", ?C, ~c"D", "EFG"])
# Wait for everything to be received in one call
:timer.sleep(100)
assert {:ok, "ABCDEFG"} = UART.read(uart2)
UART.close(uart1)
UART.close(uart2)
end
test "no cr and lf translations", %{uart1: uart1, uart2: uart2} do
# It is very common for CR and NL characters to
# be translated through ttys and serial ports, so
# check this explicitly.
assert :ok = UART.open(uart1, UARTTest.port1(), active: false)
assert :ok = UART.open(uart2, UARTTest.port2(), active: false)
assert :ok = UART.write(uart1, "\n")
assert {:ok, "\n"} = UART.read(uart2)
assert :ok = UART.write(uart1, "\r")
assert {:ok, "\r"} = UART.read(uart2)
UART.close(uart1)
UART.close(uart2)
end
test "all characters pass unharmed", %{uart1: uart1, uart2: uart2} do
assert :ok = UART.open(uart1, UARTTest.port1(), active: false)
assert :ok = UART.open(uart2, UARTTest.port2(), active: false)
# The default is 8-N-1, so this should all work
for char <- 0..255 do
assert :ok = UART.write(uart1, <<char>>)
assert {:ok, <<^char>>} = UART.read(uart2)
end
UART.close(uart1)
UART.close(uart2)
end
test "send and flush", %{uart1: uart1, uart2: uart2} do
assert :ok = UART.open(uart1, UARTTest.port1(), active: false)
assert :ok = UART.open(uart2, UARTTest.port2(), active: false)
assert :ok = UART.write(uart1, "a")
:timer.sleep(100)
assert :ok = UART.flush(uart2, :receive)
assert {:ok, ""} = UART.read(uart2, 0)
assert :ok = UART.write(uart1, "b")
:timer.sleep(100)
assert :ok = UART.flush(uart2, :both)
assert {:ok, ""} = UART.read(uart2, 0)
assert :ok = UART.write(uart1, "c")
:timer.sleep(100)
# unspecifed direction should be :both
assert :ok = UART.flush(uart2)
assert {:ok, ""} = UART.read(uart2, 0)
UART.close(uart1)
UART.close(uart2)
end
test "send more than can be done synchronously", %{uart1: uart1, uart2: uart2} do
# Note: When using the tty0tty driver, both endpoints need to be
# opened or writes will fail with :einval. This is different
# than most regular uarts where writes to nothing just twiddle
# output bits.
assert :ok = UART.open(uart1, UARTTest.port1())
assert :ok = UART.open(uart2, UARTTest.port2())
# Try a big size to trigger a write that can't complete
# immediately. This doesn't always work.
lots_o_data = :binary.copy("a", 5000)
# Allow 10 seconds for write to give it time to complete
assert :ok = UART.write(uart1, lots_o_data, 10000)
UART.close(uart1)
UART.close(uart2)
end
test "send timeout", %{uart1: uart1} do
# Don't run against tty0tty since it sends data almost
# instantaneously. Also, Windows appears to have a deep
# send buffer. Need to investigate the Windows failure more.
if !String.starts_with?(UARTTest.port1(), "tnt") && !:os.type() == {:windows, :nt} do
assert :ok = UART.open(uart1, UARTTest.port1(), speed: 1200)
# Send more than can be sent on a 1200 baud link
# in 10 milliseconds
lots_o_data = :binary.copy("a", 5000)
assert {:error, :eagain} = UART.write(uart1, lots_o_data, 10)
UART.close(uart1)
end
end
test "sends coalesce into one read", %{uart1: uart1, uart2: uart2} do
assert :ok = UART.open(uart1, UARTTest.port1(), active: false)
assert :ok = UART.open(uart2, UARTTest.port2(), active: false)
assert :ok = UART.write(uart1, "a")
assert :ok = UART.write(uart1, "b")
assert :ok = UART.write(uart1, "c")
:timer.sleep(100)
assert {:ok, "abc"} = UART.read(uart2)
UART.close(uart1)
UART.close(uart2)
end
test "error writing to a closed port", %{uart1: uart1, uart2: uart2} do
assert :ok = UART.open(uart2, UARTTest.port2(), active: false)
# port1 not yet opened
assert {:error, :ebadf} = UART.write(uart1, "A")
# port1 opened
assert :ok = UART.open(uart1, UARTTest.port1(), active: false)
assert :ok = UART.write(uart1, "A")
assert {:ok, "A"} = UART.read(uart2)
# port1 closed
assert :ok = UART.close(uart1)
assert {:error, :ebadf} = UART.write(uart1, "B")
UART.close(uart1)
UART.close(uart2)
end
test "open doesn't return error AND send a message when in active mode", %{
uart1: uart1
} do
:ok = UART.configure(uart1, active: true)
{:error, _} = UART.open(uart1, "does_not_exist")
refute_received {:circuits_uart, _port, _}, "No messages should be sent if open returns error"
:ok = UART.configure(uart1, active: false)
{:error, _} = UART.open(uart1, "does_not_exist", active: true)
refute_received {:circuits_uart, _port, _}, "No messages should be sent if open returns error"
end
test "open doesn't send messages in passive mode for open errors", %{
uart1: uart1
} do
:ok = UART.configure(uart1, active: false)
{:error, :enoent} = UART.open(uart1, "does_not_exist")
refute_received {:circuits_uart, _, _}, "No messages should be sent in passive mode"
{:error, :enoent} = UART.open(uart1, "does_not_exist", active: false)
refute_received {:circuits_uart, _, _}, "No messages should be sent in passive mode"
end
test "error writing to a closed port when using framing", %{uart1: uart1, uart2: uart2} do
framing = {UART.Framing.Line, separator: "\n"}
assert :ok = UART.open(uart2, UARTTest.port2(), active: false, framing: framing)
# port1 not yet opened
assert {:error, :ebadf} = UART.write(uart1, "A")
# port1 opened
assert :ok = UART.open(uart1, UARTTest.port1(), active: false, framing: framing)
assert :ok = UART.write(uart1, "A")
assert {:ok, "A"} = UART.read(uart2)
# port1 closed
assert :ok = UART.close(uart1)
assert {:error, :ebadf} = UART.write(uart1, "B")
UART.close(uart1)
UART.close(uart2)
end
test "active mode receive", %{uart1: uart1, uart2: uart2} do
assert :ok = UART.open(uart1, UARTTest.port1(), active: false)
assert :ok = UART.open(uart2, UARTTest.port2(), active: true)
port2 = UARTTest.port2()
# First write
assert :ok = UART.write(uart1, "a")
assert_receive {:circuits_uart, ^port2, "a"}
# Only one message should be sent
refute_receive {:circuits_uart, _, _}
# Try another write
assert :ok = UART.write(uart1, "b")
assert_receive {:circuits_uart, ^port2, "b"}
UART.close(uart1)
UART.close(uart2)
end
test "active mode receive with id: :pid", %{uart1: uart1, uart2: uart2} do
assert :ok = UART.open(uart1, UARTTest.port1(), active: false)
assert :ok = UART.open(uart2, UARTTest.port2(), active: true, id: :pid)
port2 = UARTTest.port2()
# First write
assert :ok = UART.write(uart1, "a")
assert_receive {:circuits_uart, ^uart2, "a"}
# Only one message should be sent
refute_receive {:circuits_uart, _, _}
# Configure to id: :name
UART.configure(uart2, id: :name)
# Try another write
assert :ok = UART.write(uart1, "b")
assert_receive {:circuits_uart, ^port2, "b"}
# Configure to id: :pid
UART.configure(uart2, id: :pid)
# Try another write
assert :ok = UART.write(uart1, "c")
assert_receive {:circuits_uart, ^uart2, "c"}
UART.close(uart1)
UART.close(uart2)
end
test "error when calling read in active mode", %{uart1: uart1} do
assert :ok = UART.open(uart1, UARTTest.port1(), active: true)
assert {:error, :einval} = UART.read(uart1)
UART.close(uart1)
end
@tag skip: "Fix test on Windows"
test "active mode on then off", %{uart1: uart1, uart2: uart2} do
assert :ok = UART.open(uart1, UARTTest.port1(), active: false)
assert :ok = UART.open(uart2, UARTTest.port2(), active: false)
port2 = UARTTest.port2()
assert :ok = UART.write(uart1, "a")
assert {:ok, "a"} = UART.read(uart2, 100)
assert :ok = UART.configure(uart2, active: true)
assert :ok = UART.write(uart1, "b")
assert_receive {:circuits_uart, ^port2, "b"}
assert :ok = UART.configure(uart2, active: false)
assert :ok = UART.write(uart1, "c")
assert {:ok, "c"} = UART.read(uart2, 100)
refute_receive {:circuits_uart, _, _}
assert :ok = UART.configure(uart2, active: true)
assert :ok = UART.write(uart1, "d")
assert_receive {:circuits_uart, ^port2, "d"}
refute_receive {:circuits_uart, _, _}
UART.close(uart1)
UART.close(uart2)
end
test "active mode gets event when write fails", %{uart1: uart1} do
# This only works with tty0tty since it fails write operations if no
# receiver.
if String.starts_with?(UARTTest.port1(), "tnt") do
assert :ok = UART.open(uart1, UARTTest.port1(), active: true)
port1 = UARTTest.port1()
assert {:error, errno} = UART.write(uart1, "a", 0)
assert errno in [:eagain, :einval]
assert_receive {:circuits_uart, ^port1, {:error, _eagain_or_einval}}
UART.close(uart1)
end
end
test "read timeout works", %{uart1: uart1} do
assert :ok = UART.open(uart1, UARTTest.port1(), active: false)
# 0 duration timeout
start = System.monotonic_time(:millisecond)
assert {:ok, <<>>} = UART.read(uart1, 0)
elapsed_time = System.monotonic_time(:millisecond) - start
assert_in_delta elapsed_time, 0, 100
# 500 ms timeout
start = System.monotonic_time(:millisecond)
assert {:ok, <<>>} = UART.read(uart1, 500)
elapsed_time = System.monotonic_time(:millisecond) - start
assert_in_delta elapsed_time, 400, 600
UART.close(uart1)
end
test "opened uart returns the configuration", %{uart1: uart1} do
:ok = UART.open(uart1, UARTTest.port1(), active: false, speed: 57_600)
{name, opts} = UART.configuration(uart1)
assert name == UARTTest.port1()
assert Keyword.get(opts, :active) == false
assert Keyword.get(opts, :speed) == 57_600
UART.close(uart1)
end
test "reconfiguring the uart updates the configuration", %{uart1: uart1} do
:ok = UART.open(uart1, UARTTest.port1(), active: false, speed: 9600)
{name, opts} = UART.configuration(uart1)
assert name == UARTTest.port1()
assert Keyword.get(opts, :active) == false
assert Keyword.get(opts, :speed) == 9600
:ok = UART.configure(uart1, active: true, speed: 115_200)
{name, opts} = UART.configuration(uart1)
assert name == UARTTest.port1()
assert Keyword.get(opts, :active) == true
assert Keyword.get(opts, :speed) == 115_200
UART.close(uart1)
end
# Software flow control doesn't work and I'm not sure what the deal is
if false do
test "xoff filtered with software flow control", %{uart1: uart1, uart2: uart2} do
if !String.starts_with?(UARTTest.port1(), "tnt") do
assert :ok = UART.open(uart1, UARTTest.port1(), flow_control: :softare, active: false)
assert :ok = UART.open(uart2, UARTTest.port2(), active: false)
# Test that uart1 filters xoff
assert :ok = UART.write(uart2, @xoff)
assert {:ok, ""} = UART.read(uart1, 100)
# Test that uart1 filters xon
assert :ok = UART.write(uart2, @xon)
assert {:ok, ""} = UART.read(uart1, 100)
# Test that uart1 doesn't filter other things
assert :ok = UART.write(uart2, "Z")
assert {:ok, "Z"} = UART.read(uart1, 100)
UART.close(uart1)
UART.close(uart2)
end
end
test "software flow control pausing", %{uart1: uart1, uart2: uart2} do
if !String.starts_with?(UARTTest.port1(), "tnt") do
assert :ok = UART.open(uart1, UARTTest.port1(), flow_control: :softare, active: false)
assert :ok = UART.open(uart2, UARTTest.port2(), active: false)
# send XOFF to uart1 so that it doesn't transmit
assert :ok = UART.write(uart2, @xoff)
assert :ok = UART.write(uart1, "a")
assert {:ok, ""} = UART.read(uart2, 100)
# send XON to see if we get the "a"
assert :ok = UART.write(uart2, @xon)
assert {:ok, "a"} = UART.read(uart2, 100)
UART.close(uart1)
UART.close(uart2)
end
end
end
test "call controlling_process", %{uart1: uart1} do
assert :ok = UART.open(uart1, UARTTest.port1(), active: false)
assert :ok = UART.controlling_process(uart1, self())
UART.close(uart1)
end
test "changing config on open port" do
# Implement me.
end
test "opening port with custom speed", %{uart1: uart1} do
assert :ok = UART.open(uart1, UARTTest.port1(), active: false, speed: 192_000)
UART.close(uart1)
end
test "write and read at standard speeds", %{uart1: uart1, uart2: uart2} do
standard_speeds = [9600, 19200, 38400, 57600, 115_200]
for speed <- standard_speeds do
test_send_and_receive(uart1, uart2, speed: speed)
end
end
test "write and read at custom speeds", %{uart1: uart1, uart2: uart2} do
# 31250 - MIDI
# 64000, 192_000, 200_000 - Random speeds seen in the field
#
# NOTE: This test is highly dependent on the UARTs under test being able
# to support these baud rates, so it may not be a Circuits.UART
# issue if it fails.
custom_speeds = [31250, 64000, 192_000, 200_000]
for speed <- custom_speeds do
test_send_and_receive(uart1, uart2, speed: speed)
end
end
end