Packages
grizzly
9.1.0
9.1.4
9.1.2
9.1.1
9.1.0
9.0.0
8.15.3
8.15.2
8.15.1
8.15.0
8.14.0
8.13.0
8.12.0
8.11.3
8.11.2
8.11.1
8.11.0
8.10.0
8.9.0
8.8.1
8.8.0
8.7.1
8.7.0
8.6.12
8.6.11
8.6.10
8.6.9
8.6.8
8.6.7
retired
8.6.6
8.6.5
8.6.4
8.6.3
8.6.2
8.6.1
8.6.0
8.5.3
8.5.2
8.5.1
8.5.0
8.4.0
8.3.0
8.2.3
8.2.2
8.2.1
8.2.0
8.1.0
8.0.1
8.0.0
7.4.3
7.4.2
7.4.1
7.4.0
7.3.0
7.2.0
7.1.4
7.1.3
7.1.2
7.1.1
7.1.0
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.8.8
6.8.7
6.8.6
6.8.5
6.8.4
6.8.3
6.8.2
6.8.1
6.8.0
6.7.1
6.7.0
6.6.1
6.6.0
6.5.1
6.5.0
6.4.0
6.3.0
6.2.0
6.1.1
6.1.0
6.0.1
6.0.0
5.4.1
5.4.0
5.3.0
5.2.8
5.2.7
5.2.6
5.2.5
5.2.4
5.2.3
5.2.2
5.2.1
5.2.0
5.1.2
5.1.1
5.1.0
5.0.2
5.0.1
5.0.0
4.0.1
4.0.0
3.0.0
2.1.0
2.0.0
1.0.1
1.0.0
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.1
0.19.0
0.18.3
0.18.2
0.18.1
0.18.0
0.17.7
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.2
0.16.1
0.16.0
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.9.0-rc.4
0.9.0-rc.3
0.9.0-rc.2
0.9.0-rc.1
0.9.0-rc.0
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.3
0.4.2
Elixir Z-Wave library
Current section
Files
Jump to
Current section
Files
lib/grizzly/zipgateway/sapi_monitor.ex
defmodule Grizzly.ZIPGateway.SAPIMonitor do
@moduledoc false
use GenServer
@type serial_api_status :: :ok | :unresponsive
@type option ::
{:period, pos_integer()}
| {:threshold, pos_integer()}
| {:name, GenServer.name()}
# There must be THRESHOLD retransmissions within the last PERIOD milliseconds for
# the SAPI status to be considered unresponsive. This prevents occasional SAPI
# blips from accumulating and causing a false positive unresponsive status.
@default_period :timer.seconds(30)
@default_threshold 5
@spec retransmission(GenServer.name()) :: :ok
def retransmission(name \\ __MODULE__) do
GenServer.call(name, :retransmission)
end
@spec status(GenServer.name()) :: serial_api_status()
def status(name \\ __MODULE__) do
GenServer.call(name, :status)
end
@spec reset(GenServer.name()) :: :ok
def reset(name \\ __MODULE__) do
GenServer.call(name, :reset)
end
@spec start_link([option()]) :: GenServer.on_start()
def start_link(opts \\ []) do
name = Keyword.get(opts, :name, __MODULE__)
default_period = Application.get_env(:grizzly, :sapi_monitor_period, @default_period)
default_threshold = Application.get_env(:grizzly, :sapi_monitor_threshold, @default_threshold)
period = Keyword.get(opts, :period, default_period)
threshold = Keyword.get(opts, :threshold, default_threshold)
GenServer.start_link(
__MODULE__,
[period: period, threshold: threshold],
name: name
)
end
@impl GenServer
def init(opts) do
period = Keyword.fetch!(opts, :period)
threshold = Keyword.fetch!(opts, :threshold)
state = %{
period: period,
threshold: threshold,
retransmissions: [],
status: :ok,
check_timer: nil
}
notify(:ok)
{:ok, state}
end
@impl GenServer
def handle_call(:retransmission, _from, state) do
state = %{
state
| retransmissions: [System.monotonic_time(:millisecond) | state.retransmissions]
}
{:reply, :ok, update(state)}
end
def handle_call(:status, _from, state) do
state = update(state)
{:reply, state.status, state}
end
def handle_call(:reset, _from, state) do
state = %{state | retransmissions: []}
{:reply, :ok, update(state)}
end
@impl GenServer
def handle_info(:check, state) do
{:noreply, update(state)}
end
defp update(state) do
state = %{state | retransmissions: drop_old(state.retransmissions, state.period)}
if length(state.retransmissions) >= state.threshold do
state
|> set_status(:unresponsive)
|> clear_timer()
|> schedule_check()
else
set_status(state, :ok)
end
end
defp set_status(state, new_status) do
maybe_notify(state, new_status)
%{state | status: new_status}
end
defp clear_timer(state) do
case state.check_timer do
nil ->
state
check_timer ->
_ = Process.cancel_timer(check_timer, info: false)
%{state | check_timer: nil}
end
end
defp schedule_check(state) do
# If there are any retransmissions in the current state, set a new timer to
# check again in PERIOD + 10 milliseconds
ref = Process.send_after(self(), :check, state.period + 10)
%{state | check_timer: ref}
end
# If status has not changed, do not notify
defp maybe_notify(%{status: status}, status), do: :ok
defp maybe_notify(_state, new_status) do
notify(new_status)
end
defp notify(status) do
Grizzly.Events.broadcast_event(:serial_api_status, status)
:ok
end
# Drop retransmissions older than PERIOD milliseconds
defp drop_old(retransmissions, period) do
Enum.reject(retransmissions, fn timestamp ->
System.monotonic_time(:millisecond) - timestamp > period
end)
end
end