Current section
Files
Jump to
Current section
Files
lib/sersock.ex
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