Current section
Files
Jump to
Current section
Files
src/cangaroo.erl
-module(cangaroo).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/cangaroo.gleam").
-export([start_link/1, close/1, send/2, set_loopback/2, recv_own_messages/2, set_filters/2, set_error_filter/2]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" cangaroo - Gleam bindings for SocketCAN communication on Linux.\n"
"\n"
" This module provides the public API for interacting with CAN bus\n"
" interfaces through the Excansock Elixir library. It manages CAN socket\n"
" connections using OTP actors.\n"
"\n"
" cangaroo attempts to provide a simple, idiomatic Gleam API. \n"
" Each CAN client runs as an OTP actor that\n"
" automatically receives incoming CAN frames and forwards them to your\n"
" application via a process subject.\n"
).
-file("src/cangaroo.gleam", 24).
-spec start() -> {ok, cangaroo@types:can_socket()} |
{error, cangaroo@errors:can_error()}.
start() ->
{Status, Pid} = 'Elixir.Excansock':start_link(),
case erlang:atom_to_binary(Status) of
<<"ok"/utf8>> ->
{ok, cangaroo@types:new_socket(Pid)};
Other_msg ->
{error, {start_link_error, Other_msg}}
end.
-file("src/cangaroo.gleam", 32).
-spec open(cangaroo@types:can_socket(), binary()) -> {ok, nil} |
{error, cangaroo@errors:can_error()}.
open(Socket, Interface) ->
Raw = 'Elixir.Excansock':open(
cangaroo@types:socket_pid(Socket),
Interface,
false
),
cangaroo@internal:decode_result(Raw).
-file("src/cangaroo.gleam", 90).
-spec error_to_string(cangaroo@errors:can_error()) -> binary().
error_to_string(Err) ->
case Err of
{start_link_error, Reason} ->
Reason;
{interface_bound_error, Interface} ->
<<"interface_bound:"/utf8, Interface/binary>>;
{unknown_error, Reason@1} ->
Reason@1;
invalid_message_type_error ->
<<"invalid_message_type"/utf8>>
end.
-file("src/cangaroo.gleam", 99).
-spec handle_message(
cangaroo@types:can_client(),
cangaroo@types:actor_message()
) -> gleam@otp@actor:next(cangaroo@types:can_client(), cangaroo@types:actor_message()).
handle_message(Client, Msg) ->
case Msg of
{raw_can_data, Raw_data} ->
Frame = bridge_ffi:unwrap_frame(Raw_data),
Id = bridge_ffi:get_id(Frame),
Data = bridge_ffi:get_data(Frame),
Gleam_frame = {can_frame, Id, Data},
gleam@erlang@process:send(
cangaroo@types:messages(Client),
Gleam_frame
),
gleam@otp@actor:continue(Client);
shutdown ->
Socket = cangaroo@types:client_socket(Client),
_ = 'Elixir.Excansock':close(cangaroo@types:socket_pid(Socket)),
_ = 'Elixir.GenServer':stop(cangaroo@types:socket_pid(Socket)),
gleam@otp@actor:stop()
end.
-file("src/cangaroo.gleam", 48).
?DOC(
" Creates a new CAN client and connects it to the specified network interface.\n"
"\n"
" This function starts an OTP actor that manages the CAN socket connection.\n"
" The actor automatically receives incoming CAN frames from the bus and\n"
" forwards them to a subject that you can access via `types.messages(client)`.\n"
"\n"
" The `interface` parameter should be the name of a CAN network interface\n"
" on your Linux system, such as `\"can0\"` for a physical CAN adapter or\n"
" `\"vcan0\"` for a virtual CAN interface used in testing.\n"
"\n"
" Returns `Ok(CanClient)` on success, or an error if the connection fails.\n"
).
-spec start_link(binary()) -> {ok, cangaroo@types:can_client()} |
{error, cangaroo@errors:can_error()}.
start_link(Interface) ->
User_frames = gleam@erlang@process:new_subject(),
Actor_result = begin
_pipe@7 = (gleam@otp@actor:new_with_initialiser(
5000,
fun(Self_subject) ->
Can_tag = erlang:binary_to_atom(<<"can_data_frame"/utf8>>),
Selector = begin
_pipe = gleam_erlang_ffi:new_selector(),
_pipe@1 = gleam@erlang@process:select(_pipe, Self_subject),
gleam@erlang@process:select_record(
_pipe@1,
Can_tag,
1,
fun(Field@0) -> {raw_can_data, Field@0} end
)
end,
gleam@result:'try'(
begin
_pipe@2 = start(),
gleam@result:map_error(_pipe@2, fun error_to_string/1)
end,
fun(Socket) ->
gleam@result:'try'(
begin
_pipe@3 = open(Socket, Interface),
gleam@result:map_error(
_pipe@3,
fun error_to_string/1
)
end,
fun(_) ->
Client = cangaroo@types:new_client(
Socket,
User_frames,
Self_subject
),
_pipe@4 = gleam@otp@actor:initialised(Client),
_pipe@5 = gleam@otp@actor:selecting(
_pipe@4,
Selector
),
_pipe@6 = gleam@otp@actor:returning(
_pipe@5,
Client
),
{ok, _pipe@6}
end
)
end
)
end
)),
_pipe@8 = gleam@otp@actor:on_message(_pipe@7, fun handle_message/2),
gleam@otp@actor:start(_pipe@8)
end,
case Actor_result of
{ok, Started} ->
{ok, erlang:element(3, Started)};
{error, {init_failed, Reason}} ->
{error, {start_link_error, Reason}};
{error, {init_exited, _}} ->
{error, {start_link_error, <<"exited"/utf8>>}};
{error, init_timeout} ->
{error, {start_link_error, <<"timeout"/utf8>>}}
end.
-file("src/cangaroo.gleam", 129).
?DOC(
" Closes the CAN client and releases all associated resources.\n"
"\n"
" This function sends a shutdown message to the CAN client actor, which\n"
" will close the underlying CAN socket, stop the GenServer and stop the actor process. After\n"
" calling this function, the client should no longer be used.\n"
"\n"
" This operation is asynchronous, the function returns immediately after\n"
" sending the shutdown message. The actual cleanup happens in the background.\n"
).
-spec close(cangaroo@types:can_client()) -> nil.
close(Client) ->
gleam@erlang@process:send(
cangaroo@types:client_actor_subject(Client),
shutdown
),
nil.
-file("src/cangaroo.gleam", 142).
?DOC(
" Sends a CAN frame over the bus.\n"
"\n"
" The frame must be a `CanFrame` containing:\n"
" - `id`: The CAN identifier (11-bit standard or 29-bit extended)\n"
" - `data`: The payload as a `BitArray` (up to 8 bytes for standard CAN)\n"
"\n"
" Returns `Ok(Nil)` if the frame was successfully queued for transmission,\n"
" or an error if the send operation failed.\n"
).
-spec send(cangaroo@types:can_client(), cangaroo@types:can_frame()) -> {ok, nil} |
{error, cangaroo@errors:can_error()}.
send(Client, Frame) ->
{can_frame, Id, Data} = Frame,
Elixir_frame = bridge_ffi:new_frame(Id, Data),
Socket = cangaroo@types:client_socket(Client),
Status = 'Elixir.Excansock':send(
cangaroo@types:socket_pid(Socket),
Elixir_frame
),
cangaroo@internal:check_status(Status).
-file("src/cangaroo.gleam", 162).
?DOC(
" Enables or disables local loopback of transmitted CAN frames.\n"
"\n"
" When loopback is enabled, frames sent by this socket are also delivered\n"
" to other CAN sockets on the same interface (but not to this socket itself,\n"
" unless `recv_own_messages` is also enabled).\n"
"\n"
" By default, loopback is typically enabled on CAN interfaces.\n"
"\n"
" ## Parameters\n"
"\n"
" - `client`: The CAN client to configure\n"
" - `value`: `True` to enable loopback, `False` to disable it\n"
).
-spec set_loopback(cangaroo@types:can_client(), boolean()) -> {ok, nil} |
{error, cangaroo@errors:can_error()}.
set_loopback(Client, Value) ->
Socket = cangaroo@types:client_socket(Client),
Status = 'Elixir.Excansock':set_loopback(
cangaroo@types:socket_pid(Socket),
Value
),
cangaroo@internal:check_status(Status).
-file("src/cangaroo.gleam", 186).
?DOC(
" Controls whether this client receives its own transmitted CAN frames.\n"
"\n"
" When enabled, frames sent by this client will also be received back\n"
" through the `messages` subject. This can be useful for confirming\n"
" successful transmission or for debugging purposes.\n"
"\n"
" By default, receiving own messages is disabled.\n"
"\n"
" Note: This setting only has an effect when loopback is also enabled\n"
" (see `set_loopback`).\n"
"\n"
" ## Parameters\n"
"\n"
" - `client`: The CAN client to configure\n"
" - `value`: `True` to receive own messages, `False` to filter them out\n"
).
-spec recv_own_messages(cangaroo@types:can_client(), boolean()) -> {ok, nil} |
{error, cangaroo@errors:can_error()}.
recv_own_messages(Client, Value) ->
Socket = cangaroo@types:client_socket(Client),
Status = 'Elixir.Excansock':recv_own_messages(
cangaroo@types:socket_pid(Socket),
Value
),
cangaroo@internal:check_status(Status).
-file("src/cangaroo.gleam", 209).
?DOC(
" Sets CAN ID filters to selectively receive only matching frames.\n"
"\n"
" Filters allow you to specify which CAN frames should be received based\n"
" on their CAN ID. Each filter consists of an `id` and a `mask`:\n"
"\n"
" Multiple filters can be specified - a frame is accepted if it matches\n"
" ANY of the filters (OR logic).\n"
"\n"
" Calling this function replaces any previously set filters.\n"
"\n"
" ## Parameters\n"
"\n"
" - `client`: The CAN client to configure\n"
" - `filters`: A list of `CanFilter` values defining the acceptance criteria\n"
).
-spec set_filters(
cangaroo@types:can_client(),
list(cangaroo@types:can_filter())
) -> {ok, nil} | {error, cangaroo@errors:can_error()}.
set_filters(Client, Filters) ->
Socket = cangaroo@types:client_socket(Client),
Status = 'Elixir.Excansock':set_filters(
cangaroo@types:socket_pid(Socket),
Filters
),
cangaroo@internal:check_status(Status).
-file("src/cangaroo.gleam", 229).
?DOC(
" Sets the error filter mask for receiving CAN error frames.\n"
"\n"
" CAN error frames are special frames generated by the CAN controller\n"
" to report bus errors, such as bit errors, CRC errors, or bus-off conditions.\n"
" The error filter is a bitmask that determines which types of errors\n"
" should be reported to this client.\n"
"\n"
" ## Parameters\n"
"\n"
" - `client`: The CAN client to configure\n"
" - `filter`: A bitmask specifying which error types to receive\n"
).
-spec set_error_filter(cangaroo@types:can_client(), integer()) -> {ok, nil} |
{error, cangaroo@errors:can_error()}.
set_error_filter(Client, Filter) ->
Socket = cangaroo@types:client_socket(Client),
Status = 'Elixir.Excansock':set_error_filter(
cangaroo@types:socket_pid(Socket),
Filter
),
cangaroo@internal:check_status(Status).