defmodule Sersock do @moduledoc """ Server module to export individual serial ports thru a socket. A single remote socket connection is supported. Each new socket connection closes and replaces the previous one. Native port is open until a connection is made and will be closed when the connection closes. Serial ports should be free while there is no connection. """ import Supervisor.Spec @sleep 1 ########################################## # Public API ########################################## @doc """ Starts the Server. `params` is a keyword list to be merged with the following defaults: ```elixir %{ port: 0, remote: true, device: "TTY", speed: "9600", config: "8N1", } ``` `remote` is a boolean flag indicating whether to listen on `any interface` or on `local loop interface`. `port` is the tcp port number where the serial port will be served. `device` is the name of the serial port to be exported. `speed` can be any of `1200`, `2400`, `4800`, `9600`, `19200`, `38400`, `57600`, `115200`. `config` can be any of `8N1`, `7E1`, `7O1`. `opts` is optional and is passed verbatim to GenServer. Returns `{:ok, pid}`. ## Example ```elixir Sersock.start_link([device: "ttyUSB0", port: 5000], [name: Sersock]) ``` """ def start_link(params, opts \\ []) do Agent.start_link(fn -> init(params) end, opts) end def stop(pid) do Agent.stop(pid) end def endpoint(pid) do Agent.get(pid, fn %{endpoint: endpoint} -> {:ok, endpoint} end) end def state(pid) do Agent.get(pid, fn state -> state end) end defp init(params) do remote = Keyword.get(params, :remote, true) ip = if remote, do: {0,0,0,0}, else: {127,0,0,1} port = Keyword.get(params, :port, 0) device = Keyword.fetch!(params, :device) speed = Keyword.get(params, :speed, 9600) config = Keyword.get(params, :config, "8N1") serial = %{device: device, speed: speed, config: config} {:ok, listener} = :gen_tcp.listen(port, [:binary, ip: ip, packet: :raw, active: false, reuseaddr: true]) {:ok, {ip, port}} = :inet.sockname(listener) endpoint = %{ip: ip, port: port} spec = worker(__MODULE__, [], restart: :temporary, function: :start_child) {:ok, sup} = Supervisor.start_link([spec], strategy: :simple_one_for_one) accept = spawn_link(fn -> accept(listener, serial, sup) end) %{endpoint: endpoint, sup: sup, accept: accept, listener: listener} end defp accept(listener, serial, sup) do {:ok, socket} = :gen_tcp.accept(listener) {:ok, pid} = Supervisor.start_child(sup, [socket, serial]) :ok = :gen_tcp.controlling_process(socket, pid) :go = send pid, :go accept(listener, serial, sup) end def start_child(socket, serial) do {:ok, spawn_link(fn -> receive do :go -> self = self() port = spawn_link(fn -> start_sniff(self, serial) end) :ok = :inet.setopts(socket, active: :once) loop(socket, port) end end)} end defp loop(socket, port) do :ok = receive do {:tcp, ^socket, data} -> send port, {:data, data} :ok = :inet.setopts(socket, active: :once) {:serial, ^port, data} -> :ok = :gen_tcp.send(socket, data) unexpected -> {:unexpected, unexpected} end loop(socket, port) end defp start_sniff(pid, serial) do {:ok, nid} = Sniff.open serial.device, serial.speed, serial.config poll(pid, nid) end defp poll(pid, nid) do :ok = receive do {:data, data} -> Sniff.write nid, data unexpected -> {:unexpected, unexpected} after @sleep -> :ok end resp = Sniff.read nid :ok = case resp do {:ok, <<>>} -> :ok {:ok, data} -> send pid, {:serial, self(), data} :ok unexpected -> {:unexpected, unexpected} end poll(pid, nid) end end