-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", 25). -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>> -> gleam@erlang@process:unlink(Pid), {ok, cangaroo@types:new_socket(Pid)}; Other_msg -> {error, {start_link_error, Other_msg}} end. -file("src/cangaroo.gleam", 36). -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", 105). -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", 114). -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, false}, gleam@erlang@process:send( cangaroo@types:messages(Client), Gleam_frame ), gleam@otp@actor:continue(Client); {raw_extended_can_data, Raw_data@1} -> Frame@1 = bridge_ffi:unwrap_extended_frame(Raw_data@1), Id@1 = bridge_ffi:get_id(Frame@1), Data@1 = bridge_ffi:get_data(Frame@1), Gleam_frame@1 = {can_frame, Id@1, Data@1, true}, gleam@erlang@process:send( cangaroo@types:messages(Client), Gleam_frame@1 ), 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(); {excansock_down, _} -> gleam@otp@actor:stop_abnormal(<<"excansock_down"/utf8>>) end. -file("src/cangaroo.gleam", 52). ?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@9 = (gleam@otp@actor:new_with_initialiser( 5000, fun(Self_subject) -> Can_tag = erlang:binary_to_atom(<<"can_data_frame"/utf8>>), Extended_tag = erlang:binary_to_atom( <<"can_extended_frame"/utf8>> ), gleam@result:'try'( begin _pipe = start(), gleam@result:map_error(_pipe, fun error_to_string/1) end, fun(Socket) -> gleam@result:'try'( begin _pipe@1 = open(Socket, Interface), gleam@result:map_error( _pipe@1, fun error_to_string/1 ) end, fun(_) -> Excansock_monitor = gleam@erlang@process:monitor( cangaroo@types:socket_pid(Socket) ), Selector = begin _pipe@2 = gleam_erlang_ffi:new_selector(), _pipe@3 = gleam@erlang@process:select( _pipe@2, Self_subject ), _pipe@4 = gleam@erlang@process:select_record( _pipe@3, Can_tag, 1, fun(Field@0) -> {raw_can_data, Field@0} end ), _pipe@5 = gleam@erlang@process:select_record( _pipe@4, Extended_tag, 1, fun(Field@0) -> {raw_extended_can_data, Field@0} end ), gleam@erlang@process:select_specific_monitor( _pipe@5, Excansock_monitor, fun(Down) -> {excansock_down, erlang:element(4, Down)} end ) end, Client = cangaroo@types:new_client( Socket, User_frames, Self_subject ), _pipe@6 = gleam@otp@actor:initialised(Client), _pipe@7 = gleam@otp@actor:selecting( _pipe@6, Selector ), _pipe@8 = gleam@otp@actor:returning( _pipe@7, Client ), {ok, _pipe@8} end ) end ) end )), _pipe@10 = gleam@otp@actor:on_message(_pipe@9, fun handle_message/2), gleam@otp@actor:start(_pipe@10) 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", 155). ?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", 169). ?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" " - `extended`: `True` for 29-bit extended IDs, `False` for 11-bit standard IDs\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, Extended} = Frame, Elixir_frame = bridge_ffi:new_frame(Id, Data, Extended), 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", 189). ?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", 213). ?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", 236). ?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), Elixir_filters = gleam@list:map( Filters, fun(F) -> bridge_ffi:new_filter( erlang:element(2, F), erlang:element(3, F), erlang:element(4, F) ) end ), Status = 'Elixir.Excansock':set_filters( cangaroo@types:socket_pid(Socket), Elixir_filters ), cangaroo@internal:check_status(Status). -file("src/cangaroo.gleam", 258). ?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).