defmodule Ecall.Framing do @behaviour Circuits.UART.Framing @moduledoc """ Based on Circuits.UART.Framing.Line, the difference is there is one separator for sending data and another for receiving """ defmodule State do @moduledoc false defstruct max_length: nil, in_separator: nil, out_separator: nil, processed: <<>>, in_process: <<>> end def init(args) do max_length = Keyword.get(args, :max_length, 4096) state = %State{max_length: max_length, in_separator: "\r\n", out_separator: "\r"} {:ok, state} end def add_framing(data, state) do {:ok, data <> state.out_separator, state} end def remove_framing(data, state) do {new_processed, new_in_process, lines} = process_data( state.in_separator, byte_size(state.in_separator), state.max_length, state.processed, state.in_process <> data, [] ) new_state = %{state | processed: new_processed, in_process: new_in_process} rc = if buffer_empty?(new_state), do: :ok, else: :in_frame {rc, lines, new_state} end def frame_timeout(state) do partial_line = {:partial, state.processed <> state.in_process} new_state = %{state | processed: <<>>, in_process: <<>>} {:ok, [partial_line], new_state} end def flush(direction, state) when direction == :receive or direction == :both do %{state | processed: <<>>, in_process: <<>>} end def flush(:transmit, state) do state end def buffer_empty?(%State{processed: <<>>, in_process: <<>>}), do: true def buffer_empty?(_state), do: false # Handle not enough data case defp process_data(_separator, sep_length, _max_length, processed, to_process, lines) when byte_size(to_process) < sep_length do {processed, to_process, lines} end # Process data until separator or next char defp process_data(separator, sep_length, max_length, processed, to_process, lines) do case to_process do # Handle separater <<^separator::binary-size(sep_length), rest::binary>> -> new_lines = lines ++ [processed] process_data(separator, sep_length, max_length, <<>>, rest, new_lines) # Handle line too long case to_process when byte_size(processed) == max_length and to_process != <<>> -> new_lines = lines ++ [{:partial, processed}] process_data(separator, sep_length, max_length, <<>>, to_process, new_lines) # Handle next char <> -> process_data(separator, sep_length, max_length, processed <> next_char, rest, lines) end end end