defmodule GeoTherminator.PumpAPI.Device.Server do require Logger require GeoTherminator.PumpAPI.Auth.InstallationInfo use GenServer import GeoTherminator.TypedStruct alias GeoTherminator.PumpAPI.Device alias GeoTherminator.PumpAPI.Auth.InstallationInfo alias GeoTherminator.PumpAPI.Auth.Server, as: AuthServer defmodule Options do deftypedstruct(%{ auth_server: GenServer.name(), installation: InstallationInfo.t() }) end defmodule State do deftypedstruct(%{ auth_server: GenServer.name(), device: {Device.t() | nil, nil}, status: {Device.Status.t() | nil, nil}, registers: {Device.RegisterCollection.t() | nil, nil}, opstat: {Device.OpStat.t() | nil, nil} }) end @spec start_link(Options.t()) :: GenServer.on_start() def start_link(opts) do GenServer.start_link(__MODULE__, opts, name: {:via, Registry, {Device.Registry, InstallationInfo.record(opts.installation, :id)}} ) end @impl true def init(%Options{} = opts) do {:ok, %State{auth_server: opts.auth_server}, {:continue, {:init, opts.installation}}} end @impl true def handle_continue({:init, installation}, state) do user = AuthServer.get_auth(state.auth_server) {:ok, device} = :pump_api@device@api.device_info(user, installation) :ok = Device.PubSub.broadcast_device(device) state = %State{state | device: device} |> refresh_status() schedule_status() {:noreply, state} end @impl true def handle_call(msg, from, state) def handle_call(:get_device, _from, state) do {:reply, state.device, state} end def handle_call(:get_status, _from, state) do {:reply, state.status, state} end def handle_call(:get_registers, _from, state) do {:reply, state.registers, state} end def handle_call(:get_opstat, _from, state) do {:reply, state.opstat, state} end def handle_call({:set_temp, temp}, _from, state) do user = AuthServer.get_auth(state.auth_server) Logger.debug("Begin set temp to #{temp}") resp = :pump_api@device@api.set_temp(user, state.device, temp) Logger.debug("Set temp result: #{inspect(resp)}") {:reply, resp, state} end @impl true def handle_info(msg, state) def handle_info(:refresh_status, state) do state = refresh_status(state) schedule_status() {:noreply, state} end @spec get_device(GenServer.name()) :: Device.t() def get_device(server) do GenServer.call(server, :get_device) end @spec get_status(GenServer.name()) :: Device.Status.t() def get_status(server) do GenServer.call(server, :get_status) end @spec get_registers(GenServer.name()) :: Device.RegisterCollection.t() def get_registers(server) do GenServer.call(server, :get_registers) end @spec get_opstat(GenServer.name()) :: Device.OpStat.t() def get_opstat(server) do GenServer.call(server, :get_opstat) end @spec set_temp(GenServer.name(), integer()) :: :ok | {:error, String.t()} def set_temp(server, temp) do GenServer.call(server, {:set_temp, temp}) end defp refresh_status(state) do user = AuthServer.get_auth(state.auth_server) [status, registers, opstat] = Task.async_stream( [ &:pump_api@device@api.status/2, &:pump_api@device@api.register_info/2, &:pump_api@device@api.opstat/2 ], & &1.(user, state.device), timeout: Application.fetch_env!(:geo_therminator, :api_timeout) ) |> Enum.map(fn {:ok, {:ok, val}} -> val end) Device.PubSub.broadcast_status(state.device, status) Device.PubSub.broadcast_registers(state.device, registers) Device.PubSub.broadcast_opstat(state.device, opstat) %State{state | status: status, registers: registers, opstat: opstat} end defp schedule_status() do Process.send_after( self(), :refresh_status, Application.fetch_env!(:geo_therminator, :api_refresh) ) end end