Current section

Files

Jump to
grizzly lib grizzly trace record.ex
Raw

lib/grizzly/trace/record.ex

defmodule Grizzly.Trace.Record do
@moduledoc """
Data structure for a single item in the trace log
"""
require Logger
alias Grizzly.{Trace, ZWave}
alias Grizzly.ZWave.Command
alias Grizzly.ZWave.Commands.ZIPPacket
@type t() :: %__MODULE__{
timestamp: Time.t(),
binary: binary(),
src: Trace.src() | nil,
dest: Trace.dest() | nil
}
@type opt() :: {:src, Trace.src()} | {:dest, Trace.dest()} | {:timestamp, Time.t()}
defstruct src: nil, dest: nil, binary: nil, timestamp: nil
@doc """
Make a new `Grizzly.Record.t()` from a binary string
Options:
* `:src` - the src as a string
* `:dest` - the dest as a string
"""
@spec new(binary(), [opt()]) :: t()
def new(binary, opts \\ []) do
timestamp = Keyword.get(opts, :timestamp, Time.utc_now())
src = Keyword.get(opts, :src)
dest = Keyword.get(opts, :dest)
%__MODULE__{
src: src,
dest: dest,
binary: binary,
timestamp: timestamp
}
end
@doc """
Turn a record into the string format
"""
@spec to_string(t(), Trace.format()) :: String.t()
def to_string(record, format \\ :text)
def to_string(record, :text) do
%__MODULE__{timestamp: ts, src: src, dest: dest, binary: binary} = record
ts = Time.truncate(ts, :millisecond)
prefix = "#{Time.to_string(ts)} #{src_to_string(src)} -> #{dest_to_string(dest)}"
case ZWave.from_binary(binary) do
{:ok, zip_packet} ->
"#{prefix} #{command_info_str(zip_packet, binary)}"
{:error, _} ->
"#{prefix} #{inspect(binary, limit: 500)}"
end
end
def to_string(record, :raw) do
%__MODULE__{timestamp: ts, src: src, dest: dest, binary: binary} = record
time = ts |> Time.truncate(:millisecond) |> Time.to_string()
"#{time} #{src_to_string(src)} -> #{dest_to_string(dest)}: #{inspect(binary, limit: 500)}"
end
defp src_to_string(:grizzly), do: src_to_string("G")
defp src_to_string(src), do: src |> Kernel.to_string() |> String.pad_leading(3)
defp dest_to_string(:grizzly), do: dest_to_string("G")
defp dest_to_string(dest), do: dest |> Kernel.to_string() |> String.pad_trailing(3)
defp command_info_str(%Command{name: :keep_alive} = cmd, _binary) do
case Command.param!(cmd, :ack_flag) do
:ack_request ->
" keep_alive (ack req)"
_ ->
" keep_alive (ack resp)"
end
end
defp command_info_str(zip_packet, binary) do
seq_number = Command.param!(zip_packet, :seq_number)
flag = Command.param!(zip_packet, :flag)
cond do
flag == :nack_waiting ->
expected_delay = ZIPPacket.extension(zip_packet, :expected_delay, nil)
command_info_empty_response(seq_number, flag) <>
" expected_delay=#{inspect(expected_delay)}"
flag in [:ack_response, :nack_response, :nack_waiting] ->
command_info_empty_response(seq_number, flag)
Command.param(zip_packet, :command) == nil ->
" no_operation"
true ->
command_info_with_encapsulated_command(seq_number, zip_packet, binary)
end
end
defp command_info_empty_response(seq_number, flag) do
"#{seq_number_to_str(seq_number)} #{flag}"
end
defp command_info_with_encapsulated_command(seq_number, zip_packet, binary) do
command = Command.param!(zip_packet, :command)
command_binary = ZIPPacket.unwrap(binary)
"#{seq_number_to_str(seq_number)} #{command.name} #{inspect(command_binary, limit: 500)}"
rescue
err ->
Logger.error("""
[Grizzly.Trace] Expected an encapsulated command, but no command param was found.
Binary: #{inspect(binary, limit: 500)}
This is probably a bug -- please report it along with the stack trace and, if
possible, the corresponding line in the trace file.
#{Exception.format(:error, err, __STACKTRACE__)}
""")
command_name =
try do
zip_packet.name
rescue
_ -> "UNKNOWN COMMAND"
end
"#{seq_number_to_str(seq_number)} ENCODING ERROR #{command_name} #{inspect(binary, limit: 500)}"
end
defp seq_number_to_str(seq_number) do
String.pad_trailing(Kernel.to_string(seq_number), 4, " ")
end
end