From 46f39f6506c44ad294364f3d0acd1bed0a910cd2 Mon Sep 17 00:00:00 2001 From: Andrew Adare Date: Fri, 14 Aug 2020 09:26:28 -0600 Subject: [PATCH 1/3] Improvements to interface, testing, and examples (#61) * Remove util.jl for #58. Also, JuliaFormatter. * Remove utils.jl for #58 * Implement Base.isopen method. Rename boolean SerialPort.open field to SerialPort.is_open. Remove Base function name qualifications now that they are imported. * Rename eof field to is_eof for consistency with is_open. * Add read_timeout_ms field to SerialPort. Remove open_serial_port which is unused and completely redundant with open. * Begin overhauling low-level tests * Add methods for sp_blocking_read and sp_nonblocking_read for #58 * For #58: Add read_timeout_ms field to SerialPort. Remove read methods except for byte reading. Use new nonblocking_read name for old behavior. * Export nonblocking_read * Update console.jl for changes to LibSerialPort. See #58 * Update tests for changes to LibSerialPort. See #58 * update runtests.jl * Return byte counts from blocking read and write functions * blocking writes no longer require Int conversion. Generalize write() comments. * Stop writing ADC values by default. Set write_adc = true to go back to old behavior. * Remove unused test. Adapt for changes to example firmware. * Allow MCU time to initialize UART. * Add minimal working example * Update example in README for #52 * Add configurable delay and a few comments. * Tests should now pass with either an Arduino running serial_example.ino or a USB-to-UART adapter in loopback mode. * Document possible HW configs for tests * Convert SPReturn value to Int as recommended in #61. Rename handle_error -> check. * Int conversion no longer required for sp_input_waiting * Fix doc typo in runtests.jl * Remove unnecessary Cint conversion * Run JuliaFormatter for consistent indentation. Remove more extraneous conversions. * Remove unnecessary Csize_t and Cuint conversions in ccall args * Add get_port_settings methods. For troubleshooting #55, unit testing, and general utility. * Add tests to test_port_configuration * Change serial_example to behave like a loopback device. * Adjust console.jl for changes to serial_example.ino, i.e. to work with any loopback device * Make error in sp_set_bits more transparent * Fix tests for #55. Remove port-copying tests, which did not do what I originally thought they did. * Update header comments in runtests.jl --- README.md | 39 +- examples/console.jl | 39 +- examples/mwe.jl | 31 ++ examples/serial_example/serial_example.ino | 34 +- src/LibSerialPort.jl | 17 +- src/high-level-api.jl | 255 +++++----- src/utils.jl | 65 --- src/wrap.jl | 520 +++++++++++++++------ test/runtests.jl | 35 +- test/test-high-level-api.jl | 115 +++-- test/test-low-level-api.jl | 242 ++++------ 11 files changed, 759 insertions(+), 633 deletions(-) create mode 100644 examples/mwe.jl delete mode 100644 src/utils.jl diff --git a/README.md b/README.md index 0cced2a..2dc835f 100644 --- a/README.md +++ b/README.md @@ -16,34 +16,31 @@ Apart from a very few non-essential functions, the entire library API (about 75 Try julia> using LibSerialPort - julia> list_ports() - -to get a list of ports detected on your system. + julia> list_ports() # or get_port_list() for an array of names The examples/ directory contains a simple serial console for the command line. This may serve as a useful starting point for your application. The serial_example.ino sketch can be flashed to a microcontroller supported by the Arduino environment. +```julia +using LibSerialPort -The tests are also worth looking at for demonstration of i/o and configuration. They can be run via `julia test/runtests.jl
`. Unless the address of your device matches that in runtests.jl, doing `pkg> test LibSerialPort` will fail. This problem would be addressed by [support for args](https://github.com/JuliaLang/Pkg.jl/issues/518) in the Pkg REPL. - -Note that on Windows, returning an OS-level port handle is not yet supported. +# Modify these as needed +name = "/dev/ttyS0" +baudrate = 115200 -### Reading with timeouts +# Snippet from examples/mwe.jl +LibSerialPort.open(portname, baudrate) do sp + sleep(2) -Methods for `readline` and `readuntil` are exported that allow for a timeout in seconds. + if bytesavailable(sp) > 0 + println(String(read(sp))) + end -i.e. -```julia -LibSerialPort.open(port, 115200) do s - write(s, "input") - ret = readline(s, 1.0) #try to readline with a 1 second timeout - @show ret + write(sp, "hello\n") + sleep(0.1) + println(readline(sp)) end ``` -```julia -LibSerialPort.open(port, 115200) do s - write(s, "input") - ret = readuntil(s, 'x', 1.0) #try to readuntil char `x` with a 1 second timeout - @show ret -end -``` +The tests are also worth looking at for demonstration of i/o and configuration. They can be run via `julia test/runtests.jl
`. Unless the address of your device matches that in runtests.jl, doing `pkg> test LibSerialPort` will fail. This problem would be addressed by [support for args](https://github.com/JuliaLang/Pkg.jl/issues/518) in the Pkg REPL. + +Note that on Windows, returning an OS-level port handle is not yet supported. diff --git a/examples/console.jl b/examples/console.jl index ebe6f0a..3c24266 100644 --- a/examples/console.jl +++ b/examples/console.jl @@ -1,35 +1,36 @@ -# Basic serial console. -# Data is read from a serial device and lines (but not individual keypresses) -# are written to the device asynchronously. +""" +Basic line-buffering serial console. +Data is read asynchronously from a serial device and user input is written +by line (not keypress). + +If a serial device is connected that echoes bytes, each line of user input +should be duplicated. +""" using LibSerialPort function serial_loop(sp::SerialPort) - input_line = "" + user_input = "" mcu_message = "" println("Starting I/O loop. Press ESC [return] to quit") while true # Poll for new data without blocking - @async input_line = readline(keep=true) - @async mcu_message *= read(sp, String) - - # Alternative read method: - # Requires setting a timeout and may cause bottlenecks - # @async mcu_message = readuntil(sp, "\r\n", 50) + @async user_input = readline(keep=true) + @async mcu_message *= String(nonblocking_read(sp)) - occursin("\e", input_line) && exit() + occursin("\e", user_input) && exit() # escape - # Send user input to device - if endswith(input_line, '\n') - write(sp, "$input_line") - input_line = "" + # Send user input to device with ENTER + if endswith(user_input, '\n') + write(sp, "$user_input") + user_input = "" end - # Print message from device - if occursin("\r\n", mcu_message) - lines = split(mcu_message, "\r\n") + # Print response from device as a line + if occursin("\n", mcu_message) + lines = split(mcu_message, "\n") while length(lines) > 1 println(popfirst!(lines)) end @@ -56,4 +57,4 @@ function console(args...) serial_loop(mcu) end -console(ARGS...) \ No newline at end of file +console(ARGS...) diff --git a/examples/mwe.jl b/examples/mwe.jl new file mode 100644 index 0000000..89793bb --- /dev/null +++ b/examples/mwe.jl @@ -0,0 +1,31 @@ +"""Minimal working example. +""" + +using LibSerialPort + +function main(args) + if length(args) != 2 + println("Usage: $(basename(@__FILE__)) port baudrate") + println("Available ports:") + list_ports() + return + end + portname, baudrate = args + + baudrate = parse(Int, baudrate) + + LibSerialPort.open(portname, baudrate) do sp + sleep(2) + + if bytesavailable(sp) > 0 + println(String(read(sp))) + end + + write(sp, "hello\n") + sleep(0.1) + println(readline(sp)) + end + +end + +main(ARGS) diff --git a/examples/serial_example/serial_example.ino b/examples/serial_example/serial_example.ino index 72aebd2..b7b161b 100644 --- a/examples/serial_example/serial_example.ino +++ b/examples/serial_example/serial_example.ino @@ -1,21 +1,13 @@ -int incomingByte = 0; // for incoming serial data -String cmd = ""; // Usage of Arduino's String class is OK for an example -unsigned long writeInterval = 50; -unsigned long timeMarker = millis(); +// Serial loopback example with a few testing options for LibSerialPort.jl -void handleByte(byte b) -{ - if (b == '\r' || b == '\n') - { - Serial.print("Received "); - Serial.println(cmd); - cmd = ""; - } - else - { - cmd += char(b); - } -} +// Use this to introduce latency in the response. +unsigned long responseDelay = 0; + +// Use these to generate additional traffic (timestamped ADC values). +bool write_adc = false; +unsigned long writeInterval = 50; // ms + +unsigned long timeMarker = millis(); void setup() { @@ -26,14 +18,14 @@ void loop() { if (Serial.available() > 0) { - incomingByte = Serial.read(); - handleByte(incomingByte); + delay(responseDelay); + Serial.write(Serial.read()); } - if (millis() - timeMarker > writeInterval) + if (write_adc && millis() - timeMarker > writeInterval) { timeMarker = millis(); Serial.print(timeMarker); Serial.print(" "); Serial.println(analogRead(A0)); } -} \ No newline at end of file +} diff --git a/src/LibSerialPort.jl b/src/LibSerialPort.jl index 83a9d99..3e30596 100644 --- a/src/LibSerialPort.jl +++ b/src/LibSerialPort.jl @@ -26,60 +26,48 @@ export SP_ERR_FAIL, SP_ERR_MEM, SP_ERR_SUPP, - SP_MODE_READ, SP_MODE_WRITE, SP_MODE_READ_WRITE, - SP_EVENT_RX_READY, SP_EVENT_TX_READY, SP_EVENT_ERROR, - SP_BUF_INPUT, SP_BUF_OUTPUT, SP_BUF_BOTH, - SP_PARITY_INVALID, SP_PARITY_NONE, SP_PARITY_ODD, SP_PARITY_EVEN, SP_PARITY_MARK, SP_PARITY_SPACE, - SP_RTS_INVALID, SP_RTS_OFF, SP_RTS_ON, SP_RTS_FLOW_CONTROL, - SP_CTS_INVALID, SP_CTS_IGNORE, SP_CTS_FLOW_CONTROL, - SP_DTR_INVALID, SP_DTR_OFF, SP_DTR_ON, SP_DTR_FLOW_CONTROL, - SP_DSR_INVALID, SP_DSR_IGNORE, SP_DSR_FLOW_CONTROL, - SP_XONXOFF_INVALID, SP_XONXOFF_DISABLED, SP_XONXOFF_IN, SP_XONXOFF_OUT, SP_XONXOFF_INOUT, - SP_FLOWCONTROL_NONE, SP_FLOWCONTROL_XONXOFF, SP_FLOWCONTROL_RTSCTS, SP_FLOWCONTROL_DTRDSR, - SP_SIG_CTS, SP_SIG_DSR, SP_SIG_DCD, SP_SIG_RI, - SP_TRANSPORT_NATIVE, SP_TRANSPORT_USB, SP_TRANSPORT_BLUETOOTH, @@ -168,16 +156,17 @@ export # Functions from high-level API list_ports, get_port_list, + get_port_settings, print_port_metadata, print_port_settings, set_speed, set_frame, set_flow_control, seteof, - reseteof + reseteof, + nonblocking_read include("wrap.jl") include("high-level-api.jl") -include("utils.jl") end # LibSerialPort diff --git a/src/high-level-api.jl b/src/high-level-api.jl index e8a2982..370a0bd 100644 --- a/src/high-level-api.jl +++ b/src/high-level-api.jl @@ -1,23 +1,27 @@ -import Base: open, close, write, unsafe_write, flush, - read, unsafe_read, readbytes!, readuntil, bytesavailable, eof +import Base: isopen, open, close, write, unsafe_write, flush, + read, unsafe_read, bytesavailable, eof + mutable struct SerialPort <: IO ref::Port - eof::Bool - open::Bool - function SerialPort(ref, eof, open) - sp = new(ref, eof, open) + is_eof::Bool + is_open::Bool + read_timeout_ms::Int # 0 to wait indefinitely, per sigrok libserialport interface + function SerialPort(ref, is_eof, is_open, read_timeout_ms) + sp = new(ref, is_eof, is_open, read_timeout_ms) finalizer(destroy!, sp) return sp end end + """ `sp = SerialPort(portname::AbstractString)` Constructor for the `SerialPort` object. """ -SerialPort(portname::AbstractString) = SerialPort(sp_get_port_by_name(portname), false, false) +SerialPort(portname::AbstractString) = SerialPort(sp_get_port_by_name(portname), false, false, 0) + """ `destroy!(sp::SerialPort)` @@ -29,6 +33,23 @@ function destroy!(sp::SerialPort) sp_free_port(sp.ref) end + +""" +`isopen(sp::SerialPort) -> Bool` + +Determine whether a SerialPort object is open. +""" +isopen(sp::SerialPort) = sp.is_open + + +""" +`eof(sp::SerialPort) -> Bool` + +Return EOF state (`true` or `false`)`. +""" +eof(sp::SerialPort) = sp.is_eof + + """ `set_speed(sp::SerialPort,bps::Integer)` @@ -40,6 +61,7 @@ function set_speed(sp::SerialPort, bps::Integer) return nothing end + """ `set_frame(sp::SerialPort [, ndatabits::Integer, parity::SPParity, nstopbits::Integer])` @@ -66,6 +88,7 @@ function set_frame(sp::SerialPort; return nothing end + """ `set_flow_control(sp::SerialPort [,rts::SPrts, cts::SPcts, dtr::SPdtr, dst::SPdsr, xonxoff::SPXonXoff])` @@ -105,6 +128,7 @@ function set_flow_control(sp::SerialPort; return nothing end + """ `listports([nports_guess::Integer])` @@ -127,6 +151,7 @@ function list_ports(;nports_guess::Integer=64) return nothing end + """ `get_port_list([nports_guess::Integer])` @@ -145,6 +170,7 @@ function get_port_list(;nports_guess::Integer=64) return port_list end + """ `print_port_metadata(sp::SerialPort [,show_config::Bool]) @@ -187,33 +213,64 @@ function print_port_metadata(port::LibSerialPort.Port; show_config::Bool=true) return nothing end + +function get_port_settings(config::LibSerialPort.Config) + return Dict( + "baudrate" => sp_get_config_baudrate(config), + "bits" => sp_get_config_bits(config), + "parity" => sp_get_config_parity(config), + "stopbits" => sp_get_config_stopbits(config), + "RTS" => sp_get_config_rts(config), + "CTS" => sp_get_config_cts(config), + "DTR" => sp_get_config_dtr(config), + "DSR" => sp_get_config_dsr(config), + "XonXoff" => sp_get_config_xon_xoff(config), + ) +end + +function get_port_settings(port::LibSerialPort.Port) + config = sp_get_config(port) + settings = get_port_settings(config) + sp_free_config(config) + return settings +end + +""" +`get_port_settings(sp::SerialPort)` + +Return port settings for `sp` as a dictionary. +""" +get_port_settings(sp::SerialPort) = get_port_settings(sp.ref) + + """ `print_port_settings(sp::SerialPort)` Print port settings for `sp`. """ -print_port_settings(sp::SerialPort) = print_port_settings(sp.ref) +function print_port_settings(sp::SerialPort) + print_port_settings(sp.ref) + println("Read timeout (ms, forever if 0): ", sp.read_timeout_ms) + return +end function print_port_settings(port::LibSerialPort.Port) println("Configuration for ", sp_get_port_name(port), ":") config = sp_get_config(port) print_port_settings(config) sp_free_config(config) + return end function print_port_settings(config::LibSerialPort.Config) - println("\tbaudrate\t", sp_get_config_baudrate(config)) - println("\tbits\t", sp_get_config_bits(config)) - println("\tparity\t", sp_get_config_parity(config)) - println("\tstopbits\t", sp_get_config_stopbits(config)) - println("\tRTS\t", sp_get_config_rts(config)) - println("\tCTS\t", sp_get_config_cts(config)) - println("\tDTR\t", sp_get_config_dtr(config)) - println("\tDSR\t", sp_get_config_dsr(config)) - println("\tXonXoff\t", sp_get_config_xon_xoff(config)) - println("") + s = get_port_settings(config) + for (k, v) in s + println("\t$k\t", v) + end + return end + """ `open(sp::SerialPort [, mode::SPMode])` @@ -222,12 +279,13 @@ Open the serial port `sp`. `mode` can take the values: `SP_MODE_READ`, `SP_MODE_WRITE`, and `SP_MODE_READ_WRITE` """ -function Base.open(sp::SerialPort; mode::SPMode=SP_MODE_READ_WRITE) +function open(sp::SerialPort; mode::SPMode=SP_MODE_READ_WRITE) sp_open(sp.ref, mode) - sp.open = true + sp.is_open = true return sp end + """ `open(portname::AbstractString,baudrate::Integer [,mode::SPMode, ndatabits::Integer,parity::SPParity,nstopbits::Integer])` @@ -236,42 +294,30 @@ construct, configure and open a `SerialPort` object. For details on posssible settings see `?set_flow_control` and `?set_frame`. """ -function Base.open(portname::AbstractString, - bps::Integer; - mode::SPMode=SP_MODE_READ_WRITE, - ndatabits::Integer=8, - parity::SPParity=SP_PARITY_NONE, - nstopbits::Integer=1) - sp = SerialPort(sp_get_port_by_name(portname), false, true) +function open(portname::AbstractString, + bps::Integer; + mode::SPMode=SP_MODE_READ_WRITE, + ndatabits::Integer=8, + parity::SPParity=SP_PARITY_NONE, + nstopbits::Integer=1) + sp = SerialPort(portname) sp_open(sp.ref, mode) + sp.is_open = true set_speed(sp, bps) set_frame(sp, ndatabits=ndatabits, parity=parity, nstopbits=nstopbits) return sp end -""" -`open_serial_port(port_address::AbstractString, baudrate::Integer)` - -Create and configure a SerialPort object with the standard 8N1 settings -and specified `baudrate`. Example: `open_serial_port("/dev/ttyACM0", 115200)` -""" -function open_serial_port(port_address::AbstractString, baudrate::Integer) - sp = SerialPort(port_address) - open(sp) - set_speed(sp, baudrate) - set_frame(sp, ndatabits=8, parity=SP_PARITY_NONE, nstopbits=1) - return sp -end """ close(sp::SerialPort) Close the serial port `sp`. """ -function Base.close(sp::SerialPort) - if sp.open +function close(sp::SerialPort) + if isopen(sp) sp_close(sp.ref) - sp.open = false + sp.is_open = false end return sp end @@ -284,26 +330,38 @@ sp_output_waiting(sp::SerialPort) = sp_output_waiting(sp.ref) sp_flush(sp::SerialPort, args...) = sp_flush(sp.ref, args...) sp_drain(sp::SerialPort) = sp_drain(sp.ref) -# We define here only the basic methods for writing bytes. -# All other write() methods for writing the canonical binary +# We define here only the basic methods for reading and writing +# bytes. All other methods for reading/writing the canonical binary # representation of any type, and print() methods for writing # its text representation, are inherited from the IO supertype # (see julia/base/io.jl), i.e. work just like for files. +function read(sp::SerialPort, ::Type{UInt8}) + byte_ref = Ref{UInt8}(0) + nbytes = sp_blocking_read(sp.ref, byte_ref, 1, sp.read_timeout_ms) + + # TODO: how to handle timeouts? + # if nbytes == 0 + # println("read timeout") + # end + return byte_ref.x +end + + +function unsafe_read(sp::SerialPort, p::Ptr{UInt8}, nb::UInt) + sp_blocking_read(sp.ref, p, nb, sp.read_timeout_ms) +end + + function write(sp::SerialPort, b::UInt8) - Int(sp_blocking_write(sp.ref, Ref(b))) + sp_blocking_write(sp.ref, Ref(b)) end + function unsafe_write(sp::SerialPort, p::Ptr{UInt8}, nb::UInt) - Int(sp_blocking_write(sp.ref, p, nb)) + sp_blocking_write(sp.ref, p, nb) end -""" -`eof(sp::SerialPort)` - -Return EOF state (`true` or `false`) of `sp`. -""" -Base.eof(sp::SerialPort) = sp.eof """ `seteof(sp::SerialPort, state::Bool)` @@ -311,10 +369,11 @@ Base.eof(sp::SerialPort) = sp.eof Set EOF of `sp` to `state` """ function seteof(sp::SerialPort, state::Bool) - sp.eof = state + sp.is_eof = state return nothing end + """ `reseteof(sp::SerialPort, state::Bool)` @@ -322,89 +381,33 @@ Reset EOF of `sp` to `false` """ reseteof(sp::SerialPort) = seteof(sp, false) -""" -`read(sp::SerialPort, T::Type{UInt8})` -`read(sp::SerialPort, T::Type{Char})` -Read a single Byte from the specified port and return it represented as `T`. -`T` might be either `Char or `UInt8`. if no Byte is availible in the port -buffer return zero. -""" -function Base.read(sp::SerialPort, readType::Type{Char}) - nbytes_read, bytes = sp_nonblocking_read(sp.ref, 1) - return (nbytes_read == 1) ? convert(readType,bytes[1]) : readType(0) -end +# Julia's Base module defines `read(s::IO, nb::Integer = typemax(Int))`. +# Override the default `nb` to a more useful value for this context. +read(sp::SerialPort) = read(sp, bytesavailable(sp)) -function Base.read(sp::SerialPort, readType::Type{UInt8}) - nbytes_read, bytes = sp_nonblocking_read(sp.ref, 1) - return (nbytes_read == 1) ? convert(readType,bytes[1]) : readType(0) -end """ -`readuntil(sp::SerialPort,delim::Union{Char,AbstractString,Vector{Char}},timeout_ms::Integer)` +`nonblocking_read(sp::SerialPort)` -Read until the specified delimiting byte (e.g. `'\\n'`) is encountered, or until -timeout_ms has elapsed, whichever comes first. +Read everything from the specified serial ports `sp` input buffer, one byte at +a time, until it is empty. Returns a `String`. """ -function Base.readuntil(sp::SerialPort, delim::Char, timeout_ms::Real) - return readuntil(sp,[delim],timeout_ms) -end - - -function Base.readuntil(sp::SerialPort, delim::AbstractString, timeout_ms::Real) - return readuntil(sp,convert(Vector{Char},delim),timeout_ms) -end - -function Base.readuntil(sp::SerialPort, delim::Vector{Char}, timeout_ms::Real) - start_time = time_ns() - out = IOBuffer() - lastchars = Char[0 for i=1:length(delim)] - while !eof(sp) - if (time_ns() - start_time)/1e6 > timeout_ms - break - end - if bytesavailable(sp) > 0 - c = read(sp, Char) - write(out, c) - lastchars = circshift(lastchars,-1) - lastchars[end] = c - if lastchars == delim - break - end - end +function nonblocking_read(sp::SerialPort) + result = UInt8[] + byte_ref = Ref{UInt8}(0) + while bytesavailable(sp) > 0 + sp_nonblocking_read(sp.ref, byte_ref, 1) + push!(result, byte_ref.x) end - return String(take!(out)) + return result end -""" -`Base.bytesavailable(sp::SerialPort)` - -Gets the number of bytes waiting in the input buffer. -""" -Base.bytesavailable(sp::SerialPort) = Int(sp_input_waiting(sp.ref)) """ -`readbytes!(sp::SerialPort,nbytes::Integer)` - -Read `nbytes` from the specified serial port `sp`, without blocking. Returns -a `UInt8` `Array`. -""" -function Base.readbytes!(sp::SerialPort, nbytes::Integer) - nbytes_read, bytes = sp_nonblocking_read(sp.ref, nbytes) - return bytes -end +`bytesavailable(sp::SerialPort)` +Gets the number of bytes waiting in the input buffer. """ -`read(sp::SerialPort, ::Type{String})` +bytesavailable(sp::SerialPort) = sp_input_waiting(sp.ref) -Read everything from the specified serial ports `sp` input buffer, one byte at -a time, until it is empty. Returns a `String`. -""" -function Base.read(sp::SerialPort, ::Type{String}) - result = Char[] - while Int(bytesavailable(sp)) > 0 - byte = readbytes!(sp, 1)[1] - push!(result, byte) - end - return String(join(result)) -end diff --git a/src/utils.jl b/src/utils.jl deleted file mode 100644 index 13c0044..0000000 --- a/src/utils.jl +++ /dev/null @@ -1,65 +0,0 @@ -import Base: readline, readuntil -export readline, readuntil - -#== -Timeout versions of the base functions -==# - -""" - readline(s::IO, timeout::T; keep::Bool=false) where {T<:Real} - -Like Base.readline, except times-out after `timeout` seconds. -""" -function readline(s::IO, timeout::T; keep::Bool=false) where {T<:Real} - line = readuntil(s, 0x0a, timeout, keep=true) - i = length(line) - if keep || i == 0 || line[i] != 0x0a - return String(line) - elseif i < 2 || line[i-1] != 0x0d - return String(resize!(line,i-1)) - else - return String(resize!(line,i-2)) - end -end - -""" - readuntil(s::IO, delim::AbstractChar, timeout::T; keep::Bool=false) where {T<:Real} - readuntil(s::IO, delim::T, timeout::U; keep::Bool=false) where {T, U<:Real} - -Like Base.readuntil, except times-out after `timeout` seconds. -""" -function readuntil(s::IO, delim::AbstractChar, timeout::T; keep::Bool=false) where {T<:Real} - if delim ≤ '\x7f' - return Base.readuntil_string(s, delim % UInt8, keep) - end - out = IOBuffer() - t = Timer(timeout) - while !eof(s) && isopen(t) - bytesavailable(s) == 0 && continue - c = read(s, Char) - if c == delim - keep && write(out, c) - break - elseif c != 0x00 - write(out, c) - end - yield() - end - return String(take!(out)) -end -function readuntil(s::IO, delim::T, timeout::U; keep::Bool=false) where {T, U<:Real} - out = (T === UInt8 ? Base.StringVector(0) : Vector{T}()) - t = Timer(timeout) - while !eof(s) && isopen(t) - bytesavailable(s) == 0 && continue - c = read(s, T) - if c == delim - keep && push!(out, c) - break - elseif c != 0x00 - push!(out, c) - end - yield() - end - return out -end diff --git a/src/wrap.jl b/src/wrap.jl index 659f069..e1d8899 100644 --- a/src/wrap.jl +++ b/src/wrap.jl @@ -1,3 +1,5 @@ + +# Julia types for the sp_port, sp_port_config, and sp_event_set structs struct SPPort end struct SPConfig end struct SPEventSet end @@ -113,8 +115,8 @@ SPSignal(x::SPSignal) = SPSignal(Int(x)) SPTransport(x::SPTransport) = SPTransport(Int(x)) -function handle_error(ret::SPReturn) - ret >= SP_OK && return ret +function check(ret::SPReturn) + ret >= SP_OK && return Int(ret) msg = "libserialport returned $ret - " @@ -139,8 +141,13 @@ end # enum sp_return sp_get_port_by_name(const char *portname, struct sp_port **port_ptr); function sp_get_port_by_name(portname::AbstractString) portp = PortP() - handle_error(ccall((:sp_get_port_by_name, libserialport), SPReturn, - (Ptr{UInt8}, PortP), portname, portp)) + check(ccall( + (:sp_get_port_by_name, libserialport), + SPReturn, + (Ptr{UInt8}, PortP), + portname, + portp, + )) portp[] end @@ -152,16 +159,14 @@ end # enum sp_return sp_list_ports(struct sp_port ***list_ptr); function sp_list_ports() ports = Ref{Ptr{Ptr{SPPort}}}() - handle_error(ccall((:sp_list_ports, libserialport), - SPReturn, (Ref{Ptr{Ptr{SPPort}}},), ports)) + check(ccall((:sp_list_ports, libserialport), SPReturn, (Ref{Ptr{Ptr{SPPort}}},), ports)) return ports[] end # enum sp_return sp_copy_port(const struct sp_port *port, struct sp_port **copy_ptr); function sp_copy_port(port::Port) port_copy = PortP() - handle_error(ccall((:sp_copy_port, libserialport), SPReturn, - (Port, PortP), port, port_copy)) + check(ccall((:sp_copy_port, libserialport), SPReturn, (Port, PortP), port, port_copy)) return port_copy[] end @@ -172,13 +177,12 @@ end # enum sp_return sp_open(struct sp_port *port, enum sp_mode flags); function sp_open(port::Port, mode::SPMode) - handle_error(ccall((:sp_open, libserialport), SPReturn, (Port, SPMode), - port, mode)) + check(ccall((:sp_open, libserialport), SPReturn, (Port, SPMode), port, mode)) end # enum sp_return sp_close(struct sp_port *port); function sp_close(port::Port) - handle_error(ccall((:sp_close, libserialport), SPReturn, (Port,), port)) + check(ccall((:sp_close, libserialport), SPReturn, (Port,), port)) end # char *sp_get_port_name(const struct sp_port *port); @@ -208,14 +212,20 @@ function sp_get_port_usb_bus_address(port::Port) usb_bus = Ref{Cint}() usb_address = Ref{Cint}() - ret = ccall((:sp_get_port_usb_bus_address, libserialport), SPReturn, - (Port, Ref{Cint}, Ref{Cint}), port, usb_bus, usb_address) + ret = ccall( + (:sp_get_port_usb_bus_address, libserialport), + SPReturn, + (Port, Ref{Cint}, Ref{Cint}), + port, + usb_bus, + usb_address, + ) if ret == SP_ERR_SUPP return -1, -1 end - handle_error(ret) + check(ret) return usb_bus[], usb_address[] end @@ -229,55 +239,62 @@ function sp_get_port_usb_vid_pid(port::Port) vid = Ref{Cint}() pid = Ref{Cint}() - ret = ccall((:sp_get_port_usb_vid_pid, libserialport), SPReturn, - (Port, Ref{Cint}, Ref{Cint}), port, vid, pid) + ret = ccall( + (:sp_get_port_usb_vid_pid, libserialport), + SPReturn, + (Port, Ref{Cint}, Ref{Cint}), + port, + vid, + pid, + ) if ret == SP_ERR_SUPP return -1, -1 end - handle_error(ret) + check(ret) return vid[], pid[] end # char *sp_get_port_usb_manufacturer(const struct sp_port *port); function sp_get_port_usb_manufacturer(port::Port) - m = ccall((:sp_get_port_usb_manufacturer, libserialport), - Ptr{UInt8}, (Port,), port) + m = ccall((:sp_get_port_usb_manufacturer, libserialport), Ptr{UInt8}, (Port,), port) manufacturer = (m != C_NULL) ? unsafe_string(m) : "" end # char *sp_get_port_usb_product(const struct sp_port *port); function sp_get_port_usb_product(port::Port) - p = ccall((:sp_get_port_usb_product, libserialport), - Ptr{UInt8}, (Port,), port) + p = ccall((:sp_get_port_usb_product, libserialport), Ptr{UInt8}, (Port,), port) product = (p != C_NULL) ? unsafe_string(p) : "" end # char *sp_get_port_usb_serial(const struct sp_port *port); function sp_get_port_usb_serial(port::Port) - s = ccall((:sp_get_port_usb_serial, libserialport), - Ptr{UInt8}, (Port,), port) + s = ccall((:sp_get_port_usb_serial, libserialport), Ptr{UInt8}, (Port,), port) serial = (s != C_NULL) ? unsafe_string(s) : "" end # char *sp_get_port_bluetooth_address(const struct sp_port *port); function sp_get_port_bluetooth_address(port::Port) - a = ccall((:sp_get_port_bluetooth_address, libserialport), - Ptr{UInt8}, (Port,), port) + a = ccall((:sp_get_port_bluetooth_address, libserialport), Ptr{UInt8}, (Port,), port) address = (a != C_NULL) ? unsafe_string(a) : "" end if Sys.iswindows() - # TODO: on Windows, result should be Ref{HANDLE} + # TODO: on Windows, result should be Ref{HANDLE} sp_get_port_handle(port::Port) = error("Returning port handle not supported on Windows") else # enum sp_return sp_get_port_handle(const struct sp_port *port, void *result_ptr); function sp_get_port_handle(port::Port) # For Linux and OS X result = Ref{Cint}(0) - handle_error(ccall((:sp_get_port_handle, libserialport), SPReturn, - (Port, Ref{Cint}), port, result)) + check(ccall( + (:sp_get_port_handle, libserialport), + SPReturn, + (Port, Ref{Cint}), + port, + result, + )) result[] end end @@ -285,8 +302,7 @@ end # enum sp_return sp_new_config(struct sp_port_config **config_ptr); function sp_new_config() pc = ConfigP() - handle_error(ccall((:sp_new_config, libserialport), SPReturn, (ConfigP,), - pc)) + check(ccall((:sp_new_config, libserialport), SPReturn, (ConfigP,), pc)) pc[] end @@ -297,219 +313,341 @@ end function sp_get_config(port::Port) config = sp_new_config() - handle_error(ccall((:sp_get_config, libserialport), SPReturn, - (Port, Config), port, config)) + check(ccall((:sp_get_config, libserialport), SPReturn, (Port, Config), port, config)) config end # enum sp_return sp_set_config(struct sp_port *port, const struct sp_port_config *config); function sp_set_config(port::Port, config::Config) - handle_error(ccall((:sp_set_config, libserialport), SPReturn, - (Port, Config), port, config)) + check(ccall((:sp_set_config, libserialport), SPReturn, (Port, Config), port, config)) end # enum sp_return sp_set_baudrate(struct sp_port *port, int baudrate); function sp_set_baudrate(port::Port, baudrate::Integer) - handle_error(ccall((:sp_set_baudrate, libserialport), SPReturn, - (Port, Cint), port, Cint(baudrate))) + check(ccall((:sp_set_baudrate, libserialport), SPReturn, (Port, Cint), port, baudrate)) end # enum sp_return sp_get_config_baudrate(const struct sp_port_config *config, int *baudrate_ptr); function sp_get_config_baudrate(config::Config) baudrate = Ref{Cint}() - handle_error(ccall((:sp_get_config_baudrate, libserialport), SPReturn, - (Config, Ref{Cint}), config, baudrate)) + check(ccall( + (:sp_get_config_baudrate, libserialport), + SPReturn, + (Config, Ref{Cint}), + config, + baudrate, + )) baudrate[] end # enum sp_return sp_set_config_baudrate(struct sp_port_config *config, int baudrate); function sp_set_config_baudrate(config::Config, baudrate::Integer) - handle_error(ccall((:sp_set_config_baudrate, libserialport), SPReturn, - (Config, Cint), config, Cint(baudrate))) + check(ccall( + (:sp_set_config_baudrate, libserialport), + SPReturn, + (Config, Cint), + config, + baudrate, + )) end # enum sp_return sp_set_bits(struct sp_port *port, int bits); function sp_set_bits(port::Port, bits::Integer) - @assert 5 <= bits <= 8 - handle_error(ccall((:sp_set_bits, libserialport), SPReturn, - (Port, Cint), port, Cint(bits))) + if !(5 <= bits <= 8) + throw(ArgumentError( + "libserialport allows 5-8 data bits/frame. Received $bits.")) + end + check(ccall((:sp_set_bits, libserialport), SPReturn, (Port, Cint), port, bits)) end # enum sp_return sp_get_config_bits(const struct sp_port_config *config, int *bits_ptr); function sp_get_config_bits(config::Config) bits = Ref{Cint}() - handle_error(ccall((:sp_get_config_bits, libserialport), SPReturn, - (Config, Ref{Cint}), config, bits)) + check(ccall( + (:sp_get_config_bits, libserialport), + SPReturn, + (Config, Ref{Cint}), + config, + bits, + )) bits[] end # enum sp_return sp_set_config_bits(struct sp_port_config *config, int bits); function sp_set_config_bits(config::Config, bits::Integer) - handle_error(ccall((:sp_set_config_bits, libserialport), SPReturn, - (Config, Cint), config, Cint(bits))) + check(ccall( + (:sp_set_config_bits, libserialport), + SPReturn, + (Config, Cint), + config, + bits, + )) end # enum sp_return sp_set_parity(struct sp_port *port, enum sp_parity parity); function sp_set_parity(port::Port, parity::SPParity) - handle_error(ccall((:sp_set_parity, libserialport), SPReturn, - (Port, SPParity), port, parity)) + check(ccall((:sp_set_parity, libserialport), SPReturn, (Port, SPParity), port, parity)) end # enum sp_return sp_get_config_parity(const struct sp_port_config *config, enum sp_parity *parity_ptr); function sp_get_config_parity(config::Config) parity = Ref{SPParity}() - handle_error(ccall((:sp_get_config_parity, libserialport), SPReturn, - (Config, Ref{SPParity}), config, parity)) + check(ccall( + (:sp_get_config_parity, libserialport), + SPReturn, + (Config, Ref{SPParity}), + config, + parity, + )) parity[] end # enum sp_return sp_set_config_parity(struct sp_port_config *config, enum sp_parity parity); function sp_set_config_parity(config::Config, parity::SPParity) - handle_error(ccall((:sp_set_config_parity, libserialport), SPReturn, - (Config, SPParity), config, parity)) + check(ccall( + (:sp_set_config_parity, libserialport), + SPReturn, + (Config, SPParity), + config, + parity, + )) end # enum sp_return sp_set_stopbits(struct sp_port *port, int stopbits); function sp_set_stopbits(port::Port, stopbits::Integer) - handle_error(ccall((:sp_set_stopbits, libserialport), SPReturn, - (Port, Cint), port, Cint(stopbits))) + check(ccall((:sp_set_stopbits, libserialport), SPReturn, (Port, Cint), port, stopbits)) end # enum sp_return sp_get_config_stopbits(const struct sp_port_config *config, int *stopbits_ptr); function sp_get_config_stopbits(config::Config) bits = Ref{Cint}() - handle_error(ccall((:sp_get_config_stopbits, libserialport), SPReturn, - (Config, Ref{Cint}), config, bits)) + check(ccall( + (:sp_get_config_stopbits, libserialport), + SPReturn, + (Config, Ref{Cint}), + config, + bits, + )) bits[] end # enum sp_return sp_set_config_stopbits(struct sp_port_config *config, int stopbits); function sp_set_config_stopbits(config::Config, stopbits::Integer) - handle_error(ccall((:sp_set_config_stopbits, libserialport), SPReturn, - (Config, Cint), config, Cint(stopbits))) + check(ccall( + (:sp_set_config_stopbits, libserialport), + SPReturn, + (Config, Cint), + config, + stopbits, + )) end # enum sp_return sp_set_rts(struct sp_port *port, enum sp_rts rts); function sp_set_rts(port::Port, rts::SPrts) - handle_error(ccall((:sp_set_rts, libserialport), SPReturn, - (Port, SPrts), port, rts)) + check(ccall((:sp_set_rts, libserialport), SPReturn, (Port, SPrts), port, rts)) end # enum sp_return sp_get_config_rts(const struct sp_port_config *config, enum sp_rts *rts_ptr); function sp_get_config_rts(config::Config) rts = Ref{SPrts}() - handle_error(ccall((:sp_get_config_rts, libserialport), SPReturn, - (Config, Ref{SPrts}), config, rts)) + check(ccall( + (:sp_get_config_rts, libserialport), + SPReturn, + (Config, Ref{SPrts}), + config, + rts, + )) rts[] end # enum sp_return sp_set_config_rts(struct sp_port_config *config, enum sp_rts rts); function sp_set_config_rts(config::Config, rts::SPrts) - handle_error(ccall((:sp_set_config_rts, libserialport), SPReturn, - (Config, SPrts), config, SPrts(rts))) + check(ccall( + (:sp_set_config_rts, libserialport), + SPReturn, + (Config, SPrts), + config, + rts, + )) end # enum sp_return sp_set_cts(struct sp_port *port, enum sp_cts cts); function sp_set_cts(port::Port, cts::SPcts) - handle_error(ccall((:sp_set_cts, libserialport), SPReturn, - (Port, SPcts), port, cts)) + check(ccall((:sp_set_cts, libserialport), SPReturn, (Port, SPcts), port, cts)) end # enum sp_return sp_get_config_cts(const struct sp_port_config *config, enum sp_cts *cts_ptr); function sp_get_config_cts(config::Config) cts = Ref{SPcts}() - handle_error(ccall((:sp_get_config_cts, libserialport), SPReturn, - (Config, Ref{SPcts}), config, cts)) + check(ccall( + (:sp_get_config_cts, libserialport), + SPReturn, + (Config, Ref{SPcts}), + config, + cts, + )) cts[] end # enum sp_return sp_set_config_cts(struct sp_port_config *config, enum sp_cts cts); function sp_set_config_cts(config::Config, cts::SPcts) - handle_error(ccall((:sp_set_config_cts, libserialport), SPReturn, - (Config, SPcts), config, SPcts(cts))) + check(ccall( + (:sp_set_config_cts, libserialport), + SPReturn, + (Config, SPcts), + config, + cts, + )) end # enum sp_return sp_set_dtr(struct sp_port *port, enum sp_dtr dtr); function sp_set_dtr(port::Port, dtr::SPdtr) - handle_error(ccall((:sp_set_dtr, libserialport), SPReturn, - (Port, SPdtr), port, dtr)) + check(ccall((:sp_set_dtr, libserialport), SPReturn, (Port, SPdtr), port, dtr)) end # enum sp_return sp_get_config_dtr(const struct sp_port_config *config, enum sp_dtr *dtr_ptr); function sp_get_config_dtr(config::Config) dtr = Ref{SPdtr}() - handle_error(ccall((:sp_get_config_dtr, libserialport), SPReturn, - (Config, Ref{SPdtr}), config, dtr)) + check(ccall( + (:sp_get_config_dtr, libserialport), + SPReturn, + (Config, Ref{SPdtr}), + config, + dtr, + )) dtr[] end # enum sp_return sp_set_config_dtr(struct sp_port_config *config, enum sp_dtr dtr); function sp_set_config_dtr(config::Config, dtr::SPdtr) - handle_error(ccall((:sp_set_config_dtr, libserialport), SPReturn, - (Config, SPdtr), config, SPdtr(dtr))) + check(ccall( + (:sp_set_config_dtr, libserialport), + SPReturn, + (Config, SPdtr), + config, + dtr, + )) end # enum sp_return sp_set_dsr(struct sp_port *port, enum sp_dsr dsr); function sp_set_dsr(port::Port, dsr::SPdsr) - handle_error(ccall((:sp_set_dsr, libserialport), SPReturn, - (Port, SPdsr), port, dsr)) + check(ccall((:sp_set_dsr, libserialport), SPReturn, (Port, SPdsr), port, dsr)) end # enum sp_return sp_get_config_dsr(const struct sp_port_config *config, enum sp_dsr *dsr_ptr); function sp_get_config_dsr(config::Config) dsr = Ref{SPdsr}() - handle_error(ccall((:sp_get_config_dsr, libserialport), SPReturn, - (Config, Ref{SPdsr}), config, dsr)) + check(ccall( + (:sp_get_config_dsr, libserialport), + SPReturn, + (Config, Ref{SPdsr}), + config, + dsr, + )) dsr[] end # enum sp_return sp_set_config_dsr(struct sp_port_config *config, enum sp_dsr dsr); function sp_set_config_dsr(config::Config, dsr::SPdsr) - handle_error(ccall((:sp_set_config_dsr, libserialport), SPReturn, - (Config, SPdsr), config, SPdsr(dsr))) + check(ccall( + (:sp_set_config_dsr, libserialport), + SPReturn, + (Config, SPdsr), + config, + dsr, + )) end # enum sp_return sp_set_xon_xoff(struct sp_port *port, enum sp_xonxoff xon_xoff); function sp_set_xon_xoff(port::Port, xon_xoff::SPXonXoff) - handle_error(ccall((:sp_set_xon_xoff, libserialport), SPReturn, - (Port, SPXonXoff), port, xon_xoff)) + check(ccall( + (:sp_set_xon_xoff, libserialport), + SPReturn, + (Port, SPXonXoff), + port, + xon_xoff, + )) end # enum sp_return sp_get_config_xon_xoff(const struct sp_port_config *config, enum sp_xonxoff *xon_xoff_ptr); function sp_get_config_xon_xoff(config::Config) xon_xoff = Ref{SPXonXoff}() - handle_error(ccall((:sp_get_config_xon_xoff, libserialport), SPReturn, - (Config, Ref{SPXonXoff}), config, xon_xoff)) + check(ccall( + (:sp_get_config_xon_xoff, libserialport), + SPReturn, + (Config, Ref{SPXonXoff}), + config, + xon_xoff, + )) xon_xoff[] end # enum sp_return sp_set_config_xon_xoff(struct sp_port_config *config, enum sp_xonxoff xon_xoff); function sp_set_config_xon_xoff(config::Config, xon_xoff::SPXonXoff) - handle_error(ccall((:sp_set_config_xon_xoff, libserialport), SPReturn, - (Config, SPXonXoff), config, SPXonXoff(xon_xoff))) + check(ccall( + (:sp_set_config_xon_xoff, libserialport), + SPReturn, + (Config, SPXonXoff), + config, + SPXonXoff(xon_xoff), + )) end # enum sp_return sp_set_config_flowcontrol(struct sp_port_config *config, enum sp_flowcontrol flowcontrol); function sp_set_config_flowcontrol(config::Config, flowcontrol::SPFlowControl) - handle_error(ccall((:sp_set_config_flowcontrol, libserialport), SPReturn, - (Config, SPFlowControl), config, flowcontrol)) + check(ccall( + (:sp_set_config_flowcontrol, libserialport), + SPReturn, + (Config, SPFlowControl), + config, + flowcontrol, + )) end # enum sp_return sp_set_flowcontrol(struct sp_port *port, enum sp_flowcontrol flowcontrol); function sp_set_flowcontrol(port::Port, flowcontrol::SPFlowControl) - handle_error(ccall((:sp_set_flowcontrol, libserialport), SPReturn, - (Port, SPFlowControl), port, flowcontrol)) + check(ccall( + (:sp_set_flowcontrol, libserialport), + SPReturn, + (Port, SPFlowControl), + port, + flowcontrol, + )) end # enum sp_return sp_blocking_read(struct sp_port *port, void *buf, size_t count, unsigned int timeout_ms); +function sp_blocking_read( + port::Port, + buffer::Union{Ref{T},Ptr{T}}, + nbytes::Integer, + timeout_ms::Integer, +) where {T} + # If the read succeeds, the return value is the number of bytes read. + ret = check(ccall( + (:sp_blocking_read, libserialport), + SPReturn, + (Port, Ptr{UInt8}, Csize_t, Cuint), + port, + buffer, + sizeof(T) * nbytes, + timeout_ms, + )) + return ret +end function sp_blocking_read(port::Port, nbytes::Integer, timeout_ms::Integer) buffer = zeros(UInt8, nbytes) # If the read succeeds, the return value is the number of bytes read. - ret = handle_error(ccall((:sp_blocking_read, libserialport), SPReturn, - (Port, Ptr{UInt8}, Csize_t, Cuint), - port, buffer, Csize_t(nbytes), Cuint(timeout_ms))) - return Int(ret), buffer + ret = check(ccall( + (:sp_blocking_read, libserialport), + SPReturn, + (Port, Ptr{UInt8}, Csize_t, Cuint), + port, + buffer, + nbytes, + timeout_ms, + )) + return ret, buffer end # enum sp_return sp_blocking_read_next(struct sp_port *port, void *buf, size_t count, unsigned int timeout_ms); @@ -517,28 +655,59 @@ function sp_blocking_read_next(port::Port, nbytes::Integer, timeout_ms::Integer) buffer = zeros(UInt8, nbytes) # If the read succeeds, the return value is the number of bytes read. - ret = handle_error(ccall((:sp_blocking_read_next, libserialport), SPReturn, - (Port, Ptr{UInt8}, Csize_t, Cuint), - port, buffer, Csize_t(nbytes), Cuint(timeout_ms))) - return Int(ret), buffer + ret = check(ccall( + (:sp_blocking_read_next, libserialport), + SPReturn, + (Port, Ptr{UInt8}, Csize_t, Cuint), + port, + buffer, + nbytes, + timeout_ms, + )) + return ret, buffer end # enum sp_return sp_nonblocking_read(struct sp_port *port, void *buf, size_t count); +function sp_nonblocking_read( + port::Port, + buffer::Union{Ref{T},Ptr{T}}, + nbytes::Integer, +) where {T} + check(ccall( + (:sp_nonblocking_read, libserialport), + SPReturn, + (Port, Ptr{UInt8}, Csize_t), + port, + buffer, + sizeof(T) * nbytes, + )) +end function sp_nonblocking_read(port::Port, nbytes::Integer) buffer = zeros(UInt8, nbytes) # If the read succeeds, the return value is the number of bytes read. - ret = handle_error(ccall((:sp_nonblocking_read, libserialport), SPReturn, - (Port, Ptr{UInt8}, Csize_t), - port, buffer, Csize_t(nbytes))) - return Int(ret), buffer + ret = check(ccall( + (:sp_nonblocking_read, libserialport), + SPReturn, + (Port, Ptr{UInt8}, Csize_t), + port, + buffer, + Csize_t(nbytes), + )) + return ret, buffer end # enum sp_return sp_blocking_write(struct sp_port *port, const void *buf, size_t count, unsigned int timeout_ms); function sp_blocking_write(port::Port, buffer::Array{UInt8}, timeout_ms::Integer) - handle_error(ccall((:sp_blocking_write, libserialport), SPReturn, - (Port, Ptr{UInt8}, Csize_t, Cuint), - port, pointer(buffer), length(buffer), timeout_ms)) + check(ccall( + (:sp_blocking_write, libserialport), + SPReturn, + (Port, Ptr{UInt8}, Csize_t, Cuint), + port, + pointer(buffer), + length(buffer), + timeout_ms, + )) end """ @@ -565,24 +734,49 @@ requested number of bytes or raise an `ErrorException`. In the event of an error there is no way to determine how many bytes were sent before the error occured. """ -function sp_blocking_write(port::Port, buffer::Union{Ref{T},Ptr{T}}, n::Integer = 1, - timeout_ms::Integer = 0) where T - handle_error(ccall((:sp_blocking_write, libserialport), SPReturn, - (Port, Ptr{UInt8}, Csize_t, Cuint), - port, buffer, sizeof(T) * n, timeout_ms)) +function sp_blocking_write( + port::Port, + buffer::Union{Ref{T},Ptr{T}}, + n::Integer = 1, + timeout_ms::Integer = 0, +) where {T} + # If successful, the return value is the number of bytes written. + ret = check(ccall( + (:sp_blocking_write, libserialport), + SPReturn, + (Port, Ptr{UInt8}, Csize_t, Cuint), + port, + buffer, + sizeof(T) * n, + timeout_ms, + )) + return ret end function sp_blocking_write(port::Port, buffer::String, timeout_ms::Integer) - handle_error(ccall((:sp_blocking_write, libserialport), SPReturn, - (Port, Ptr{UInt8}, Csize_t, Cuint), - port, buffer, length(buffer), timeout_ms)) + # If successful, the return value is the number of bytes written. + ret = check(ccall( + (:sp_blocking_write, libserialport), + SPReturn, + (Port, Ptr{UInt8}, Csize_t, Cuint), + port, + buffer, + length(buffer), + timeout_ms, + )) + return ret end # enum sp_return sp_nonblocking_write(struct sp_port *port, const void *buf, size_t count); function sp_nonblocking_write(port::Port, buffer::Array{UInt8}) - handle_error(ccall((:sp_nonblocking_write, libserialport), SPReturn, - (Port, Ptr{UInt8}, Csize_t), - port, pointer(buffer), length(buffer))) + check(ccall( + (:sp_nonblocking_write, libserialport), + SPReturn, + (Port, Ptr{UInt8}, Csize_t), + port, + pointer(buffer), + length(buffer), + )) end """ @@ -603,16 +797,30 @@ Returns the number of bytes written on success, or raises an `ErrorException`. The number of bytes returned may be any number from zero to the maximum that was requested. """ -function sp_nonblocking_write(port::Port, buffer::Union{Ptr{T},Ref{T}}, n::Integer = 1) where T - handle_error(ccall((:sp_nonblocking_write, libserialport), SPReturn, - (Port, Ptr{UInt8}, Csize_t), - port, buffer, n * sizeof(T))) +function sp_nonblocking_write( + port::Port, + buffer::Union{Ptr{T},Ref{T}}, + n::Integer = 1, +) where {T} + check(ccall( + (:sp_nonblocking_write, libserialport), + SPReturn, + (Port, Ptr{UInt8}, Csize_t), + port, + buffer, + n * sizeof(T), + )) end function sp_nonblocking_write(port::Port, buffer::String) - handle_error(ccall((:sp_nonblocking_write, libserialport), SPReturn, - (Port, Ptr{UInt8}, Csize_t), - port, buffer, sizeof(buffer))) + check(ccall( + (:sp_nonblocking_write, libserialport), + SPReturn, + (Port, Ptr{UInt8}, Csize_t), + port, + buffer, + sizeof(buffer), + )) end # enum sp_return sp_input_waiting(struct sp_port *port); @@ -620,8 +828,7 @@ end Returns the number of bytes in the input buffer or an error code. """ function sp_input_waiting(port::Port) - handle_error(ccall((:sp_input_waiting, libserialport), SPReturn, (Port,), - port)) + check(ccall((:sp_input_waiting, libserialport), SPReturn, (Port,), port)) end # enum sp_return sp_output_waiting(struct sp_port *port); @@ -629,8 +836,7 @@ end Returns the number of bytes in the output buffer or an error code. """ function sp_output_waiting(port::Port) - handle_error(ccall((:sp_output_waiting, libserialport), SPReturn, (Port,), - port)) + check(ccall((:sp_output_waiting, libserialport), SPReturn, (Port,), port)) end # enum sp_return sp_flush(struct sp_port *port, enum sp_buffer buffers); @@ -645,8 +851,7 @@ Supported values for `buffers`: `SP_BUF_INPUT`, `SP_BUF_OUTPUT`, `SP_BUF_BOTH` Returns SP_OK upon success or raises an `ErrorException` otherwise. """ function sp_flush(port::Port, buffers::SPBuffer) - handle_error(ccall((:sp_flush, libserialport), SPReturn, - (Port, SPBuffer), port, buffers)) + check(ccall((:sp_flush, libserialport), SPReturn, (Port, SPBuffer), port, buffers)) end # enum sp_return sp_drain(struct sp_port *port); @@ -657,51 +862,74 @@ end Wait for buffered data to be transmitted. """ function sp_drain(port::Port) - handle_error(ccall((:sp_drain, libserialport), SPReturn, (Port,), port)) + check(ccall((:sp_drain, libserialport), SPReturn, (Port,), port)) end # enum sp_return sp_new_event_set(struct sp_event_set **result_ptr); function sp_new_event_set() event_set = Ref{Ptr{SPEventSet}}() - handle_error(ccall((:sp_new_event_set, libserialport), SPReturn, - (Ref{Ptr{SPEventSet}},), event_set)) + check(ccall( + (:sp_new_event_set, libserialport), + SPReturn, + (Ref{Ptr{SPEventSet}},), + event_set, + )) event_set[] end # enum sp_return sp_add_port_events(struct sp_event_set *event_set, const struct sp_port *port, enum sp_event mask); function sp_add_port_events(event_set::Ref{SPEventSet}, port::Port, mask::SPEvent) - handle_error(ccall((:sp_add_port_events, libserialport), SPReturn, - (Ref{SPEventSet}, Port, SPEvent), event_set, port, mask)) + check(ccall( + (:sp_add_port_events, libserialport), + SPReturn, + (Ref{SPEventSet}, Port, SPEvent), + event_set, + port, + mask, + )) end # enum sp_return sp_wait(struct sp_event_set *event_set, unsigned int timeout_ms); function sp_wait(event_set::Ref{SPEventSet}, timeout_ms::Integer) - handle_error(ccall((:sp_wait, libserialport), SPReturn, - (Ref{SPEventSet}, Cuint), event_set, timeout_ms)) + check(ccall( + (:sp_wait, libserialport), + SPReturn, + (Ref{SPEventSet}, Cuint), + event_set, + timeout_ms, + )) end # void sp_free_event_set(struct sp_event_set *event_set); function sp_free_event_set(event_set::Ref{SPEventSet}) - handle_error(ccall((:sp_free_event_set, libserialport), SPReturn, - (Ref{SPEventSet},), event_set)) + check(ccall( + (:sp_free_event_set, libserialport), + SPReturn, + (Ref{SPEventSet},), + event_set, + )) end # enum sp_return sp_get_signals(struct sp_port *port, enum sp_signal *signal_mask); function sp_get_signals(port::Port, signal_mask::Ref{SPSignal}) - handle_error(ccall((:sp_get_signals, libserialport), SPReturn, - (Port, Ref{SPSignal}), port, signal_mask)) + check(ccall( + (:sp_get_signals, libserialport), + SPReturn, + (Port, Ref{SPSignal}), + port, + signal_mask, + )) signal_mask[] end # enum sp_return sp_start_break(struct sp_port *port); function sp_start_break(port::Port) - handle_error(ccall((:sp_start_break, libserialport), SPReturn, (Port,), - port)) + check(ccall((:sp_start_break, libserialport), SPReturn, (Port,), port)) end # enum sp_return sp_end_break(struct sp_port *port); function sp_end_break(port::Port) - handle_error(ccall((:sp_end_break, libserialport), SPReturn, (Port,), port)) + check(ccall((:sp_end_break, libserialport), SPReturn, (Port,), port)) end # int sp_last_error_code(void); diff --git a/test/runtests.jl b/test/runtests.jl index f50b9ea..0aa8ce6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,11 +1,16 @@ #= -Run test locally using -$ julia test/runtests.jl /dev/ttyXYZ +These tests require a serial device to echo bytes written from the host computer. -/dev/ttyXYZ can be: -/dev/ttyS0 (for Travis) -/dev/ttyS4 -/dev/ttyUSB0 +For example, these hardware configurations should work: + - A standalone USB-to-UART adapter (e.g. FTDI FT232) in loopback mode (TX and RX pins jumped). + - A USB-to-serial equipped, arduino-compatible microcontroller running examples/serial_example.ino. + - The USB-to-serial chip on some microcontroller boards can be used directly, bypassing the micro. + For example, an Arduino UNO R3 can be connected via USB with RESET grounded and TX wired to RX. + +Run the tests locally using +$ julia test/runtests.jl + +LibSerialPort.list_ports() may help with finding the correct port address. =# using LibSerialPort @@ -17,7 +22,7 @@ if haskey(ENV, "CI") end else if length(ARGS) == 0 - port = "/dev/ttyS0" # /dev/ttyS4 /dev/ttyUSB0 + port = "/dev/ttyS0" else port = ARGS[1] end @@ -37,21 +42,5 @@ else @test test_high_level_api(port, baudrate) == nothing end - @testset "Reading with timeouts" begin - LibSerialPort.open(port, 115200) do s - #Tests assume serial port being tested won't output anything after being flushed - #TODO: Find a better way to test this - sp_flush(s, SP_BUF_BOTH) - @test readline(s, 1.0) == "" #readline with a 1 second timeout - @test readuntil(s, 'a', 1.0) == "" #readuntil 'a' with a 1 second timeout - end - end - - # console.jl runs forever, thus isn't amenable to unit testing - # @testset "Examples" begin - # include("../examples/console.jl") - # @test console() == nothing - # @test console(port, baudrate) == nothing - # end end end diff --git a/test/test-high-level-api.jl b/test/test-high-level-api.jl index 6dad946..5b5e1d8 100644 --- a/test/test-high-level-api.jl +++ b/test/test-high-level-api.jl @@ -1,72 +1,84 @@ using LibSerialPort +using Test +function test_blocking_serial_loopback(sp::SerialPort) -function test_nonblocking_serial_loopback(sp::SerialPort) + # This "tests" `read` indirectly, but has an important side effect of flushing + # the input buffer, putting the test into a more well-defined state. + println("\nRead all data currently waiting in the serial input buffer...") + if bytesavailable(sp) > 0 + println(String(read(sp))) + end - println("\n[TEST] Read any incoming data for ~1 second...") - for i = 1:1000 - print(read(sp, String)) - sleep(0.001) + for i = 1:10 + msg = "test message $i\n" + write(sp, msg) + print("Wrote ", msg) + sleep(0.1) + line = readline(sp) + println(line) + @test startswith(line, "test message") end - println() - sp_flush(sp, SP_BUF_BOTH) +end - print("\n\n[TEST] Serial loopback - ") - println("Send 100 short messages and read whatever comes back...") - for i = 1:100 - write(sp, "Test message $i\n") - print(read(sp, String)) - i == 100 && write(sp, "done") - end +function test_nonblocking_serial_loopback(sp::SerialPort) - # Allow up to 100 more ms to get the remaining data + println("\nnonblocking_read...") for i = 1:100 - data = read(sp, String) - print(data) - if occursin(data, "done") - break - end + print(String(nonblocking_read(sp))) sleep(0.001) end - println() - sp_flush(sp, SP_BUF_BOTH) -end + write(sp, "test message.\n") -function test_readline(sp::SerialPort) + sleep(0.1) - println("\n[TEST] Read any incoming data for ~1 second...") - for i = 1:1000 - print(read(sp, String)) - sleep(0.001) - end + result = String(nonblocking_read(sp)) + show(result) println() + @test occursin("test message", result) +end - sp_flush(sp, SP_BUF_BOTH) - print("\n\n[TEST] Serial loopback - ") - println("Send 100 short messages and read whatever comes back...") +""" +Check that a serial device returns the expected result under a given +setting for the number of data bits `n` in a UART frame. - for i = 1:100 - write(sp, "Test message $i\n") - sleep(0.001) - received_message = readuntil(sp, '\n') # same as readline(sp) - println(chomp(received_message)) +The most common value for n is 8. Support for other values is device-dependent, +but only 5,6,7,8 are supported by libserialport. +""" +function test_bytestring_roundtrip(s::SerialPort, n::Int=8) + + # If 0x11 and 0x13 are missing, flow control may be enabled. + # set_flow_control(s, xonxoff=SP_XONXOFF_DISABLED) + + msg = collect(UInt8(0):UInt8(255)) + + write(s, msg) + sp_drain(s) - # Trigger an EOF when done writing so readuntil doesn't hang forever - if i == 100 - seteof(sp, true) - end + sleep(0.1) + # rcv = nonblocking_read(s) + rcv = read(s) + + # Expected response + msg .&= UInt8(1 << n - 1) + + if rcv != msg + @warn("Received data does not match transmitted data:") + println("Bytes received: ", rcv) + println("Bytes missing: ", setdiff(msg, rcv)); + println("Bytes added: ", setdiff(rcv, msg)); end - reseteof(sp) - sp_flush(sp, SP_BUF_BOTH) + @test rcv == msg end + function test_high_level_api(args...) if length(args) != 2 @@ -83,11 +95,24 @@ function test_high_level_api(args...) sp = open(args[1], parse(Int, args[2])) + sp.read_timeout_ms = 1000 + print_port_metadata(sp) print_port_settings(sp) - test_nonblocking_serial_loopback(sp) - test_readline(sp) + sleep(2) # MCU serial startup + + @testset "Blocking read/write" begin + test_blocking_serial_loopback(sp) + end + + @testset "Nonblocking read" begin + test_nonblocking_serial_loopback(sp) + end + + @testset "Bytestring roundtrip" begin + test_bytestring_roundtrip(sp) + end close(sp) return diff --git a/test/test-low-level-api.jl b/test/test-low-level-api.jl index a6bbd58..136eb67 100644 --- a/test/test-low-level-api.jl +++ b/test/test-low-level-api.jl @@ -1,113 +1,13 @@ -# `export LIBSERIALPORT_DEBUG=` to display debug info (`unset` to clear) - -using LibSerialPort - -""" -Test that we can change some port configuration settings on a copy of the -provided port. Use two approaches to cover the various set and get functions. -The original port configuration should not be modified by these tests! """ -function test_port_configuration(port::LibSerialPort.Port) - # 1. (Direct) use setter functions for the port struct - test_change_port_copy_method1(port) - # 2. (Roundabout) get a new sp_port_configuration instance, modify it, then - # copy its data fields to the port struct. - test_change_port_copy_method2(port) -end - -function test_change_port_copy_method1(port::LibSerialPort.Port) - port2 = sp_copy_port(port) - sp_close(port) - sp_open(port2, SP_MODE_READ_WRITE) - - print("\n[TEST1] INITIAL ") - print_port_settings(port2) - println("[TEST1] changing port configuration settings.") - sp_set_baudrate(port2, 115200) - sp_set_bits(port2, 6) - sp_set_parity(port2, SP_PARITY_EVEN) - sp_set_stopbits(port2, 2) - - # Request to send / clear to send go as a pair. - # They must be enabled or disabled together. - # RTS and CTS not supported by all host systems, hence the try/catch. - try - sp_set_rts(port2, SP_RTS_OFF) - catch e - println("NOTE: skipped set_rts - $e") - end - - try - sp_set_cts(port2, SP_CTS_IGNORE) - catch e - println("NOTE: skipped set_cts - $e") - end - - sp_set_dtr(port2, SP_DTR_OFF) - sp_set_dsr(port2, SP_DSR_IGNORE) - - sp_set_xon_xoff(port2, SP_XONXOFF_INOUT) - - print("[TEST1] UPDATED ") - print_port_settings(port2) - - println("[TEST1] closing and deleting copied port") - sp_close(port2) - sp_free_port(port2) +This test is written to run on a machine connected to an Arduino-compatible +microcontroller running examples/serial_example.ino. - println("[TEST1] reopening original port") - sp_open(port, SP_MODE_READ_WRITE) - - print("[TEST1] ORIGINAL ") - print_port_settings(port) -end - -function test_change_port_copy_method2(port::LibSerialPort.Port) - port2 = sp_copy_port(port) - sp_close(port) - sp_open(port2, SP_MODE_READ_WRITE) - - # Either - # config2 = sp_new_config() - # Or - config2 = sp_get_config(port2) - - print("\n[TEST2] INITIAL ") - print_port_settings(config2) - - println("[TEST2] changing configuration settings.") - sp_set_config_baudrate(config2, 115200) - sp_set_config_bits(config2, 6) - sp_set_config_parity(config2, SP_PARITY_EVEN) - sp_set_config_stopbits(config2, 2) - sp_set_config_rts(config2, SP_RTS_OFF) - sp_set_config_cts(config2, SP_CTS_IGNORE) - sp_set_config_dtr(config2, SP_DTR_OFF) - sp_set_config_dsr(config2, SP_DSR_IGNORE) - sp_set_config_xon_xoff(config2, SP_XONXOFF_INOUT) - - # sp_set_config(port2, config2) - try - sp_set_config(port2, config2) - catch e - println("NOTE: skipped sp_set_config - $e") - end - - sp_free_config(config2) - - print("[TEST2] UPDATED ") - print_port_settings(port2) - - println("[TEST2] closing and deleting copied port") - sp_close(port2) - sp_free_port(port2) +In bash, `export LIBSERIALPORT_DEBUG=` to display debug info (`unset` to clear) +""" - println("[TEST2] reopening original port") - sp_open(port, SP_MODE_READ_WRITE) +using LibSerialPort +using Test - print("[TEST2] ORIGINAL ") - print_port_settings(port) -end """ Use all wrapped functions to display version info (tested on 0.1.1) @@ -132,64 +32,96 @@ function test_blocking_serial_loopback(port::LibSerialPort.Port, write_timeout_ms::Integer, read_timeout_ms::Integer) println("\nTesting serial loopback with blocking write/read functions...") - println() - nbytes_read, bytes = sp_blocking_read(port, 128, 50) - println("nbytes_read: $nbytes_read") - if nbytes_read > 0 - data = String(bytes) - println(data) - end - sp_drain(port) + + # Clear serial buffers (deletes any buffered data) sp_flush(port, SP_BUF_BOTH) + sleep(0.5) + + function loopback_with_reallocation() + """Use sp_blocking_read, which allocates a new read buffer internally on every call. + """ + for i = 1:10 + message = collect(UInt8(0):UInt8(255)) + sp_blocking_write(port, message, write_timeout_ms) + + sleep(0.1) + + num_bytes_to_read = Int(sp_input_waiting(port)) + if num_bytes_to_read > 0 + + nbytes_read, bytes_read = sp_blocking_read(port, num_bytes_to_read, read_timeout_ms) + + println("($i) $nbytes_read, $num_bytes_to_read") + + @test nbytes_read == num_bytes_to_read - function loop() - for i = 1:100 - sp_blocking_write(port, "Test message $i\n", write_timeout_ms) - sp_drain(port) - sp_flush(port, SP_BUF_OUTPUT) - nbytes_read, bytes = sp_blocking_read(port, 128, 50) - println("($i) nbytes_read: $nbytes_read") - if nbytes_read > 0 - data = String(bytes) - println(data) + if bytes_read != message + @warn("Received data does not match transmitted data:") + println("Bytes received: ", bytes_read) + println("Bytes missing: ", setdiff(message, bytes_read)); + println("Bytes added: ", setdiff(bytes_read, message)); + end + + @test bytes_read == message end - sp_flush(port, SP_BUF_INPUT) end end - @time loop() -end + function loopback_with_preallocation() + """Use sp_blocking_read with a preallocated read buffer. + """ + for i = 1:10 + message = "Test message $i\n" + sp_blocking_write(port, message, write_timeout_ms) -function test_nonblocking_serial_loopback(port::LibSerialPort.Port) + sleep(0.1) + + num_bytes_to_read = Int(sp_input_waiting(port)) + if num_bytes_to_read > 0 + + nbytes_read = 0 + bytes_read = zeros(UInt8, 1024) + + for j in 1:num_bytes_to_read + b = Ref{UInt8}(0) + nbytes_read += Int(sp_blocking_read(port, b, 1, read_timeout_ms)) + bytes_read[j] = b.x + end + + response = String(bytes_read[1:nbytes_read]) - println("\n[TEST] Read any incoming data for ~1 second...") - for i = 1:1000 - nbytes_read, bytes = sp_nonblocking_read(port, 1024) - print(String(bytes)) - sleep(0.001) + @test nbytes_read == num_bytes_to_read + + println("($i) $nbytes_read, $num_bytes_to_read\n$response") + end + end end + @time loopback_with_reallocation() + @time loopback_with_preallocation() +end + +function test_nonblocking_serial_loopback(port::LibSerialPort.Port) + sp_flush(port, SP_BUF_BOTH) - print("\n\n[TEST] Serial loopback - ") - println("Send 100 short messages and read whatever comes back...") + println("\nTesting serial loopback with nonblocking write/read functions...") function loops() - for i = 1:100 + for i = 1:10 sp_nonblocking_write(port, "Test message $i\n") - sp_drain(port) - nbytes_read, bytes = sp_nonblocking_read(port, 256) - print(String(bytes)) end - # Read and print any remaining data for ~50 ms - for i = 1:50 - nbytes_read, bytes = sp_nonblocking_read(port, 256) - print(String(bytes)) - sleep(0.001) - end + sleep(0.1) # Give the connected device time to echo back - sp_flush(port, SP_BUF_BOTH) + for i = 1:10 + num_bytes_to_read = Int(sp_input_waiting(port)) + if num_bytes_to_read > 0 + nbytes_read, bytes = sp_nonblocking_read(port, num_bytes_to_read) + @test nbytes_read == num_bytes_to_read + sleep(0.1) + end + end end @time loops() @@ -197,7 +129,7 @@ end """ This example demonstrates serial communication with one port. The default -configuration is 9600-8-N-1, i.e. 9600 bps with 8 data bits, no parity check, +configuration is 115200-8-N-1, i.e. 115200 bps with 8 data bits, no parity check, and one stop bit. The baud rate is overridden on the command line with a second argument. Hardware and software flow control measures are disabled by default. @@ -218,22 +150,26 @@ function test_low_level_api(args...) end port = sp_get_port_by_name(args[1]) # e.g. "/dev/cu.wchusbserial1410" - baudrate = nargs >= 2 ? parse(Int, args[2]) : 9600 + baudrate = nargs >= 2 ? parse(Int, args[2]) : 115200 print_version() list_ports() sp_open(port, SP_MODE_READ_WRITE) - test_port_configuration(port) - sp_set_baudrate(port, baudrate) + sp_set_bits(port, 8) - test_blocking_serial_loopback(port, 10, 40) + sleep(2) - test_nonblocking_serial_loopback(port) + @testset "Blocking read/write" begin + test_blocking_serial_loopback(port, 10, 10) + end + + @testset "Nonblocking read/write" begin + test_nonblocking_serial_loopback(port) + end - println("\nClosing and freeing port. Over and out!") sp_close(port) sp_free_port(port) end From 91fcb676dac7a6b11db99bd0cda6f5ee094ccd63 Mon Sep 17 00:00:00 2001 From: Samuel Powell Date: Sat, 15 Aug 2020 20:22:55 +0100 Subject: [PATCH 2/3] Update testing and version on master (#73) * Add Julia 0.5 to testing targets * Minor version increment to 0.5.0 on master --- .travis.yml | 1 + Project.toml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 81894f6..dab6bfa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,7 @@ arch: julia: - 1.3 - 1.4 + - 1.5 - nightly matrix: allow_failures: diff --git a/Project.toml b/Project.toml index d6d0d44..c304588 100644 --- a/Project.toml +++ b/Project.toml @@ -1,6 +1,6 @@ name = "LibSerialPort" uuid = "a05a14c7-6e3b-5ba9-90a2-45558833e1df" -version = "0.4.0" +version = "0.5.0" [deps] Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" From 83b0506d2ee872ea022b0af731969b89ec48b024 Mon Sep 17 00:00:00 2001 From: Markus Kuhn Date: Wed, 11 Nov 2020 13:10:09 +0000 Subject: [PATCH 3/3] Add missing timeout implementation from #61 and fixed #72 (missing flow-control setting in tests) (#74) * fix test_low_level_api() by actually disabling flow control This test routine claimed in a comment to disable flow control. It actually requires the serial port to be transparent for all 256 byte values, which in turn requires deactivation of software flow control, because otherwise the byte-transparency tests fail for bytes 0x11 (Ctrl-Q, XON) and 0x13 (Ctrl-S, XOFF) on systems such as Ubuntu Linux where software flow control is active by default. * completed read/write timeout implementation in high-level API Added new functions to set and clear cumulative timeouts for blocking read and write functions, a new `Timeout` exception that will be thrown, and time-tracking code in calls to the blocking read/write functions. This way, users can now set overall timeouts on other IO functions, such as `readuntil`, that make multiple calls to our blocking functions. * change close(::SerialPort) to return nothing That what other close(::IO) methods in Base do. It looks quite odd in the REPL to get the entire closed SerialPort object thrown back at you. * build HTML documentation using Documenter.jl also polished some docstrings and fixed a parameter name inconsistency * split low-level API into separate module (#68) The low-level API wrappers now sit in a submodule LibSerialPort.Lib. The high-level API currently still re-exports a few symbols from that low-level API module, to preserve backwards compatibility. This is due to * my previously started attempt to merge the low and high-level APIs for the three functions sp_flush, sp_drain, and sp_output_waiting * the fact that several high-level APIs currently still refer to enum types and constants defined by the low-level API We may want to phase out either or both in future, for a cleaner separation, and a more Julian high-level API. * documentation typo fixed * allow arm64 to fail Arm64 builds have long filed, therefore let's allow them to fail in Travis until that problem is fixed, such that PR authors for other issues do not get build errors that they are not responsible for. --- .gitignore | 1 + .travis.yml | 12 +- docs/Project.toml | 2 + docs/make.jl | 8 + docs/src/index.md | 78 ++++ docs/src/wrap.md | 5 + src/LibSerialPort.jl | 790 +++++++++++++++++++++++++++++++------ src/high-level-api.jl | 413 ------------------- src/wrap.jl | 179 ++++++++- test/runtests.jl | 1 + test/test-low-level-api.jl | 4 +- 11 files changed, 945 insertions(+), 548 deletions(-) create mode 100644 docs/Project.toml create mode 100644 docs/make.jl create mode 100644 docs/src/index.md create mode 100644 docs/src/wrap.md delete mode 100644 src/high-level-api.jl diff --git a/.gitignore b/.gitignore index 08a13c5..e0ba07b 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ deps/* !deps/build.jl Manifest.toml +docs/build/ diff --git a/.travis.yml b/.travis.yml index dab6bfa..509fed2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,9 +13,10 @@ julia: - 1.4 - 1.5 - nightly -matrix: +jobs: allow_failures: - julia: nightly + - arch: arm64 exclude: - os: osx arch: x86 @@ -25,6 +26,15 @@ matrix: arch: arm64 - julia: nightly arch: arm64 + include: + - stage: "Documentation" + julia: 1.5 + os: linux + script: + - julia --project=docs/ -e 'using Pkg; Pkg.develop(PackageSpec(path=pwd())); + Pkg.instantiate()' + - julia --project=docs/ docs/make.jl + after_success: skip before_install: # - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo chmod 666 /dev/tty*; fi diff --git a/docs/Project.toml b/docs/Project.toml new file mode 100644 index 0000000..dfa65cd --- /dev/null +++ b/docs/Project.toml @@ -0,0 +1,2 @@ +[deps] +Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" diff --git a/docs/make.jl b/docs/make.jl new file mode 100644 index 0000000..52f05fb --- /dev/null +++ b/docs/make.jl @@ -0,0 +1,8 @@ +using Documenter, LibSerialPort, LibSerialPort.Lib + +makedocs( + sitename="LibSerialPort.jl", + format = Documenter.HTML( + prettyurls = get(ENV, "CI", nothing) == "true" + ) +) diff --git a/docs/src/index.md b/docs/src/index.md new file mode 100644 index 0000000..db00400 --- /dev/null +++ b/docs/src/index.md @@ -0,0 +1,78 @@ +# LibSerialPort.jl – access serial ports + +```@docs +LibSerialPort +``` + +# Enumerating serial ports + +```@docs +list_ports +get_port_list +print_port_metadata +``` + +## Opening and configuring ports + +```@docs +LibSerialPort.open(::AbstractString, ::Integer) +SerialPort(::AbstractString) +open(::SerialPort; ::SPMode) +close(sp::SerialPort) +set_speed +set_frame +set_flow_control +isopen(sp::SerialPort) +eof(sp::SerialPort) +seteof +get_port_settings +print_port_settings +set_read_timeout +set_write_timeout +clear_read_timeout +clear_write_timeout +sp_flush +sp_drain +sp_output_waiting +``` + +# Read and write methods from Base + +Many of the read/write methods defined in `Base` that operate on an +object of type `IO` can also be used with objects of type +`SerialPort`. Therefore we repeat the documentation of some of these +here. (Note that some of the following docstings also refer to other +methods that are not applicable to `SerialPort`.) + +```@docs +Base.read(::IO, ::Any) +Base.read! +Base.readbytes! +Base.readavailable +Base.readline +Base.readlines +Base.eachline +Base.write +Base.print(::IO, ::Any) +``` + +# Additional read methods + +```@docs +read(::SerialPort) +nonblocking_read(::SerialPort) +bytesavailable(::SerialPort) +``` + +# Other references + +The following are listed here only because they are referenced above: + +```@docs +Base.ntoh +Base.hton +Base.ltoh +Base.htol +Base.stdout +Base.string(xs...) +``` diff --git a/docs/src/wrap.md b/docs/src/wrap.md new file mode 100644 index 0000000..a63ef82 --- /dev/null +++ b/docs/src/wrap.md @@ -0,0 +1,5 @@ +# Low-level C API wrappers + +```@autodocs +Modules = [LibSerialPort.Lib] +``` diff --git a/src/LibSerialPort.jl b/src/LibSerialPort.jl index 3e30596..176abfa 100644 --- a/src/LibSerialPort.jl +++ b/src/LibSerialPort.jl @@ -1,14 +1,66 @@ +""" +The `LibSerialPort` module provides access to [serial +ports](https://en.wikipedia.org/wiki/Serial_port) +([UARTs](https://en.wikipedia.org/wiki/Universal_asynchronous_receiver-transmitter) +or USB/Bluetooth devices that emulate their operating-system +interface) using the portable C library +[libserialport](http://sigrok.org/wiki/Libserialport). + +It defines the `SerialPort` type, which is returned by +`LibSerialPort.open`. This is a subtype of `Base.IO` and can +therefore be used like a file handle, using many of the same `read`, +`write`, `print`, etc. methods defined for `Base.IO`. + +# Example +``` +using LibSerialPort + +list_ports() +ports = get_port_list() + +sp = LibSerialPort.open("/dev/ttyUSB0", 115200) +set_flow_control(sp) +sp_flush(sp, SP_BUF_BOTH) +write(sp, "hello\\n") +println(readline(sp)) +close(sp) +``` +""" module LibSerialPort -using Libdl -using libserialport_jll +include("wrap.jl") +using .Lib # low-level wrapper for the libserialport C API +import .Lib: sp_flush, sp_drain, sp_output_waiting export + SerialPort, + list_ports, + get_port_list, + get_port_settings, + print_port_metadata, + print_port_settings, + set_speed, + set_frame, + set_flow_control, + set_read_timeout, + set_write_timeout, + clear_read_timeout, + clear_write_timeout, + seteof, + reseteof, + nonblocking_read, + + # Re-export the following symbol definitions from LibSerialPort.Lib. + # (We may want to phase out some of these later.) + + # Low-level functions currently extended by the high-level API + sp_flush, + sp_drain, + sp_output_waiting, + # Enum types - SPReturn, SPMode, - SPEvent, SPBuffer, SPParity, SPrts, @@ -16,157 +68,639 @@ export SPdtr, SPdsr, SPXonXoff, - SPFlowControl, - SPSignal, - SPTransport, # Enum values - SP_OK, - SP_ERR_ARG, - SP_ERR_FAIL, - SP_ERR_MEM, - SP_ERR_SUPP, SP_MODE_READ, SP_MODE_WRITE, SP_MODE_READ_WRITE, - SP_EVENT_RX_READY, - SP_EVENT_TX_READY, - SP_EVENT_ERROR, SP_BUF_INPUT, SP_BUF_OUTPUT, SP_BUF_BOTH, - SP_PARITY_INVALID, SP_PARITY_NONE, SP_PARITY_ODD, SP_PARITY_EVEN, SP_PARITY_MARK, SP_PARITY_SPACE, - SP_RTS_INVALID, SP_RTS_OFF, SP_RTS_ON, SP_RTS_FLOW_CONTROL, - SP_CTS_INVALID, SP_CTS_IGNORE, SP_CTS_FLOW_CONTROL, - SP_DTR_INVALID, SP_DTR_OFF, SP_DTR_ON, SP_DTR_FLOW_CONTROL, - SP_DSR_INVALID, SP_DSR_IGNORE, SP_DSR_FLOW_CONTROL, - SP_XONXOFF_INVALID, SP_XONXOFF_DISABLED, SP_XONXOFF_IN, SP_XONXOFF_OUT, - SP_XONXOFF_INOUT, - SP_FLOWCONTROL_NONE, - SP_FLOWCONTROL_XONXOFF, - SP_FLOWCONTROL_RTSCTS, - SP_FLOWCONTROL_DTRDSR, - SP_SIG_CTS, - SP_SIG_DSR, - SP_SIG_DCD, - SP_SIG_RI, - SP_TRANSPORT_NATIVE, - SP_TRANSPORT_USB, - SP_TRANSPORT_BLUETOOTH, - - # Functions from libserialport API - sp_get_port_by_name, - sp_free_port, - sp_list_ports, - sp_copy_port, - sp_free_port_list, - sp_open, - sp_close, - sp_get_port_name, - sp_get_port_description, - sp_get_port_transport, - sp_get_port_usb_bus_address, - sp_get_port_usb_vid_pid, - sp_get_port_usb_manufacturer, - sp_get_port_usb_product, - sp_get_port_usb_serial, - sp_get_port_bluetooth_address, - sp_get_port_handle, - sp_new_config, - sp_free_config, - sp_get_config, - sp_set_config, - sp_set_baudrate, - sp_get_config_baudrate, - sp_set_config_baudrate, - sp_set_bits, - sp_get_config_bits, - sp_set_config_bits, - sp_set_parity, - sp_get_config_parity, - sp_set_config_parity, - sp_set_stopbits, - sp_get_config_stopbits, - sp_set_config_stopbits, - sp_set_rts, - sp_get_config_rts, - sp_set_config_rts, - sp_set_cts, - sp_get_config_cts, - sp_set_config_cts, - sp_set_dtr, - sp_get_config_dtr, - sp_set_config_dtr, - sp_set_dsr, - sp_get_config_dsr, - sp_set_config_dsr, - sp_set_xon_xoff, - sp_get_config_xon_xoff, - sp_set_config_xon_xoff, - sp_set_config_flowcontrol, - sp_set_flowcontrol, - sp_blocking_read, - sp_blocking_read_next, - sp_nonblocking_read, - sp_blocking_write, - sp_nonblocking_write, - sp_input_waiting, - sp_output_waiting, - sp_flush, - sp_drain, - sp_new_event_set, - sp_add_port_events, - sp_wait, - sp_free_event_set, - sp_get_signals, - sp_start_break, - sp_end_break, - sp_last_error_code, - sp_last_error_message, - sp_get_major_package_version, - sp_get_minor_package_version, - sp_get_micro_package_version, - sp_get_package_version_string, - sp_get_current_lib_version, - sp_get_revision_lib_version, - sp_get_age_lib_version, - sp_get_lib_version_string, - - # Wrapper object for high-level API - SerialPort, + SP_XONXOFF_INOUT - # Functions from high-level API - list_ports, - get_port_list, - get_port_settings, - print_port_metadata, - print_port_settings, - set_speed, - set_frame, - set_flow_control, - seteof, - reseteof, - nonblocking_read +import Base: isopen, open, close, write, unsafe_write, flush, + read, unsafe_read, bytesavailable, eof -include("wrap.jl") -include("high-level-api.jl") + +mutable struct SerialPort <: IO + ref::Port + is_eof::Bool + is_open::Bool + + # cumulative timeout limits, passed on to blocking libserialport functions + # 0 means wait indefinitely, as per sigrok libserialport interface + read_timeout_ms::Cuint + write_timeout_ms::Cuint + + function SerialPort(ref, is_eof, is_open) + sp = new(ref, is_eof, is_open, 0, 0) + finalizer(destroy!, sp) + return sp + end +end + + +""" +A `Timeout` exception is thrown by blocking read or write functions +when the cumulative blocking time since that last call to +`set_read_timeout(t)` or `set_write_timeout(t)` has exceeded `t` +seconds. +""" +struct Timeout <: Exception +end + + +""" + SerialPort(portname::AbstractString) + +Constructs and returns a `SerialPort` object. +""" +SerialPort(portname::AbstractString) = SerialPort(sp_get_port_by_name(portname), false, false) + + +""" + destroy!(sp::SerialPort) + +Close the `SerialPort` object and deallocate associated memory +objects. +""" +function destroy!(sp::SerialPort) + close(sp) + sp_free_port(sp.ref) +end + + +""" + isopen(sp::SerialPort) -> Bool + +Determine whether a `SerialPort` object is open. +""" +isopen(sp::SerialPort) = sp.is_open + + +""" + eof(sp::SerialPort) -> Bool + +Return the “end-of-file” state (`true` or `false`). Since serial ports +do not have any standard mechanism for signalling the end of a +transmitted file, this is just a dummy function that returns whatever +Boolean value `eof` was previously set with `seteof(sp, eof)`. +Returns `false` by default. +""" +eof(sp::SerialPort) = sp.is_eof + + +""" + set_speed(sp::SerialPort, baudrate::Integer) + +Set the data signalling rate, or the reciprocal duration of one data +bit, in bits/s. The set of values supported depends on the UART +hardware, but typically includes e.g. 9600, 19200 and 115200. + +Raise an `ErrorException` if `bps` is not a value supported by the +driver or hardware. +""" +function set_speed(sp::SerialPort, baudrate::Integer) + sp_set_baudrate(sp.ref, baudrate) + return nothing +end + + +""" + set_frame(sp::SerialPort; + ndatabits::Integer = 8, + parity::SPParity = SP_PARITY_NONE, + nstopbits::Integer = 1) + +Configure the [data +framing](https://en.wikipedia.org/wiki/Universal_asynchronous_receiver/transmitter#Data_framing) +parameters. Defaults to the very common “8N1” scheme, which consists +of a start bit followed by eight data bits, no parity bit, one stop +bit. + +for more details. + +* `ndatabits` is the number of data bits, which is `8` in the common "8N1" scheme + +* `parity` controls the presence and value of a party bit and can take + the values `SP_PARITY_NONE` (default), `SP_PARITY_ODD`, + `SP_PARITY_EVEN`, `SP_PARITY_MARK` and `SP_PARITY_SPACE` + +* `nstopbits` sets the number of stop bits, typically `1` (default) or `2` +""" +function set_frame(sp::SerialPort; + ndatabits::Integer=8, + parity::SPParity=SP_PARITY_NONE, + nstopbits::Integer=1) + + sp_set_bits(sp.ref, ndatabits) + sp_set_parity(sp.ref, parity) + sp_set_stopbits(sp.ref, nstopbits) + return nothing +end + + +""" + set_flow_control(sp::SerialPort; + rts::SPrts = SP_RTS_OFF, + cts::SPcts = SP_CTS_IGNORE, + dtr::SPdtr = SP_DTR_OFF, + dst::SPdsr = SP_DSR_IGNORE, + xonxoff::SPXonXoff = SP_XONXOFF_DISABLED)` + +Configure the flow-control lines and method. Many systems don't support all options. +If an unsupported option is requested, the library will return SP_ERR_SUPP. + +* `rts` controls the output line _Ready To Send (RTS)_ and can take + the values `SP_RTS_OFF` (default), `SP_RTS_ON` and + `SP_RTS_FLOW_CONTROL`. + +* `cts` controls the input line _Clear To Send (CTS)_ and can take the + values `SP_CTS_IGNORE` (default) and `SP_CTS_FLOW_CONTROL` + +* `dtr` controls the output line _Data Terminal Ready (DTR)_ and can + take the values `SP_DTR_OFF` (default), `SP_DTR_ON`, and + `SP_DTR_FLOW_CONTROL` + +* `dsr` controls the input line _Data Set Ready (DSR)_ and can take + the values `SP_DSR_IGNORE` (default) and `SP_DSR_FLOW_CONTROL` + +* `xonxoff` controls whether [software flow + control](https://en.wikipedia.org/wiki/Software_flow_control) via the + control bytes XOFF (pause transmission, 0x13, Ctrl-S) and XON (resume + transmission, 0x11, Ctrl-Q) is active, and in which direction; it can + take the values: `SP_XONXOFF_DISABLED` (default), `SP_XONXOFF_IN`, + `SP_XONXOFF_OUT`, and `SP_XONXOFF_INOUT` +""" +function set_flow_control(sp::SerialPort; + rts::SPrts=SP_RTS_OFF, + cts::SPcts=SP_CTS_IGNORE, + dtr::SPdtr=SP_DTR_OFF, + dsr::SPdsr=SP_DSR_IGNORE, + xonxoff::SPXonXoff=SP_XONXOFF_DISABLED) + + # CTS and RTS must be enabled or disabled as a pair + sp_set_rts(sp.ref, rts) + sp_set_cts(sp.ref, cts) + + sp_set_dtr(sp.ref, dtr) + sp_set_dsr(sp.ref, dsr) + + sp_set_xon_xoff(sp.ref, xonxoff) + return nothing +end + + +""" + list_ports([nports_guess::Integer])` + +Print a list of currently visible ports, along with some basic info. + +`nports_guess` provides the number of ports guessed. Its default is `64`. +""" +function list_ports(;nports_guess::Integer=64) + ports = sp_list_ports() + + for port in unsafe_wrap(Array, ports, nports_guess, own=false) + port == C_NULL && return + + println(sp_get_port_name(port)) + println("\tDescription:\t", sp_get_port_description(port)) + println("\tTransport type:\t", sp_get_port_transport(port)) + end + + sp_free_port_list(ports) + return nothing +end + + +""" + get_port_list([nports_guess::Integer]) + +Return a vector of currently visible ports. + +`nports_guess` provides the number of ports guessed. Its default is `64`. +""" +function get_port_list(;nports_guess::Integer=64) + ports = sp_list_ports() + port_list = String[] + for port in unsafe_wrap(Array, ports, nports_guess, own=false) + port == C_NULL && return port_list + push!(port_list, sp_get_port_name(port)) + end + sp_free_port_list(ports) + return port_list +end + + +""" + print_port_metadata(sp::SerialPort; show_config::Bool = true) + +Print info found for this port. +Note: port should be open to obtain a valid FD/handle before accessing fields. + +`show_config` is `true` by default and prints out the current port settings. +""" +function print_port_metadata(sp::SerialPort; show_config::Bool=true) + print_port_metadata(sp.ref, show_config=show_config) + return nothing +end + +function print_port_metadata(port::LibSerialPort.Port; show_config::Bool=true) + println("\nPort name:\t", sp_get_port_name(port)) + transport = sp_get_port_transport(port) + print("\nPort transport:\t"); + if transport == SP_TRANSPORT_NATIVE + println("native serial port") + elseif transport == SP_TRANSPORT_USB + println("USB") + println("Manufacturer:\t", sp_get_port_usb_manufacturer(port)) + println("Product:\t", sp_get_port_usb_product(port)) + println("USB serial number:\t", sp_get_port_usb_serial(port)) + bus, addr = sp_get_port_usb_bus_address(port) + println("USB bus #:\t", bus) + println("Address on bus:\t", addr) + vid, pid = sp_get_port_usb_vid_pid(port) + println("Vendor ID:\t", vid) + println("Product ID:\t", pid) + elseif transport == SP_TRANSPORT_BLUETOOTH + println("Bluetooth") + println("Bluetooth address:\t", sp_get_port_bluetooth_address(port)) + end + println("File descriptor:\t", sp_get_port_handle(port)) + + if show_config + print_port_settings(port) + end + return nothing +end + + +function get_port_settings(config::LibSerialPort.Config) + return Dict( + "baudrate" => sp_get_config_baudrate(config), + "bits" => sp_get_config_bits(config), + "parity" => sp_get_config_parity(config), + "stopbits" => sp_get_config_stopbits(config), + "RTS" => sp_get_config_rts(config), + "CTS" => sp_get_config_cts(config), + "DTR" => sp_get_config_dtr(config), + "DSR" => sp_get_config_dsr(config), + "XonXoff" => sp_get_config_xon_xoff(config), + ) +end + +function get_port_settings(port::LibSerialPort.Port) + config = sp_get_config(port) + settings = get_port_settings(config) + sp_free_config(config) + return settings +end + +""" + get_port_settings(sp::SerialPort) + +Return port settings for `sp` as a dictionary. +""" +get_port_settings(sp::SerialPort) = get_port_settings(sp.ref) + + +""" + print_port_settings(sp::SerialPort) + +Print port settings for `sp`. +""" +function print_port_settings(sp::SerialPort) + print_port_settings(sp.ref) + println("Read timeout (ms, forever if 0): ", sp.read_timeout_ms) + return +end + +function print_port_settings(port::LibSerialPort.Port) + println("Configuration for ", sp_get_port_name(port), ":") + config = sp_get_config(port) + print_port_settings(config) + sp_free_config(config) + return +end + +function print_port_settings(config::LibSerialPort.Config) + s = get_port_settings(config) + for (k, v) in s + println("\t$k\t", v) + end + return +end + + +""" + open(sp::SerialPort; mode::SPMode = SP_MODE_READ_WRITE) + +Open the serial port `sp`. + +`mode` selects in which direction of transmission access is requested +and can take the values: `SP_MODE_READ`, `SP_MODE_WRITE`, and +`SP_MODE_READ_WRITE` (default). +""" +function open(sp::SerialPort; mode::SPMode=SP_MODE_READ_WRITE) + sp_open(sp.ref, mode) + sp.is_open = true + return sp +end + + +""" + open(portname::AbstractString, baudrate::Integer; + mode::SPMode, ndatabits::Integer, + parity::SPParity, nstopbits::Integer) + +Construct, configure and open a `SerialPort` object. + +* `portname` is the name of the serial port to open. Typical port + names available depend on the operating system. Some valid names are + listed by [`get_port_list()`](@ref), but there are can be others and aliases: +* * Linux: `"/dev/ttyS0"`, `"/dev/ttyUSB0"`, `"/dev/serial/by-id/..."` +* * macOS: `"/dev/cu.usbserial-0001"`, `"/dev/cu.Bluetooth-Incoming-Port"` +* * Windows: `"COM1"`, `"COM2"`,`"COM3"` +* `baudrate` is the data signalling rate, or the reciprocal duration + of one data bit, in bits/s. The set of values supported depends on + the UART hardware, but typically includes e.g. 9600, 19200 and 115200. +* `mode` selects in which direction of transmission access is + requested and can take the values: `SP_MODE_READ`, `SP_MODE_WRITE`, + and `SP_MODE_READ_WRITE` (default). + +The parameters `ndatabits`, `parity` and `nstopbits` have the same +meaning as in [`set_frame`](@ref) and default to the common “8N1” +frame format (8 data bits, no parity, one stop bit). + +To set the flow-control method, use [`set_flow_control`](@ref). +""" +function open(portname::AbstractString, + baudrate::Integer; + mode::SPMode=SP_MODE_READ_WRITE, + ndatabits::Integer=8, + parity::SPParity=SP_PARITY_NONE, + nstopbits::Integer=1) + sp = SerialPort(portname) + sp_open(sp.ref, mode) + sp.is_open = true + set_speed(sp, baudrate) + set_frame(sp, ndatabits=ndatabits, parity=parity, nstopbits=nstopbits) + return sp +end + + +""" + close(sp::SerialPort) + +Close the serial port `sp`. +""" +function close(sp::SerialPort) + if isopen(sp) + sp_close(sp.ref) + sp.is_open = false + end + return +end + +# fixes https://github.com/JuliaIO/LibSerialPort.jl/issues/53 +@deprecate flush(sp::SerialPort, buffer::SPBuffer=SP_BUF_BOTH) sp_flush(sp, buffer) + +# pass through some methods from the low-level interface +sp_output_waiting(sp::SerialPort) = sp_output_waiting(sp.ref) +sp_flush(sp::SerialPort, args...) = sp_flush(sp.ref, args...) +sp_drain(sp::SerialPort) = sp_drain(sp.ref) + +""" + set_read_timeout(sp::SerialPort, seconds::Real) + +Set a read timeout limit of `t` > 0 seconds for the total (cumulative) +time that subsequently called blocking read functions can wait before +a `Timeout` exception is thrown. + +# Example + +``` +sp=LibSerialPort.open("/dev/ttyUSB0", 115200) +# wait until either two lines have been received +# or 10 seconds have elapsed +set_read_timeout(sp, 10) +try + line1 = readuntil(sp, '\n') + line2 = readuntil(sp, '\n') +catch e + if isa(e, LibSerialPort.Timeout) + println("Too late!") + else + rethrow() + end +end +clear_read_timeout(sp) +``` + +See also: [`clear_read_timeout`](@ref), [`set_write_timeout`](@ref) +""" +function set_read_timeout(sp::SerialPort, seconds::Real) + @assert seconds > 0 + sp.read_timeout_ms = seconds * 1000; + return +end + +""" + set_write_timeout(sp::SerialPort, seconds::Real) + +Set a write timeout limit of `t` > 0 seconds for the total (cumulative) +time that subsequently called blocking read functions can wait before +a `Timeout` exception is thrown. + +# Example + +``` +sp=LibSerialPort.open("/dev/ttyUSB0", 300) +# wait until either 4000 periods have been +# passed on to the serial-port driver or +# 10 seconds have elapsed +set_write_timeout(sp, 10) +try + for i=1:50 ; write(sp, '.' ^ 80); end +catch e + if isa(e, LibSerialPort.Timeout) + println("This took too long!") + else + rethrow() + end +end +clear_write_timeout(sp) +``` + +See also: [`clear_write_timeout`](@ref), [`set_read_timeout`](@ref) +""" +function set_write_timeout(sp::SerialPort, seconds::Real) + @assert seconds > 0 + sp.write_timeout_ms = seconds * 1000; + return +end + +""" + clear_read_timeout(sp::SerialPort) + +Cancel any previous read timeout, such that blocking read operations +will now wait without any time limit. +""" +function clear_read_timeout(sp::SerialPort) + sp.read_timeout_ms = 0; + return +end + +""" + clear_write_timeout(sp::SerialPort) + +Cancel any previous write timeout, such that blocking write +operations will block without any time limit. +""" +function clear_write_timeout(sp::SerialPort) + sp.write_timeout_ms = 0; + return +end + +# We define here only the basic methods for reading and writing +# bytes. All other methods for reading/writing the canonical binary +# representation of any type, and print() methods for writing +# its text representation, are inherited from the IO supertype +# (see julia/base/io.jl), i.e. work just like for files. + +function read(sp::SerialPort, ::Type{UInt8}) + byte_ref = Ref{UInt8}(0) + unsafe_read(sp, byte_ref, 1) + return byte_ref[] +end + + +function unsafe_read(sp::SerialPort, p::Ptr{UInt8}, nb::UInt) + if sp.read_timeout_ms > 0 + starttime = time_ns() + end + nbytes = sp_blocking_read(sp.ref, p, nb, sp.read_timeout_ms) + if nbytes < nb + @assert sp.read_timeout_ms > 0 + sp.read_timeout_ms = 0 + throw(Timeout()) + elseif sp.read_timeout_ms > 0 + # how much of the timeout have we used up already? + elapsed_ms = (time_ns() - starttime + 999_999) ÷ 1_000_000 + if sp.read_timeout_ms > elapsed_ms + sp.read_timeout_ms -= elapsed_ms + else + sp.read_timeout_ms = 0 + throw(Timeout()) + end + end + nothing +end + + +function write(sp::SerialPort, b::UInt8) + unsafe_write(sp, Ref(b), 1) +end + + +function unsafe_write(sp::SerialPort, p::Ptr{UInt8}, nb::UInt) + if sp.write_timeout_ms > 0 + starttime = time_ns() + end + nbytes = sp_blocking_write(sp.ref, p, nb, sp.write_timeout_ms) + if nbytes < nb + @assert sp.write_timeout_ms > 0 + sp.write_timeout_ms = 0 + throw(Timeout()) + elseif sp.write_timeout_ms > 0 + # how much of the timeout have we used up already? + elapsed_ms = (time_ns() - starttime + 999_999) ÷ 1_000_000 + if sp.write_timeout_ms > elapsed_ms + sp.write_timeout_ms -= elapsed_ms + else + sp.write_timeout_ms = 0 + throw(Timeout()) + end + end + nbytes +end + + +""" + seteof(sp::SerialPort, state::Bool) + +Set the return value of `eof(sp)` to `state`. +""" +function seteof(sp::SerialPort, state::Bool) + sp.is_eof = state + return nothing +end + + +""" + reseteof(sp::SerialPort) + +Set the return value of `eof(sp)` to its default of `false` +""" +reseteof(sp::SerialPort) = seteof(sp, false) + + +""" + read(sp::SerialPort) + +Return all incoming bytes currently available in the UART as a +Vector{UInt8}. + +!!! note + + Julia's Base module defines `read(s::IO, nb::Integer = typemax(Int))`. + This method overrides the default value `nb` to `bytesavailable(sp)`, + which is useful for this context. +""" +read(sp::SerialPort) = read(sp, bytesavailable(sp)) + + +""" + nonblocking_read(sp::SerialPort) + +Read everything from the specified serial ports `sp` input buffer, one byte at +a time, until it is empty. Returns a `String`. +""" +function nonblocking_read(sp::SerialPort) + result = UInt8[] + byte_ref = Ref{UInt8}(0) + while bytesavailable(sp) > 0 + sp_nonblocking_read(sp.ref, byte_ref, 1) + push!(result, byte_ref.x) + end + return result +end + + +""" + bytesavailable(sp::SerialPort) + +Return the number of bytes waiting in the input buffer. +""" +bytesavailable(sp::SerialPort) = sp_input_waiting(sp.ref) end # LibSerialPort diff --git a/src/high-level-api.jl b/src/high-level-api.jl deleted file mode 100644 index 370a0bd..0000000 --- a/src/high-level-api.jl +++ /dev/null @@ -1,413 +0,0 @@ -import Base: isopen, open, close, write, unsafe_write, flush, - read, unsafe_read, bytesavailable, eof - - -mutable struct SerialPort <: IO - ref::Port - is_eof::Bool - is_open::Bool - read_timeout_ms::Int # 0 to wait indefinitely, per sigrok libserialport interface - function SerialPort(ref, is_eof, is_open, read_timeout_ms) - sp = new(ref, is_eof, is_open, read_timeout_ms) - finalizer(destroy!, sp) - return sp - end -end - - -""" -`sp = SerialPort(portname::AbstractString)` - -Constructor for the `SerialPort` object. -""" -SerialPort(portname::AbstractString) = SerialPort(sp_get_port_by_name(portname), false, false, 0) - - -""" -`destroy!(sp::SerialPort)` - -Destructor for the `SerialPort` object. -""" -function destroy!(sp::SerialPort) - close(sp) - sp_free_port(sp.ref) -end - - -""" -`isopen(sp::SerialPort) -> Bool` - -Determine whether a SerialPort object is open. -""" -isopen(sp::SerialPort) = sp.is_open - - -""" -`eof(sp::SerialPort) -> Bool` - -Return EOF state (`true` or `false`)`. -""" -eof(sp::SerialPort) = sp.is_eof - - -""" -`set_speed(sp::SerialPort,bps::Integer)` - -Set connection speed of `sp` in bits per second. Raise an -`ErrorException` if `bps` is not a valid/supported value. -""" -function set_speed(sp::SerialPort, bps::Integer) - sp_set_baudrate(sp.ref, bps) - return nothing -end - - -""" -`set_frame(sp::SerialPort [, ndatabits::Integer, parity::SPParity, nstopbits::Integer])` - -Configure packet framing. Defaults to the most common "8N1" scheme. See -https://en.wikipedia.org/wiki/Universal_asynchronous_receiver/transmitter#Data_framing -for more details. - -`ndatabits` is the number of data bits which is `8` in the common "8N1" sceme. - -The `parity` is set to none in the "8N1" sceme and can take the values: -`SP_PARITY_NONE`, `SP_PARITY_ODD`, `SP_PARITY_EVEN`, `SP_PARITY_MARK` and -`SP_PARITY_SPACE`. - -`nstopbits` is the number of stop bits, which is `1` by default. -""" -function set_frame(sp::SerialPort; - ndatabits::Integer=8, - parity::SPParity=SP_PARITY_NONE, - nstopbits::Integer=1) - - sp_set_bits(sp.ref, ndatabits) - sp_set_parity(sp.ref, parity) - sp_set_stopbits(sp.ref, nstopbits) - return nothing -end - - -""" -`set_flow_control(sp::SerialPort [,rts::SPrts, cts::SPcts, dtr::SPdtr, dst::SPdsr, xonxoff::SPXonXoff])` - -Configure flow control settings. Many systems don't support all options. -If an unsupported option is requested, the library will return SP_ERR_SUPP. - -`rts` can take the values: `SP_RTS_OFF`, `SP_RTS_ON` and `SP_RTS_FLOW_CONTROL` -and defaults to `SP_RTS_OFF`. - -`cts` can take the values: `SP_CTS_IGNORE` and `SP_CTS_FLOW_CONTROL`. Its -default is `SP_CTS_IGNORE`. - -`dtr` can take the values: `SP_DTR_OFF`, `SP_DTR_ON`, and `SP_DTR_FLOW_CONTROL` -and defaults to `SP_DTR_OFF`. - -`dsr` can take the values: `SP_DSR_IGNORE` and `SP_DSR_FLOW_CONTROL`. Its -default is SP_DSR_IGNORE`. - -`xonxoff` can take values: `SP_XONXOFF_DISABLED`, `SP_XONXOFF_IN`, -`SP_XONXOFF_OUT`, and `SP_XONXOFF_INOUT` and defaults to `SP_XONXOFF_DISABLED`. -""" -function set_flow_control(sp::SerialPort; - rts::SPrts=SP_RTS_OFF, - cts::SPcts=SP_CTS_IGNORE, - dtr::SPdtr=SP_DTR_OFF, - dsr::SPdsr=SP_DSR_IGNORE, - xonxoff::SPXonXoff=SP_XONXOFF_DISABLED) - - # CTS and RTS must be enabled or disabled as a pair - sp_set_rts(sp.ref, rts) - sp_set_cts(sp.ref, cts) - - sp_set_dtr(sp.ref, dtr) - sp_set_dsr(sp.ref, dsr) - - sp_set_xon_xoff(sp.ref, xonxoff) - return nothing -end - - -""" -`listports([nports_guess::Integer])` - -Print a list of currently visible ports, along with some basic info. - -`nports_guess` provides the number of ports guessed. Its default is `64`. -""" -function list_ports(;nports_guess::Integer=64) - ports = sp_list_ports() - - for port in unsafe_wrap(Array, ports, nports_guess, own=false) - port == C_NULL && return - - println(sp_get_port_name(port)) - println("\tDescription:\t", sp_get_port_description(port)) - println("\tTransport type:\t", sp_get_port_transport(port)) - end - - sp_free_port_list(ports) - return nothing -end - - -""" -`get_port_list([nports_guess::Integer])` - -Return a vector of currently visible ports. - -`nports_guess` provides the number of ports guessed. Its default is `64`. -""" -function get_port_list(;nports_guess::Integer=64) - ports = sp_list_ports() - port_list = String[] - for port in unsafe_wrap(Array, ports, nports_guess, own=false) - port == C_NULL && return port_list - push!(port_list, sp_get_port_name(port)) - end - sp_free_port_list(ports) - return port_list -end - - -""" -`print_port_metadata(sp::SerialPort [,show_config::Bool]) - -Print info found for this port. -Note: port should be open to obtain a valid FD/handle before accessing fields. - -`show_config` is `true` by default and prints out the current port settings. -""" -function print_port_metadata(sp::SerialPort; show_config::Bool=true) - print_port_metadata(sp.ref, show_config=show_config) - return nothing -end - -function print_port_metadata(port::LibSerialPort.Port; show_config::Bool=true) - println("\nPort name:\t", sp_get_port_name(port)) - transport = sp_get_port_transport(port) - print("\nPort transport:\t"); - if transport == SP_TRANSPORT_NATIVE - println("native serial port") - elseif transport == SP_TRANSPORT_USB - println("USB") - println("Manufacturer:\t", sp_get_port_usb_manufacturer(port)) - println("Product:\t", sp_get_port_usb_product(port)) - println("USB serial number:\t", sp_get_port_usb_serial(port)) - bus, addr = sp_get_port_usb_bus_address(port) - println("USB bus #:\t", bus) - println("Address on bus:\t", addr) - vid, pid = sp_get_port_usb_vid_pid(port) - println("Vendor ID:\t", vid) - println("Product ID:\t", pid) - elseif transport == SP_TRANSPORT_BLUETOOTH - println("Bluetooth") - println("Bluetooth address:\t", sp_get_port_bluetooth_address(port)) - end - println("File descriptor:\t", sp_get_port_handle(port)) - - if show_config - print_port_settings(port) - end - return nothing -end - - -function get_port_settings(config::LibSerialPort.Config) - return Dict( - "baudrate" => sp_get_config_baudrate(config), - "bits" => sp_get_config_bits(config), - "parity" => sp_get_config_parity(config), - "stopbits" => sp_get_config_stopbits(config), - "RTS" => sp_get_config_rts(config), - "CTS" => sp_get_config_cts(config), - "DTR" => sp_get_config_dtr(config), - "DSR" => sp_get_config_dsr(config), - "XonXoff" => sp_get_config_xon_xoff(config), - ) -end - -function get_port_settings(port::LibSerialPort.Port) - config = sp_get_config(port) - settings = get_port_settings(config) - sp_free_config(config) - return settings -end - -""" -`get_port_settings(sp::SerialPort)` - -Return port settings for `sp` as a dictionary. -""" -get_port_settings(sp::SerialPort) = get_port_settings(sp.ref) - - -""" -`print_port_settings(sp::SerialPort)` - -Print port settings for `sp`. -""" -function print_port_settings(sp::SerialPort) - print_port_settings(sp.ref) - println("Read timeout (ms, forever if 0): ", sp.read_timeout_ms) - return -end - -function print_port_settings(port::LibSerialPort.Port) - println("Configuration for ", sp_get_port_name(port), ":") - config = sp_get_config(port) - print_port_settings(config) - sp_free_config(config) - return -end - -function print_port_settings(config::LibSerialPort.Config) - s = get_port_settings(config) - for (k, v) in s - println("\t$k\t", v) - end - return -end - - -""" -`open(sp::SerialPort [, mode::SPMode])` - -Open the serial port `sp`. - -`mode` can take the values: `SP_MODE_READ`, `SP_MODE_WRITE`, and -`SP_MODE_READ_WRITE` -""" -function open(sp::SerialPort; mode::SPMode=SP_MODE_READ_WRITE) - sp_open(sp.ref, mode) - sp.is_open = true - return sp -end - - -""" -`open(portname::AbstractString,baudrate::Integer [,mode::SPMode, - ndatabits::Integer,parity::SPParity,nstopbits::Integer])` - -construct, configure and open a `SerialPort` object. - -For details on posssible settings see `?set_flow_control` and `?set_frame`. -""" -function open(portname::AbstractString, - bps::Integer; - mode::SPMode=SP_MODE_READ_WRITE, - ndatabits::Integer=8, - parity::SPParity=SP_PARITY_NONE, - nstopbits::Integer=1) - sp = SerialPort(portname) - sp_open(sp.ref, mode) - sp.is_open = true - set_speed(sp, bps) - set_frame(sp, ndatabits=ndatabits, parity=parity, nstopbits=nstopbits) - return sp -end - - -""" -close(sp::SerialPort) - -Close the serial port `sp`. -""" -function close(sp::SerialPort) - if isopen(sp) - sp_close(sp.ref) - sp.is_open = false - end - return sp -end - -# fixes https://github.com/JuliaIO/LibSerialPort.jl/issues/53 -@deprecate flush(sp::SerialPort, buffer::SPBuffer=SP_BUF_BOTH) sp_flush(sp, buffer) - -# pass through some methods from the low-level interface -sp_output_waiting(sp::SerialPort) = sp_output_waiting(sp.ref) -sp_flush(sp::SerialPort, args...) = sp_flush(sp.ref, args...) -sp_drain(sp::SerialPort) = sp_drain(sp.ref) - -# We define here only the basic methods for reading and writing -# bytes. All other methods for reading/writing the canonical binary -# representation of any type, and print() methods for writing -# its text representation, are inherited from the IO supertype -# (see julia/base/io.jl), i.e. work just like for files. - -function read(sp::SerialPort, ::Type{UInt8}) - byte_ref = Ref{UInt8}(0) - nbytes = sp_blocking_read(sp.ref, byte_ref, 1, sp.read_timeout_ms) - - # TODO: how to handle timeouts? - # if nbytes == 0 - # println("read timeout") - # end - return byte_ref.x -end - - -function unsafe_read(sp::SerialPort, p::Ptr{UInt8}, nb::UInt) - sp_blocking_read(sp.ref, p, nb, sp.read_timeout_ms) -end - - -function write(sp::SerialPort, b::UInt8) - sp_blocking_write(sp.ref, Ref(b)) -end - - -function unsafe_write(sp::SerialPort, p::Ptr{UInt8}, nb::UInt) - sp_blocking_write(sp.ref, p, nb) -end - - -""" -`seteof(sp::SerialPort, state::Bool)` - -Set EOF of `sp` to `state` -""" -function seteof(sp::SerialPort, state::Bool) - sp.is_eof = state - return nothing -end - - -""" -`reseteof(sp::SerialPort, state::Bool)` - -Reset EOF of `sp` to `false` -""" -reseteof(sp::SerialPort) = seteof(sp, false) - - -# Julia's Base module defines `read(s::IO, nb::Integer = typemax(Int))`. -# Override the default `nb` to a more useful value for this context. -read(sp::SerialPort) = read(sp, bytesavailable(sp)) - - -""" -`nonblocking_read(sp::SerialPort)` - -Read everything from the specified serial ports `sp` input buffer, one byte at -a time, until it is empty. Returns a `String`. -""" -function nonblocking_read(sp::SerialPort) - result = UInt8[] - byte_ref = Ref{UInt8}(0) - while bytesavailable(sp) > 0 - sp_nonblocking_read(sp.ref, byte_ref, 1) - push!(result, byte_ref.x) - end - return result -end - - -""" -`bytesavailable(sp::SerialPort)` - -Gets the number of bytes waiting in the input buffer. -""" -bytesavailable(sp::SerialPort) = sp_input_waiting(sp.ref) - diff --git a/src/wrap.jl b/src/wrap.jl index e1d8899..677f05b 100644 --- a/src/wrap.jl +++ b/src/wrap.jl @@ -1,3 +1,164 @@ +""" +The `LibSerialPort.Lib` module provides a low-level Julia wrapper +around each of the C function, types and constants exported by the C +library [libserialport](http://sigrok.org/wiki/Libserialport). The +wrapper functions very closely follow the C API, but they check for +error codes returned and raise these as a Julia `ErrorException`. +""" +module Lib + +using Libdl +using libserialport_jll + +export + # Types + Port, + Config, + + # Enum types + SPReturn, + SPMode, + SPEvent, + SPBuffer, + SPParity, + SPrts, + SPcts, + SPdtr, + SPdsr, + SPXonXoff, + SPFlowControl, + SPSignal, + SPTransport, + + # Enum values + SP_OK, + SP_ERR_ARG, + SP_ERR_FAIL, + SP_ERR_MEM, + SP_ERR_SUPP, + SP_MODE_READ, + SP_MODE_WRITE, + SP_MODE_READ_WRITE, + SP_EVENT_RX_READY, + SP_EVENT_TX_READY, + SP_EVENT_ERROR, + SP_BUF_INPUT, + SP_BUF_OUTPUT, + SP_BUF_BOTH, + SP_PARITY_INVALID, + SP_PARITY_NONE, + SP_PARITY_ODD, + SP_PARITY_EVEN, + SP_PARITY_MARK, + SP_PARITY_SPACE, + SP_RTS_INVALID, + SP_RTS_OFF, + SP_RTS_ON, + SP_RTS_FLOW_CONTROL, + SP_CTS_INVALID, + SP_CTS_IGNORE, + SP_CTS_FLOW_CONTROL, + SP_DTR_INVALID, + SP_DTR_OFF, + SP_DTR_ON, + SP_DTR_FLOW_CONTROL, + SP_DSR_INVALID, + SP_DSR_IGNORE, + SP_DSR_FLOW_CONTROL, + SP_XONXOFF_INVALID, + SP_XONXOFF_DISABLED, + SP_XONXOFF_IN, + SP_XONXOFF_OUT, + SP_XONXOFF_INOUT, + SP_FLOWCONTROL_NONE, + SP_FLOWCONTROL_XONXOFF, + SP_FLOWCONTROL_RTSCTS, + SP_FLOWCONTROL_DTRDSR, + SP_SIG_CTS, + SP_SIG_DSR, + SP_SIG_DCD, + SP_SIG_RI, + SP_TRANSPORT_NATIVE, + SP_TRANSPORT_USB, + SP_TRANSPORT_BLUETOOTH, + + # Functions from libserialport API + sp_get_port_by_name, + sp_free_port, + sp_list_ports, + sp_copy_port, + sp_free_port_list, + sp_open, + sp_close, + sp_get_port_name, + sp_get_port_description, + sp_get_port_transport, + sp_get_port_usb_bus_address, + sp_get_port_usb_vid_pid, + sp_get_port_usb_manufacturer, + sp_get_port_usb_product, + sp_get_port_usb_serial, + sp_get_port_bluetooth_address, + sp_get_port_handle, + sp_new_config, + sp_free_config, + sp_get_config, + sp_set_config, + sp_set_baudrate, + sp_get_config_baudrate, + sp_set_config_baudrate, + sp_set_bits, + sp_get_config_bits, + sp_set_config_bits, + sp_set_parity, + sp_get_config_parity, + sp_set_config_parity, + sp_set_stopbits, + sp_get_config_stopbits, + sp_set_config_stopbits, + sp_set_rts, + sp_get_config_rts, + sp_set_config_rts, + sp_set_cts, + sp_get_config_cts, + sp_set_config_cts, + sp_set_dtr, + sp_get_config_dtr, + sp_set_config_dtr, + sp_set_dsr, + sp_get_config_dsr, + sp_set_config_dsr, + sp_set_xon_xoff, + sp_get_config_xon_xoff, + sp_set_config_xon_xoff, + sp_set_config_flowcontrol, + sp_set_flowcontrol, + sp_blocking_read, + sp_blocking_read_next, + sp_nonblocking_read, + sp_blocking_write, + sp_nonblocking_write, + sp_input_waiting, + sp_output_waiting, + sp_flush, + sp_drain, + sp_new_event_set, + sp_add_port_events, + sp_wait, + sp_free_event_set, + sp_get_signals, + sp_start_break, + sp_end_break, + sp_last_error_code, + sp_last_error_message, + sp_get_major_package_version, + sp_get_minor_package_version, + sp_get_micro_package_version, + sp_get_package_version_string, + sp_get_current_lib_version, + sp_get_revision_lib_version, + sp_get_age_lib_version, + sp_get_lib_version_string # Julia types for the sp_port, sp_port_config, and sp_event_set structs struct SPPort end @@ -825,7 +986,7 @@ end # enum sp_return sp_input_waiting(struct sp_port *port); """ -Returns the number of bytes in the input buffer or an error code. +Returns the number of bytes in the input buffer. """ function sp_input_waiting(port::Port) check(ccall((:sp_input_waiting, libserialport), SPReturn, (Port,), port)) @@ -833,7 +994,7 @@ end # enum sp_return sp_output_waiting(struct sp_port *port); """ -Returns the number of bytes in the output buffer or an error code. +Returns the number of bytes in the output buffer. """ function sp_output_waiting(port::Port) check(ccall((:sp_output_waiting, libserialport), SPReturn, (Port,), port)) @@ -844,11 +1005,19 @@ end sp_flush(port::Port, buffers::SPBuffer) sp_flush(port::SerialPort, buffers::SPBuffer) -Flush serial port buffers. Data in the selected buffer(s) is discarded. +Discard data in the selected serial-port buffer(s). Supported values for `buffers`: `SP_BUF_INPUT`, `SP_BUF_OUTPUT`, `SP_BUF_BOTH` -Returns SP_OK upon success or raises an `ErrorException` otherwise. +Returns `SP_OK` upon success or raises an `ErrorException` otherwise. + +!!! note + + Not to be confused with `Base.flush`, which writes out buffered + data rather than discarding it: the underlying `libserialport` C + library unfortunately uses the verb “flush” differently from its + normal meaning for `Base.IO` (`sp_drain` provides the latter + in this library). """ function sp_flush(port::Port, buffers::SPBuffer) check(ccall((:sp_flush, libserialport), SPReturn, (Port, SPBuffer), port, buffers)) @@ -996,3 +1165,5 @@ function sp_get_lib_version_string() ver = ccall((:sp_get_lib_version_string, libserialport), Ptr{UInt8}, ()) unsafe_string(ver) end + +end # module wrap diff --git a/test/runtests.jl b/test/runtests.jl index 0aa8ce6..a93221c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -14,6 +14,7 @@ LibSerialPort.list_ports() may help with finding the correct port address. =# using LibSerialPort +using LibSerialPort.Lib using Test if haskey(ENV, "CI") diff --git a/test/test-low-level-api.jl b/test/test-low-level-api.jl index 136eb67..ba034ea 100644 --- a/test/test-low-level-api.jl +++ b/test/test-low-level-api.jl @@ -131,8 +131,7 @@ end This example demonstrates serial communication with one port. The default configuration is 115200-8-N-1, i.e. 115200 bps with 8 data bits, no parity check, and one stop bit. The baud rate is overridden on the command line with a -second argument. Hardware and software flow control measures are disabled by -default. +second argument. Hardware and software flow control measures are disabled. """ function test_low_level_api(args...) @@ -159,6 +158,7 @@ function test_low_level_api(args...) sp_set_baudrate(port, baudrate) sp_set_bits(port, 8) + sp_set_flowcontrol(port, SP_FLOWCONTROL_NONE) sleep(2)