import cangaroo/bridge import cangaroo/errors import cangaroo/excansock import cangaroo/internal import cangaroo/types.{ type ActorMessage, type CanClient, type CanFrame, type CanSocket, } import gleam/erlang/atom import gleam/erlang/process import gleam/otp/actor import gleam/result fn start() -> Result(CanSocket, errors.CanError) { let #(status, pid) = excansock.start_link() case atom.to_string(status) { "ok" -> Ok(types.new_socket(pid)) other_msg -> Error(errors.StartLinkError(other_msg)) } } fn open(socket: CanSocket, interface: String) -> Result(Nil, errors.CanError) { let raw = excansock.open(types.socket_pid(socket), interface, False) internal.decode_result(raw) } pub fn start_link(interface: String) -> Result(CanClient, errors.CanError) { let user_frames = process.new_subject() let actor_result = { actor.new_with_initialiser(5000, fn(self_subject) { let can_tag = atom.create("can_data_frame") let selector = process.new_selector() |> process.select(for: self_subject) |> process.select_record( tag: can_tag, fields: 1, mapping: types.RawCanData, ) use socket <- result.try(start() |> result.map_error(error_to_string)) use _ <- result.try( open(socket, interface) |> result.map_error(error_to_string), ) let client = types.new_client(socket, user_frames, self_subject) actor.initialised(client) |> actor.selecting(selector) |> actor.returning(client) |> Ok }) } |> actor.on_message(handle_message) |> actor.start() case actor_result { Ok(started) -> Ok(started.data) Error(actor.InitFailed(reason)) -> Error(errors.StartLinkError(reason)) Error(actor.InitExited(_)) -> Error(errors.StartLinkError("exited")) Error(actor.InitTimeout) -> Error(errors.StartLinkError("timeout")) } } fn error_to_string(err: errors.CanError) -> String { case err { errors.StartLinkError(reason) -> reason errors.InterfaceBoundError(interface) -> "interface_bound:" <> interface errors.UnknownError(reason) -> reason errors.InvalidMessageTypeError -> "invalid_message_type" } } fn handle_message( client: CanClient, msg: ActorMessage, ) -> actor.Next(CanClient, ActorMessage) { case msg { types.RawCanData(raw_data) -> { let frame = bridge.unwrap_frame(raw_data) let id = bridge.get_id(frame) let data = bridge.get_data(frame) let gleam_frame = types.CanFrame(id:, data:) process.send(types.messages(client), gleam_frame) actor.continue(client) } types.Shutdown -> { let socket = types.client_socket(client) let _ = excansock.close(types.socket_pid(socket)) let _ = excansock.stop(types.socket_pid(socket)) actor.stop() } } } pub fn close(client: CanClient) -> Nil { process.send(types.client_actor_subject(client), types.Shutdown) Nil } pub fn send(client: CanClient, frame: CanFrame) -> Result(Nil, errors.CanError) { let types.CanFrame(id:, data:) = frame let elixir_frame = bridge.new_frame(id, data) let socket = types.client_socket(client) let status = excansock.send(types.socket_pid(socket), elixir_frame) internal.check_status(status) } pub fn set_loopback( client: CanClient, value: Bool, ) -> Result(Nil, errors.CanError) { let socket = types.client_socket(client) let status = excansock.set_loopback(types.socket_pid(socket), value) internal.check_status(status) } pub fn recv_own_messages( client: CanClient, value: Bool, ) -> Result(Nil, errors.CanError) { let socket = types.client_socket(client) let status = excansock.recv_own_messages(types.socket_pid(socket), value) internal.check_status(status) } pub fn set_filters( client: CanClient, filters: List(types.CanFilter), ) -> Result(Nil, errors.CanError) { let socket = types.client_socket(client) let status = excansock.set_filters(types.socket_pid(socket), filters) internal.check_status(status) } pub fn set_error_filter( client: CanClient, filter: Int, ) -> Result(Nil, errors.CanError) { let socket = types.client_socket(client) let status = excansock.set_error_filter(types.socket_pid(socket), filter) internal.check_status(status) }