Where do I put the client/server boundary

Hi All, I’m writing a tool that monitors some hardware and want to try following the OTP philosophy as closely as possible. I hope it’s ok if I attach some Elixir code, though the question is much more general.


  def start_link(opts) do
    GenServer.start_link(__MODULE__, :ok, opts)
  end

  def nickname_of(sensor, typ) do
    repl = GenServer.call(MachineGovernor.MercuryITC, {:query, "READ:DEV:#{sensor}:#{typ}:NICK?\n"})
    repl |> String.split(":") |> List.last()
  end

  def enum_devices() do
    repl = GenServer.call(MachineGovernor.MercuryITC, {:query, "READ:SYS:CAT:DEV?\n"})
    repl = String.split(repl, ":") |> Enum.drop(4) |> Enum.chunk_every(3)
    Enum.map(repl, fn([sensor,typ,check]) ->
        {sensor,typ,nickname_of(sensor,typ)}
    end)
  end

  def sensor(typ, sensor) do
    msg = case typ do
        "HTR" -> "READ:DEV:#{sensor}:HTR:SIG:POWR?\n"
        "TEMP" -> "READ:DEV:#{sensor}:TEMP:SIG:TEMP?\n"
        "LVL" -> "READ:DEV:#{sensor}:LVL:SIG:HEL:LEV?\n"
    end

    repl = GenServer.call(MachineGovernor.MercuryITC, {:query, msg})
    repl = String.split(repl, ":") |> List.last()
    {f, ""} = String.slice(repl,0..-2) |> Float.parse
    {f, String.slice(repl,-1..-1)}
  end

  @impl true
  def init(:ok) do
    {:ok, uart} = Circuits.UART.start_link
    Circuits.UART.open(uart, "ttyACM1", speed: 9600, active: false)
    Circuits.UART.configure(uart, framing: {Circuits.UART.Framing.Line, separator: "\n"})
    {:ok, uart}
  end

  @impl true
  def handle_call({:query, msg}, _from, uart) do
    Circuits.UART.write(uart, msg)
    {:ok, repl} = Circuits.UART.read(uart, 600)
    {:reply, repl, uart}
  end

What I’d like to know is if I’ve put the boundary between the server and the client in the right place. The exposed API could be pretty similar, and indeed the only relevant part of state is the PID for the serial port process. Additionally, is it the OTP way of doing things to just let the server get killed if the serial port drops? This seems to me to be the “non-defensive programming” way of doing things.

1 Like

Did you consider removing the GenServer altogether and instead exposing a plain module that makes use of Circuits.UART directly:

Untested:

def start_link(device \\ "ttyACM1") do
    with {:ok, uart} <- Circuits.UART.start_link(name: __MODULE__),
           :ok <- Circuits.UART.open(uart, device, speed: 9600, active: false),
           :ok <- Circuits.UART.configure(uart, framing: {Circuits.UART.Framing.Line, separator: "\n"}) do
        {:ok, uart}
    end
end

def nickname_of(sensor, typ) do
    query("READ:DEV:#{sensor}:#{typ}:NICK?\n")
      |> String.split(":")
      |> List.last()
end

defp query(q) do
    :ok = Circuits.UART.write(__MODULE__, q)
   {:ok, reply} = Circuits.UART.read(uart, 600)
   reply
end
1 Like