elixir-circuits / circuits_uart

Discover and use UARTs and serial ports in Elixir
Apache License 2.0
188 stars 48 forks source link

[Question] Is it possible to read a specific but dynamic amount of bytes? #95

Closed bmitc closed 3 years ago

bmitc commented 3 years ago

Setup

Expected Behavior

A function to read a specified number of bytes, where the read terminates once the number of bytes has been read or a timeout occurs. The number of bytes to be read may differ between calls.

Is there a way to do this with Circuits.UART? This is a common use case, so I may be missing something.

Thanks!

Actual Behavior

Steps to Reproduce the Problem

fhunleth commented 3 years ago

I typically use the framer feature so that whatever is received by the app (synchronously or asynchronously via message passing) is self-contained.

bmitc commented 3 years ago

Thanks for the response. I already knew about the framing, but I am still unsure how to use it to get the dynamic byte read behavior I'm looking for. I'm newish to Elixir and haven't done behaviours before. I'll try to use the FourByte example as a starting point, but will it be possible to configure the framing to "frame" different amounts of bytes? If I understand correctly, that could be awkward because it means there will be a need to call configure before every read.

Why was this closed so quickly? I don't see a clear resolution. I think there's multiple gaps here.

  1. Every serial library I have ever used has a built-in read for a specific number of bytes. That includes Python's PySerial, .NET's SerialPort class, and the industry standard VISA specification and its implementations, such as NI-VISA. I think this library would be greatly improved if there was a read/3 implementation that allowed setting the number of bytes to read in addition to a timeout.

  2. Improved documentation or examples, especially since this is a very common use case for serial protocols that return a dynamic amount of bytes depending upon the message sent.

  3. If a read/3 can't be implemented as in (1), I think a DynamicByte framer could be included with the library, which I'd be happy to help work on once I get things understood.

bmitc commented 2 years ago

@fhunleth I'd like to revisit this if possible. I'm looking to get back into a side-project of mine to test out the viability of Elixir Circuits and Nerves, and I need this functionality. I posted on the Elixir Forum recently but didn't get an answer, so I thought I would ask here again. If you could provide some detail, I would greatly appreciate it. :)

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 could have a read/3 function like read(pid, bytes_to_read, timeout). This is quite common in the serial communication world:

I tried looking through the code to see how read/2 is currently implemented but didn't dig into the C code. I also looked through the GenServer but didn't quite understand the ramifications of calling configure before every read.

Thanks for any help!