Skip to content

Commit

Permalink
Add read with timeout to uart driver
Browse files Browse the repository at this point in the history
Adds a new uart:read/2 that takes a timeout parameter for reads. If no data is received during the
timeout period `timeout` is returned, and the listener is removed allowing for another read without
getting the `{error, ealready}` error tuple.

Closes #1446

Signed-off-by: Winford <[email protected]>
  • Loading branch information
UncleGrumpy committed Feb 3, 2025
1 parent ca39782 commit 54b5195
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Added the ability to run beams from the CLI for Generic Unix platform (it was already possible with nodejs and emscripten).
- Added preliminary support for ESP32P4 (no networking support yet).
- Added `uart:read/2` with a timeout parameter.

### Fixed

Expand Down
38 changes: 37 additions & 1 deletion libs/eavmlib/src/uart.erl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
%

-module(uart).
-export([open/1, open/2, close/1, read/1, write/2]).
-export([open/1, open/2, close/1, read/1, read/2, write/2]).

-type peripheral() :: string() | binary().
% The peripheral `Name' may be one of: `"UART0"' | `"UART1"' | `"UART2"' | `<<"UART0">>' | `<<"UART1">>' | `<<"UART2">>'.
Expand Down Expand Up @@ -104,6 +104,42 @@ close(Pid) when is_pid(Pid) ->
read(Pid) when is_pid(Pid) ->
port:call(Pid, read).

%%-----------------------------------------------------------------------------
%% @param Pid of the uart port to be read
%% @param Timeout millisecond to wait for data to become available
%% @returns `{ok, Data}', `timeout', or `{error, Reason}'
%% @doc Read data from a UART port
%%
%% This function will return any data that is available within the
%% timeout period to the process. After the timeout has expired a new
%% read command may be used regardless of whether the last read was
%% sent a payload.
%% Example:
%% ```
%% Data = case uart:read(Uart, 3000) of
%% {ok, Bin} -> Bin;
%% timeout -> <<"">>;
%% Error -> error_handler_fun(Uart, Error)
%% end,
%% '''
%% Any data sent to the esp32 over uart between reads with a timeout will
%% be lost, so be sure this is what you want. Most applications will want
%% a single process to read from UART and continue to listen until a payload
%% is received, and likely pass the payload off for processing and
%% immediately begin another read.
%% @end
%%-----------------------------------------------------------------------------
-spec read(Pid :: pid(), Timeout :: integer()) ->
{ok, Data :: iodata()} | {error, _Reason :: term()}.
read(Pid, Timeout) when is_pid(Pid) ->
case port:call(Pid, read, Timeout) of
{error, timeout} ->
port:call(Pid, cancel_read),
timeout;
Result ->
Result
end.

%%-----------------------------------------------------------------------------
%% @param Pid of the uart port to be written to
%% @param Data to be written to the given uart port
Expand Down
22 changes: 21 additions & 1 deletion src/platforms/esp32/components/avm_builtins/uart_driver.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,15 @@ enum uart_cmd
UARTInvalidCmd = 0,
UARTReadCmd = 1,
UARTWriteCmd = 2,
UARTCloseCmd = 3
UARTCloseCmd = 3,
UARTCancelCmd = 4
};

static const AtomStringIntPair cmd_table[] = {
{ ATOM_STR("\x4", "read"), UARTReadCmd },
{ ATOM_STR("\x5", "write"), UARTWriteCmd },
{ ATOM_STR("\x5", "close"), UARTCloseCmd },
{ ATOM_STR("\xB", "cancel_read"), UARTCancelCmd },
SELECT_INT_DEFAULT(UARTInvalidCmd)
};

Expand Down Expand Up @@ -347,6 +349,19 @@ static void uart_driver_do_read(Context *ctx, GenMessage gen_message)
}
}

static void uart_driver_do_cancel_read(Context *ctx, GenMessage gen_message)
{
struct UARTData *uart_data = ctx->platform_data;

uart_data->reader_process_pid = term_invalid_term();
uart_data->reader_ref_ticks = 0;

term pid = gen_message.pid;
term ref = gen_message.ref;

port_send_reply(ctx, pid, ref, OK_ATOM);
}

static void uart_driver_do_write(Context *ctx, GenMessage gen_message)
{
GlobalContext *glb = ctx->global;
Expand Down Expand Up @@ -482,6 +497,11 @@ static NativeHandlerResult uart_driver_consume_mailbox(Context *ctx)
is_closed = true;
break;

case UARTCancelCmd:
TRACE("cancel_read\n");
uart_driver_do_cancel_read(ctx, gen_message);
break;

default:
TRACE("uart: error: unrecognized command.\n");
}
Expand Down

0 comments on commit 54b5195

Please sign in to comment.