Packages
grizzly
7.0.3
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/log_monitor.ex
defmodule Grizzly.ZIPGateway.LogMonitor do
@moduledoc """
Monitors the Z/IP Gateway logs to extract and process certain data.
"""
use GenServer
require Logger
@type serial_api_status :: :ok | :initializing | :unknown | :unresponsive
@type network_key_type ::
:s0
| :s2_unauthenticated
| :s2_authenticated
| :s2_access_control
| :s2_authenticated_long_range
| :s2_access_control_long_range
@doc """
Returns the estimated status of the Z-Wave module based on Z/IP Gateway's
log output.
"""
@spec serial_api_status(GenServer.name()) :: serial_api_status()
def serial_api_status(name \\ __MODULE__) do
GenServer.call(name, :serial_api_status)
end
@doc """
Returns the network home id as extracted from the Z/IP Gateway logs.
"""
@spec home_id(GenServer.name()) :: binary() | nil
def home_id(name \\ __MODULE__) do
GenServer.call(name, :home_id)
end
@doc """
Returns a keyword list of the network keys extracted from the Z/IP Gateway logs.
"""
@spec network_keys(GenServer.name()) :: [{network_key_type(), binary()}]
def network_keys(name \\ __MODULE__) do
GenServer.call(name, :network_keys)
end
@doc """
Returns a string containing a filename and network keys formatted for use with the
Zniffer application (available through Simplicity Studio on Windows).
"""
@spec zniffer_network_keys(GenServer.name()) :: binary() | nil
def zniffer_network_keys(name \\ __MODULE__) do
home_id = home_id(name)
network_keys = network_keys(name)
cond do
is_nil(home_id) ->
nil
length(network_keys) != 6 ->
nil
true ->
"""
#{home_id}.txt:
9F;#{network_keys[:s2_access_control]};1
9F;#{network_keys[:s2_access_control_long_range]};1
9F;#{network_keys[:s2_authenticated]};1
9F;#{network_keys[:s2_authenticated_long_range]};1
9F;#{network_keys[:s2_unauthenticated]};1
98;#{network_keys[:s0]};1
"""
end
end
@spec start_link(keyword()) :: GenServer.on_start()
def start_link(opts \\ []) do
name = Keyword.get(opts, :name, __MODULE__)
GenServer.start_link(__MODULE__, opts, name: name)
end
@impl GenServer
def init(opts) do
{:ok,
%{
home_id: nil,
network_keys: [],
next_key_type: nil,
sapi_retransmissions: 0,
sapi_status: :unknown,
status_reporter: opts[:status_reporter]
}}
end
@impl GenServer
def handle_call(:serial_api_status, _from, %{sapi_status: sapi_status} = state) do
{:reply, sapi_status, state}
end
def handle_call(:home_id, _from, %{home_id: home_id} = state) do
{:reply, home_id, state}
end
def handle_call(:network_keys, _from, %{network_keys: network_keys} = state) do
{:reply, network_keys, state}
end
@impl GenServer
# We have the next key type in state, so extract the network key and save it
def handle_info({:message, network_key}, %{next_key_type: key_type} = state)
when not is_nil(key_type) do
network_key = network_key |> String.trim() |> String.upcase()
if Regex.match?(~r/^[0-9A-F]{32}$/, network_key) do
Logger.debug("[Grizzly] Extracted network key type=#{key_type} key=#{network_key}")
updated_network_keys = Keyword.put(state.network_keys, key_type, network_key)
{:noreply, %{state | network_keys: updated_network_keys, next_key_type: nil}}
else
Logger.error(
"[Grizzly] #{inspect(key_type)} network key appears to be invalid: #{network_key}"
)
{:noreply, state}
end
end
# The actual network key is printed on the next line, so save where we're at in state
# for when we receive the next message.
def handle_info({:message, "Key class 0x" <> _ = message}, state) do
next_key_type =
case Regex.named_captures(~r/Key class 0x\d+: (?<key_class>.+$)/, message) do
%{"key_class" => "KEY_CLASS_S0"} ->
:s0
%{"key_class" => "KEY_CLASS_S2_UNAUTHENTICATED"} ->
:s2_unauthenticated
%{"key_class" => "KEY_CLASS_S2_AUTHENTICATED"} ->
:s2_authenticated
%{"key_class" => "KEY_CLASS_S2_ACCESS"} ->
:s2_access_control
%{"key_class" => "KEY_CLASS_S2_AUTHENTICATED_LR"} ->
:s2_authenticated_long_range
%{"key_class" => "KEY_CLASS_S2_ACCESS_LR"} ->
:s2_access_control_long_range
%{"key_class" => key_class} ->
Logger.error("[Grizzly] Unknown network key class: #{key_class}")
nil
nil ->
Logger.error(
"[Grizzly] Unexpected Z/IP Gateway message while extracting network keys: #{message}"
)
nil
end
{:noreply, %{state | next_key_type: next_key_type}}
end
# Extract the Home ID from the ZIP_Router_Reset message
def handle_info({:message, "ZIP_Router_Reset:" <> message}, state) do
home_id =
case Regex.named_captures(~r/Home ID = 0x(?<home_id>[0-9a-fA-F]{8})/, message) do
%{"home_id" => home_id} ->
Logger.debug("[Grizzly] Extracted Home ID from ZIP_Router_Reset message: #{home_id}")
String.upcase(home_id)
nil ->
Logger.error(
"[Grizzly] Error extracting Home ID from ZIP_Router_Reset message: #{message}"
)
nil
end
{:noreply, %{state | home_id: home_id}}
end
def handle_info({:message, "Serial Process init" <> _}, state) do
{:noreply, set_sapi_status(state, :initializing)}
end
def handle_info({:message, "Bridge init done" <> _}, state) do
{:noreply, set_sapi_status(state, :ok)}
end
def handle_info({:message, message}, state) do
cond do
not String.contains?(message, " SerialAPI: Retransmission") ->
{:noreply, state}
state.sapi_retransmissions + 1 > 4 ->
{:noreply, set_sapi_status(state, :unresponsive)}
true ->
{:noreply, %{state | sapi_retransmissions: state.sapi_retransmissions + 1}}
end
end
defp set_sapi_status(state, new_status) when new_status in [:ok, :initializing] do
maybe_notify_status_reporter(state, new_status)
%{state | sapi_status: new_status, sapi_retransmissions: 0}
end
defp set_sapi_status(state, new_status) do
maybe_notify_status_reporter(state, new_status)
%{state | sapi_status: new_status}
end
defp maybe_notify_status_reporter(%{sapi_status: status}, status), do: :ok
defp maybe_notify_status_reporter(state, new_status) do
cond do
is_function(state.status_reporter, 1) ->
state.status_reporter.(new_status)
is_atom(state.status_reporter) and
function_exported?(state.status_reporter, :serial_api_status, 1) ->
state.status_reporter.serial_api_status(new_status)
true ->
:ok
end
:ok
end
end