Current section
Files
Jump to
Current section
Files
src/cangaroo.gleam
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)
}