defmodule Serial do @moduledoc """ Serial communication through Elixir ports. """ use GenServer @send 0 @connect 1 @disconnect 2 @open 3 @close 4 @speed 5 @parity_odd 6 @parity_even 7 @break 8 @doc """ Starts a serial port. The process invoking this function will receive messages in the form of `{:elixir_serial, pid, data}`. """ def start_link(opts \\ []) do GenServer.start_link(__MODULE__, self(), opts) end @doc """ Opens a connection to the given tty. """ def open(pid, tty) do GenServer.call(pid, {:open, tty}) end @doc """ Close the currently open connection. """ def close(pid) do GenServer.call(pid, {:cmd, @close}) end @doc """ Reopens the connection to the current tty. """ def connect(pid) do GenServer.call(pid, {:cmd, @connect}) end @doc """ Disconnects from the current tty. """ def disconnect(pid) do GenServer.call(pid, {:cmd, @disconnect}) end @doc """ Sets the connection speed to the given value. """ def set_speed(pid, speed) do set_speed(pid, speed, speed) end @doc """ Sets the input and output connection speed to the given values. """ def set_speed(pid, in_speed, out_speed) do GenServer.call(pid, {:speed, in_speed, out_speed}) end @doc """ Sets the parity to either `:odd` and `:even`. """ def set_parity(pid, :odd) do GenServer.call(pid, {:cmd, @parity_odd}) end def set_parity(pid, :even) do GenServer.call(pid, {:cmd, @parity_even}) end @doc """ Sends a break to the open connection. """ def break(pid) do GenServer.call(pid, {:cmd, @break}) end @doc """ Sends data over the open connection. """ def send_data(pid, data) do GenServer.call(pid, {:send, data}) end def init(pid) do exec = :code.priv_dir(:serial) ++ '/serial' port = Port.open({:spawn_executable, exec}, [{:args, ['-erlang']}, :binary, {:packet, 2}]) {:ok, {pid, port}} end def handle_call({:open, tty}, _from, {_pid, port} = state) do Port.command(port, [@open, tty]) {:reply, :ok, state} end def handle_call({:speed, new_in_speed, new_out_speed}, _from, {_pid, port} = state) do Port.command(port, [@speed, Integer.to_char_list(new_in_speed), ' ', Integer.to_char_list(new_out_speed), 0]) {:reply, :ok, state} end def handle_call({:cmd, cmd}, _from, {_pid, port} = state) do Port.command(port, [cmd]) {:reply, :ok, state} end def handle_call({:send, data}, _from, {_pid, port} = state) do Port.command(port, [@send, data]) {:reply, :ok, state} end def handle_info({port, {:data, data}}, {pid, port} = state) do send(pid, {:elixir_serial, self(), data}) {:noreply, state} end end