Current section
Files
Jump to
Current section
Files
lib/excansock.ex
defmodule Excansock do
use GenServer
import Bitwise
import Excansock.CanConstants
defmodule State do
defstruct [
socket: -1,
controlling_process: nil,
queue_size: 0,
use_queue: false,
queue: nil
]
@type t() :: %__MODULE__{
socket: number,
controlling_process: pid | nil,
queue_size: number,
use_queue: boolean,
queue: any
}
end
defmodule CanFrame do
@enforce_keys [:id, :data]
defstruct [
id: nil,
data: <<>>
]
@type t() :: %__MODULE__{
id: number,
data: binary
}
end
defmodule CanFilter do
@enforce_keys [:can_id, :can_mask]
defstruct [
can_id: 0,
can_mask: 0
]
@type t() :: %__MODULE__{
can_id: number,
can_mask: number
}
end
@doc """
Start up Excansock GenServer.
"""
@spec start_link([term]) :: {:ok, pid} | {:error, term}
def start_link(opts \\ []) do
queue_size = Keyword.get(opts, :queue_size, 0)
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, {queue_size}, name: name)
end
@doc """
Open CAN socket.
"""
@spec open(GenServer.server(), binary, boolean) :: :ok | :error
def open(pid, name, canfd \\ false) do
GenServer.call(pid, {:open, name, canfd})
end
@doc """
Close CAN socket.
"""
@spec close(GenServer.server()) :: :ok | :error
def close(pid) do
GenServer.call(pid, :close)
end
@doc """
Send CAN message
"""
@spec send(GenServer.server(), CanFrame.t) :: :ok
def send(pid, frame) do
GenServer.call(pid, {:send, frame})
end
@doc """
Enable / disable CAN loopback
"""
@spec set_loopback(GenServer.server(), boolean) :: :ok
def set_loopback(pid, value) do
GenServer.call(pid, {:set_loopback, value})
end
@doc """
Enable / disable receiving own messages
"""
@spec recv_own_messages(GenServer.server(), boolean) :: :ok
def recv_own_messages(pid, value) do
GenServer.call(pid, {:recv_own_messages, value})
end
@doc """
Set CAN filters
"""
@spec set_filters(GenServer.server(), list(CanFilter.t)) :: :ok
def set_filters(pid, filters) do
GenServer.call(pid, {:set_filters, filters})
end
@doc """
Set bus error filter
"""
@spec set_error_filter(GenServer.server(), integer) :: :ok
def set_error_filter(pid, filter) do
GenServer.call(pid, {:set_error_filter, filter})
end
@impl true
@spec init({number}) :: {:ok, Excansock.State.t()}
def init({queue_size}) do
{:ok, %State{ queue_size: queue_size, queue: :queue.new(), use_queue: queue_size == :infinity || queue_size > 0 }}
end
@impl true
def handle_call({:open, name, canfd}, {from_pid, _}, state = %State{}) do
if(state.socket != -1) do
{:reply, {:error, :ebound}, state }
else
case Excansock.Nif.excansock_open(name, if(canfd, do: 1, else: 0)) do
{:ok, socket} ->
GenServer.cast(self(), :receive)
{:reply, :ok, %{ state | socket: socket, controlling_process: from_pid } }
{:error, error} -> {:reply, {:error, error}, state }
end
end
end
@impl true
def handle_call(:close, _, state = %State{}) do
Excansock.Nif.excansock_close(state.socket)
{:reply, :ok, %State{}}
end
@impl true
def handle_call({:send, frame = %CanFrame{}}, _, state = %State{use_queue: true}) do
cond do
state.queue_size != :infinity && :queue.len(state.queue) >= state.queue_size -> {:reply, :full, state}
:queue.len(state.queue) > 0 -> {:reply, :ok, %{state | queue: :queue.in(frame, state.queue)}}
true ->
case Excansock.Nif.excansock_send_try(state.socket, frame.id, frame.data) do
:ok -> {:reply, :ok, state}
:eagain -> {:reply, :ok, %{state | queue: :queue.in(frame, state.queue)}}
end
end
end
@impl true
def handle_call({:send, frame = %CanFrame{}}, _, state = %State{use_queue: false}) do
case Excansock.Nif.excansock_send_try(state.socket, frame.id, frame.data) do
:ok -> {:reply, :ok, state}
:eagain -> {:reply, :full, state}
end
end
@impl true
def handle_call({:recv_own_messages, enabled}, _, state = %State{}) do
value = if enabled, do: 1, else: 0
:ok = Excansock.Nif.excansock_recv_own_messages(state.socket, value)
{:reply, :ok, state}
end
@impl true
def handle_call({:set_loopback, enabled}, _, state = %State{}) do
value = if enabled, do: 1, else: 0
:ok = Excansock.Nif.excansock_set_loopback(state.socket, value)
{:reply, :ok, state}
end
@impl true
def handle_call({:set_filters, fs}, _, state = %State{}) do
bytes = for(f <- fs, into: [], do: {f.can_id, f.can_mask})
:ok = Excansock.Nif.excansock_set_filters(state.socket, bytes)
{:reply, :ok, state}
end
@impl true
def handle_call({:set_error_filter, mask}, _, state = %State{}) do
:ok = Excansock.Nif.excansock_set_error_filter(state.socket, mask)
{:reply, :ok, state}
end
@impl true
def handle_cast(:receive, state) do
receive_frame(state)
{:noreply, state}
end
@impl true
def handle_info({:select, _socket, _ref, :ready_input}, state) do
{:noreply, receive_frame(state)}
end
@impl true
def handle_info({:select, _socket, _ref, :ready_output}, state) do
{:noreply, send_frame(state)}
end
defp send_frame(state = %State{}) do
case :queue.out(state.queue) do
{:empty, _} -> state
{{:value, frame}, queue_n} ->
case Excansock.Nif.excansock_send_try(state.socket, frame.id, frame.data) do
:eagain -> state
:ok -> send_frame(%{state | queue: queue_n})
end
end
end
defp receive_frame(state = %State{}) do
case Excansock.Nif.excansock_recv_try(state.socket) do
:eagain ->
state
{:can_frame, can_id, data} ->
process_incoming_frame(can_id, data, state)
receive_frame(state)
end
end
defp process_incoming_frame(id, data, state = %State{}) do
can_frame = %CanFrame{
id: id,
data: data,
}
cond do
(can_frame.id &&& canERR_FLAG()) > 0 ->
Kernel.send(state.controlling_process, {:can_error_frame, %{ can_frame | id: can_frame.id &&& bnot(canERR_FLAG()) }})
(can_frame.id &&& canRTR_FLAG()) > 0 ->
Kernel.send(state.controlling_process, {:can_rtr_frame, %{ can_frame | id: can_frame.id &&& bnot(canRTR_FLAG()) }})
(can_frame.id &&& canEFF_FLAG()) > 0 ->
Kernel.send(state.controlling_process, {:can_extended_frame, %{ can_frame | id: can_frame.id &&& bnot(canEFF_FLAG()) }})
true ->
Kernel.send(state.controlling_process, {:can_data_frame, can_frame})
end
{:noreply, state}
end
end