defmodule Cannes.Dumper do @moduledoc """ This module is for communicating with linux can sockets over candump. TODO: - Add logic to mount a can interface - Test if desired interface is reacable - Test if candump is available - Pass options to candump """ alias Porcelain.Process, as: Proc @doc """ Spawns a candump process on the given interface. ## Example iex> Cannes.Dumper.start("vcan0") %Porcelain.Process{ err: nil, out: #Function<63.104660160/2 in Stream.unfold/2>, pid: #PID<0.212.0> } """ def start(interface) when is_binary(interface) do proc = Porcelain.spawn("candump", ["-L", interface], out: :stream) case proc do {:error, message} -> raise message _ -> proc end end @doc """ Stops a running candump process carefully. ## Example proc = Cannes.Dumper.stop(proc) true """ def stop(dumper_process) do Proc.stop(dumper_process) end @doc """ Check if the candump process is still running. """ def alive?(dumper_process) do Proc.alive?(dumper_process) end @doc """ Returns a stream of formatted can messages. TODO: There is currently a problem that i can't solve. Therefore the ugly fix in line 32 is necessary. Also see elixirforum post (https://elixirforum.com/t/problems-with-processing-fast-occurring-elements-in-stream/36462) ## Example iex> Cannes.Dumper.get_formatted_stream(proc) #Stream<[ enum: #Function<63.104660160/2 in Stream.unfold/2>, funs: [#Function<51.104660160/1 in Stream.reject/2>, #Function<38.104660160/1 in Stream.each/2>] ]> """ def get_formatted_stream(dumper_process) do get_stream(dumper_process) |> Stream.reject(fn x -> countSub(x, "\n") > 1 end) |> Stream.each(fn item -> format_candump_string(item) end) end def get_stream(%Proc{out: outstream}) do outstream end @doc """ Parses the given candump string in the candump log format into a map. ## Example iex> Cannes.Dumper.format_candump_string("(1398128227.045337) can0 133#0000000098\\n") %{ data: <<0, 0, 0, 0, 0, 0, 0, 9>>, identifier: <<1, 51>>, interface: "can0", timestamp: 1398128227.045337 } """ def format_candump_string(dump_string) do message = dump_string |> String.slice(0..-3) |> String.split(" ") payload = String.split(Enum.at(message, 2), "#") %{ timestamp: String.slice(Enum.at(message, 0), 1..-2) |> String.to_float(), interface: Enum.at(message, 1), identifier: Enum.at(payload, 0) |> String.pad_leading(4, "0") |> Base.decode16!(), data: Enum.at(payload, 1) |> String.pad_leading(16, "0") |> Base.decode16!() } end # Currently needed for trashy hack in `get_formatted_stream()` defp countSub(str, sub) do length(String.split(str, sub)) - 1 end end