nerves-networking / vintage_net_mobile

Mobile networking for VintageNet
Apache License 2.0
27 stars 11 forks source link

Removable modems #17

Open LostKobrakai opened 4 years ago

LostKobrakai commented 4 years ago

As requested on the forums: https://elixirforum.com/t/vintagenet-a-new-network-configuration-library-for-nerves/27535/3

What I have right now are a few related modules:

RentoryHub.Hardware.Usb is mostly borrowed from Toolshed's lsusb

defmodule RentoryHub.Hardware.Usb do
  @doc "Find out if a USB device is available"
  def available?(id) do
    Enum.any?(usb_information(), &match_id_in_information(&1, id))
  end

  defp match_id_in_information(%{"PRODUCT" => pid}, id), do: to_vidpid(pid) == id
  defp match_id_in_information(_, _), do: false

  @doc "Information about all usb devices available"
  def usb_information do
    Path.wildcard("/sys/bus/usb/devices/*/uevent")
    |> Enum.map(&File.read!/1)
    |> Enum.map(&parse_kv_config/1)
    |> Enum.filter(&(&1["DEVTYPE"] == "usb_device"))
  end

  defp parse_kv_config(contents) do
    contents
    |> String.split("\n")
    |> Enum.flat_map(&parse_kv/1)
    |> Enum.into(%{})
  end

  defp parse_kv(""), do: []
  defp parse_kv(<<"#", _rest::binary>>), do: []

  defp parse_kv(key_equals_value) do
    [key, value] = String.split(key_equals_value, "=", parts: 2, trim: true)
    [{key, value}]
  end

  defp to_vidpid(""), do: "?"

  defp to_vidpid(raw) do
    # The VIDPID comes in as "vid/pid/somethingelse"
    [vid_str, pid_str, _] = String.split(raw, "/", trim: true)
    vid = String.pad_leading(vid_str, 4, "0")
    pid = String.pad_leading(pid_str, 4, "0")
    vid <> ":" <> pid
  end
end

RentoryHub.Hardware.UsbDetector polls usb devices and uses some pubsub to notify other parties. This is use to trigger the usb_modeswitch I need for my modem. It could also detect the modem and "enable" ppp afterwards, but currently there's not really an api for vintagenet to enable/disable ppp.

defmodule RentoryHub.Hardware.UsbDetector do
  use GenStateMachine, callback_mode: [:handle_event_function, :state_enter]
  alias RentoryHub.Hardware.Usb

  def start_link(init_arg) do
    GenStateMachine.start_link(__MODULE__, init_arg)
  end

  @impl GenStateMachine
  def init(init_arg) do
    id = Keyword.fetch!(init_arg, :id)
    interval = Keyword.get(init_arg, :interval, :timer.seconds(10))

    {:ok, check_state(id), %{id: id, interval: interval}, tick_timeout_action(interval)}
  end

  def handle_event(:enter, _, state, %{id: id}) do
    RentoryHub.PubSub.publish("usb/#{id}", %{state: state})
    :keep_state_and_data
  end

  @impl GenStateMachine
  def handle_event({:timeout, :tick}, _, state, %{id: id, interval: interval} = data) do
    case check_state(id) do
      ^state -> {:keep_state_and_data, tick_timeout_action(interval)}
      state -> {:next_state, state, data, tick_timeout_action(interval)}
    end
  end

  defp tick_timeout_action(interval) do
    {{:timeout, :tick}, interval, %{}}
  end

  defp check_state(id) do
    if Usb.available?(id), do: :available, else: :unavailable
  end
end
Supervisor.child_spec({RentoryHub.Hardware.UsbDetector, id: "12d1:1f01"},
        id: {RentoryHub.Hardware.UsbDetector, "12d1:1f01"}
      ),

I'm actually wondering if this should be a concern for vintage_net at all. Maybe the device detection part is one thing and vintage_net_lte should just handle a tty file being available or not (or coming/going).