I’m trying to plot data coming from the serial port in real-time. Not sure if how I’m doing it the correct approach but this is what I did. I created this module that I call SerialPortReader.jl the code for it is as follows:
"""
SerialPortReader
This module provides functionality to read data from a serial port,
parse it and pass it to a user-specified function for further processing.
"""
module SerialPortReader
# Import serial port package
using LibSerialPort
# Flag to control data reading
global continue_reading = false
"""
read_serial_data(ch::Channel, portname::String, baudrate::Int)
Reads data from the serial port specified by `portname` at the given `baudrate`,
splits the incoming string into substrings, drops the first element and puts the
rest into the passed `Channel` `ch`.
# Arguments
- `ch`: Channel where the read data is put (Channel)
- `portname`: Name of the serial port (String)
- `baudrate`: Baud rate for the serial communication (Int)
# Returns
- `nothing`
"""
function read_serial_data(ch::Channel, portname::String, baudrate::Int)
try
LibSerialPort.open(portname, baudrate) do sp
while (continue_reading)
if bytesavailable(sp) > 0
data = readuntil(sp, '\n')
data_str = String(data)
str_values = split(data_str) # Split the string into an array of substrings
str_values = str_values[2:end] # Drop the first element
put!(ch, str_values) # Put the data line into the channel
end
sleep(0.1) # Pause to prevent high CPU usage
end
end
catch e
println("Could not open port $portname: $e") # Error handling
end
end
"""
start_reading(portname::String, baudrate::Int; print_data_to_console::Bool=false)
Starts reading data from the serial port specified by `portname` at the given `baudrate`.
This function starts two asynchronous tasks: one for reading the data from the serial port
and another for processing the data lines. If `print_data_to_console` is set to true, the
read data is printed to the console.
# Arguments
- `portname`: Name of the serial port (String)
- `baudrate`: Baud rate for the serial communication (Int)
- `print_data_to_console`: If set to true, the read data is printed to the console. (Bool, default=false)
# Returns
- A Channel that the read data is pushed into
"""
function start_reading(portname::String, baudrate::Int; print_data_to_console::Bool=false)
global continue_reading = true
ch = Channel{Array{SubString{String},1}}(Inf) # Create a channel to store the data lines
data_ch = Channel{Array{SubString{String},1}}(Inf) # Create a channel to return the data
@async read_serial_data(ch, portname, baudrate) # Run read_serial_data in a separate task
@async while continue_reading # Process the data lines in another task
data_line = take!(ch) # Take a data line from the channel
if print_data_to_console
println(data_line) # print the data line to the console
end
put!(data_ch, data_line) # put the data line into the data channel
end
return data_ch # return the data channel
end
"""
stop_reading()
Stops reading data from the serial port.
# Returns
- `nothing`
"""
function stop_reading()
global continue_reading = false
end
# Export the functions
export start_reading, stop_reading
end
In a second script that I call test_script.jl I call the start_reading function from SerialPortReader.jl, parse the data, add a timestamp and output it in the REPL to just to make sure everything it working. so far so good. See the code for test_scripts.jl below:
# Load packages that will be used
using GLMakie
using Observables
using DataStructures: CircularBuffer
include("SerialPortReader.jl")
using .SerialPortReader # Import your SerialPortReader module
using Dates
# Define a global variable to control the viewing process
global continue_viewing = true
"""
parse_data(data::Array{SubString{String},1})
Parse the data read from the serial port. This function needs to be implemented.
# Arguments
- `data`: Data read from the serial port (Array{SubString{String},1})
# Returns
- Parsed data
"""
function parse_data(data::Array{SubString{String},1})
# Remove any non-numeric strings
numeric_data = filter(x -> isdigit(x[1]) || x[1] == '.', data)
# Convert each string in the array to a float
float_data = map(x -> parse(Float64, x), numeric_data)
# If there are more than five elements, keep only the last five
if length(float_data) > 5
float_data = float_data[end-3:end]
end
return float_data
end
"""
view_data(portname::String, baudrate::Int)
Starts reading data from the serial port, parses the data, adds an elapsed time column in seconds,
and prints the data to the console.
# Arguments
- `portname`: Name of the serial port (String)
- `baudrate`: Baud rate for the serial communication (Int)
# Returns
- `nothing`
"""
function view_data(portname::String, baudrate::Int)
global continue_viewing = true
data_ch = SerialPortReader.start_reading(portname, baudrate)
start_time = now()
@async while continue_viewing
data = take!(data_ch)
parsed_data = parse_data(data)
elapsed_time = (now() - start_time).value / 1000
parsed_data = [elapsed_time; parsed_data]
@show parsed_data
end
end
"""
stop_viewing()
Stops the data viewing process.
# Returns
- `nothing`
"""
function stop_viewing()
global continue_viewing = false
SerialPortReader.stop_reading()
end
# Call the functions
view_data("COM12", 9600)
sleep(15)
stop_viewing()
When I execute test_scripts.jl I get this output in the REPL which is what I expect.
parsed_data = [0.621, 52.1, 23.4, 74.12, 23.16, 73.68]
parsed_data = [2.575, 52.1, 23.4, 74.12, 23.16, 73.68]
parsed_data = [4.638, 52.0, 23.4, 74.12, 23.15, 73.68]
parsed_data = [6.59, 51.9, 23.4, 74.12, 23.15, 73.67]
parsed_data = [8.643, 51.9, 23.4, 74.12, 23.15, 73.67]
parsed_data = [10.599, 51.8, 23.4, 74.12, 23.15, 73.67]
parsed_data = [12.654, 51.8, 23.4, 74.12, 23.15, 73.67]
parsed_data = [14.598, 51.8, 23.4, 74.12, 23.15, 73.67]
false
julia>
Where I’m getting stuck is how to plot lets say the first and second element of parse_data within the view_data function in real-time using Makie. I’ve attempted the CircularBuffer route and the Dataframes route in combination with Observables. I tried using Channels, but all ended in failure. Unfortunately, I’ve failed too many ways to post here, so I’ve decided to start with where things are working and see if someone could provide me with at least a hint that could point me in the right direction.