Skip to content

Commit 7ffc76e

Browse files
committed
Add read with timeout to uart driver
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 <winford@object.stream>
1 parent 518d18e commit 7ffc76e

File tree

3 files changed

+61
-1
lines changed

3 files changed

+61
-1
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ with nodejs and emscripten)
1313
- Added preliminary support for ESP32P4 (no networking support yet).
1414
- Added memory info in `out_of_memory` crash logs to help developers fix memory issues.
1515
- Added documentation and function specs for uart driver
16+
- Added `uart:read/2` with a timeout parameter.
1617

1718
### Fixed
1819

libs/eavmlib/src/uart.erl

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
%
2020

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

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

107+
%%-----------------------------------------------------------------------------
108+
%% @param Pid of the uart port to be read
109+
%% @param Timeout millisecond to wait for data to become available
110+
%% @returns `{ok, Data}', or `{error, Reason}'
111+
%% @doc Read data from a UART port
112+
%%
113+
%% This function will return any data that is available within the
114+
%% timeout period to the process. After the timeout has expired a new
115+
%% read command may be used regardless of whether the last read was
116+
%% sent a payload.
117+
%% Example:
118+
%% ```
119+
%% Data = case uart:read(Uart, 3000) of
120+
%% {ok, Bin} -> Bin;
121+
%% {error, timeout} -> <<"">>;
122+
%% Error -> error_handler_fun(Uart, Error)
123+
%% end,
124+
%% '''
125+
%% Any data sent to the esp32 over uart between reads with a timeout will
126+
%% be lost, so be sure this is what you want. Most applications will want
127+
%% a single process to read from UART and continue to listen until a payload
128+
%% is received, and likely pass the payload off for processing and
129+
%% immediately begin another read.
130+
%% @end
131+
%%-----------------------------------------------------------------------------
132+
-spec read(Pid :: pid(), Timeout :: pos_integer()) ->
133+
{ok, Data :: iodata()} | {error, _Reason :: term()}.
134+
read(Pid, Timeout) when is_pid(Pid) ->
135+
case port:call(Pid, read, Timeout) of
136+
{error, timeout} ->
137+
port:call(Pid, cancel_read),
138+
{error, timeout};
139+
Result ->
140+
Result
141+
end.
142+
107143
%%-----------------------------------------------------------------------------
108144
%% @param Pid of the uart port to be written to
109145
%% @param Data to be written to the given uart port

src/platforms/esp32/components/avm_builtins/uart_driver.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ static const AtomStringIntPair cmd_table[] = {
103103
{ ATOM_STR("\x4", "read"), UARTReadCmd },
104104
{ ATOM_STR("\x5", "write"), UARTWriteCmd },
105105
{ ATOM_STR("\x5", "close"), UARTCloseCmd },
106+
{ ATOM_STR("\xB", "cancel_read"), UARTCancelCmd },
106107
SELECT_INT_DEFAULT(UARTInvalidCmd)
107108
};
108109

@@ -394,6 +395,23 @@ static void uart_driver_do_read(Context *ctx, GenMessage gen_message)
394395
}
395396
}
396397

398+
static void uart_driver_do_cancel_read(Context *ctx, GenMessage gen_message)
399+
{
400+
struct UARTData *uart_data = ctx->platform_data;
401+
402+
safe_update_reader_data(uart_data, NO_READER, NO_REF);
403+
404+
term pid = gen_message.pid;
405+
term ref = gen_message.ref;
406+
407+
if (UNLIKELY(memory_ensure_free_with_roots(ctx, TUPLE_SIZE(2), 1, &ref, MEMORY_CAN_SHRINK) != MEMORY_GC_OK)) {
408+
ESP_LOGE(TAG, "[uart_driver_do_read] Failed to allocate space for return value");
409+
globalcontext_send_message(ctx->global, term_to_local_process_id(pid), OUT_OF_MEMORY_ATOM);
410+
}
411+
412+
port_send_reply(ctx, pid, ref, OK_ATOM);
413+
}
414+
397415
static void uart_driver_do_write(Context *ctx, GenMessage gen_message)
398416
{
399417
GlobalContext *glb = ctx->global;
@@ -533,6 +551,11 @@ static NativeHandlerResult uart_driver_consume_mailbox(Context *ctx)
533551
is_closed = true;
534552
break;
535553

554+
case UARTCancelCmd:
555+
TRACE("cancel_read\n");
556+
uart_driver_do_cancel_read(ctx, gen_message);
557+
break;
558+
536559
default:
537560
TRACE("uart: error: unrecognized command.\n");
538561
}

0 commit comments

Comments
 (0)