bmitc
Using Circuits.UART, how do you read a specific amount of bytes?
For Circuits.UART, I am looking for a way to read a specific amount of bytes where the number of bytes to read may vary from call to call. This is a common use case needed for when certain messages return a specific amount of bytes in the response but the number of bytes in the response may differ from message to message.
For example, I expected to be able to do something like this:
iex> {:ok, pid} = Circuits.UART.start_link
iex> Circuits.UART.open(pid, "COM2", speed: 115200, active: false)
iex> Circuits.UART.write(pid, "some message expecting 3 bytes in response\r\n")
iex> {:ok, three_byte_response} = Circuits.UART.read(pid, 3, 3000)
iex> Circuits.UART.write(pid, "some message expecting 5 bytes in response\r\n")
iex> {:ok, five_byte_response} = Circuits.UART.read(pid, 5, 3000)
This isn’t possible right now because the read/2 of course doesn’t have a number of bytes to read parameter. I originally expected there to be something like read/3 which would accept a specific number of bytes to read.
It seems a possible alternative is to create a framer behaviour, but that has two downsides: (1) the data isn’t actually framed, (2) this would require constantly calling configure like Circuits.UART.configure(pid, number_of_bytes: rx_framing_timeout: 500) before every write and read pair. I’m not entirely sure of the consequences of doing (2), but it’s the only possible way I could see doing this with the framing feature (if it would even work).
I think the Circuits.UART module should have a read/3 function like read(pid, bytes_to_read, timeout). This is quite common in the serial communication world:
So I’m curious, how have people handled this with Circuits.UART?
I asked this question a while back on the Circuits GitHub repo, but got a non-answer.
First Post!
Sebb
I do not use circuits, but from the docs it seems, that this is not supported. So you either have to change the other side to a framed protocol or change circuits.uart or write your own - which is not too hard if you do not aim to be as comfortable as circuits.uart.
Open a Port — Elixir v1.12.3 and just take the bytes from the uart and write them to stdout (and vice versa).
I did this for a project of mine and it seems to work but its not battle tested. If you feel up to it I could make the code public for you to get it.
Note: I use a framed protocol, so you’d have to add the functionality you need. But my uart-adapter has just 200loc and should be easy to handle.
To give an idea how this works:
...
static void
forward(int const from, int const to, int const err_code)
{
static char buf[BUFSIZ];
ssize_t const size = read(from, buf, BUFSIZ);
require(size > 0, err_code + ERR_READ_ERROR_);
ssize_t const write_size = write(to, buf, size);
require(write_size == size, err_code + ERR_WRITE_ERROR_);
}
int
main(int argc, char* argv[])
{
f_uart = config_uart(argc, argv);
int const maxfd = f_uart + 1;
fd_set fds;
for (;;) {
FD_ZERO(&fds);
FD_SET(f_uart, &fds);
FD_SET(STDIN_FILENO, &fds);
select(maxfd, &fds, NULL, NULL, NULL);
if (FD_ISSET(f_uart, &fds)) {
forward(f_uart, STDOUT_FILENO, ERR_FWD_STDOUT_FAILED_);
}
if (FD_ISSET(STDIN_FILENO, &fds)) {
forward(STDIN_FILENO, f_uart, ERR_FWD_STDIN_FAILED_);
}
}
and on the Elixir side:
def handle_info({_port, {:data, data}}, state = %{subscriber: subscriber}) do
if subscriber do
send(subscriber, {:data, data})
end
{:noreply, state}
end
...
@impl true
def handle_cast({:write, data}, %{port: port} = state) do
Port.command(port, data)
{:noreply, state}
end
...







