Skip to content

Commit

Permalink
Add Version Get Command
Browse files Browse the repository at this point in the history
Add support for the Version get command (v2) to help get more
information about Z-Wave firmware and protocol versions.
  • Loading branch information
mattludwigs committed Oct 8, 2019
1 parent 47f3762 commit 5dc35a2
Show file tree
Hide file tree
Showing 8 changed files with 329 additions and 7 deletions.
62 changes: 61 additions & 1 deletion lib/grizzly/command_class/version.ex
Original file line number Diff line number Diff line change
@@ -1,12 +1,31 @@
defmodule Grizzly.CommandClass.Version do
@moduledoc """
Helpers for working with the command class VERSION
"""

alias Grizzly.CommandClass.Mappings
require Logger

@type library_type ::
:static_controller
| :controller
| :enhanced_slave
| :slave
| :installer
| :routing_slave
| :bridge_controller
| :device_under_test
| :av_remote
| :av_device

@type version_report :: %{
protocol_library: library_type(),
protocol_version: byte(),
protocol_sub_version: byte(),
firmware_version: byte(),
firmware_sub_version: byte()
}

@doc """
Decode version report data
"""
Expand All @@ -24,6 +43,30 @@ defmodule Grizzly.CommandClass.Version do
}
end

@spec decode_version_report(binary()) ::
{:ok, version_report()} | {:error, :invalid_version_report, binary()}
def decode_version_report(
<<protocol_library, protocol_version, protocol_sub_version, firmware_version,
firmware_sub_version, _::binary>> = binary
) do
with {:ok, library_type} <- decode_library_type(protocol_library) do
{:ok,
%{
protocol_library: library_type,
protocol_version: protocol_version,
protocol_sub_version: protocol_sub_version,
firmware_version: firmware_version,
firmware_sub_version: firmware_sub_version
}}
else
{:error, :invalid_library_type, library_type} ->
_ = Logger.warn("Invalid library type: #{inspect(library_type)}")
{:error, :invalid_version_report, binary}
end
end

def decode_version_report(binary), do: {:error, :invalid_version_report, binary}

@doc """
Encode command class
"""
Expand All @@ -38,4 +81,21 @@ defmodule Grizzly.CommandClass.Version do
{:ok, byte}
end
end

@doc """
Decode a byte into a library type
"""
@spec decode_library_type(byte()) ::
{:ok, library_type()} | {:error, :invalid_library_type, byte()}
def decode_library_type(0x01), do: {:ok, :static_controller}
def decode_library_type(0x02), do: {:ok, :controller}
def decode_library_type(0x03), do: {:ok, :enhanced_slave}
def decode_library_type(0x04), do: {:ok, :slave}
def decode_library_type(0x05), do: {:ok, :installer}
def decode_library_type(0x06), do: {:ok, :routing_slave}
def decode_library_type(0x07), do: {:ok, :bridge_controller}
def decode_library_type(0x08), do: {:ok, :device_under_test}
def decode_library_type(0x0A), do: {:ok, :av_remote}
def decode_library_type(0x0B), do: {:ok, :av_device}
def decode_library_type(byte), do: {:error, :invalid_library_type, byte}
end
90 changes: 90 additions & 0 deletions lib/grizzly/command_class/version/get.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
defmodule Grizzly.CommandClass.Version.Get do
@moduledoc """
Module for working with the `VERSION GET` command.
This is for getting version information about the module Z-Wave
Command Options:
* `:seq_number` - the sequence number used by the Z/IP packet
* `:retries` - the number of attempts to send the command (default 2)
"""

@behaviour Grizzly.Command

alias Grizzly.Packet
alias Grizzly.CommandClass.Version

@type t :: %__MODULE__{
seq_number: Grizzly.seq_number(),
retries: non_neg_integer()
}

@type opt ::
{:seq_number, Grizzly.seq_number()}
| {:retries, non_neg_integer()}

defstruct seq_number: nil, retries: 2

@spec init([opt]) :: {:ok, t}
def init(opts) do
{:ok, struct(__MODULE__, opts)}
end

@spec encode(t) :: {:ok, binary}
def encode(%__MODULE__{seq_number: seq_number}) do
{:ok, Packet.header(seq_number) <> <<0x86, 0x11>>}
end

@spec handle_response(t, Packet.t()) ::
{:continue, t}
| {:done, {:error, :nack_response}}
| {:done, {:ok, Version.version_report()}}
| {:retry, t}
def handle_response(%__MODULE__{seq_number: seq_number} = command, %Packet{
seq_number: seq_number,
types: [:ack_response]
}) do
{:continue, command}
end

def handle_response(%__MODULE__{seq_number: seq_number, retries: 0}, %Packet{
seq_number: seq_number,
types: [:nack_response]
}) do
{:done, {:error, :nack_response}}
end

def handle_response(%__MODULE__{seq_number: seq_number, retries: n} = command, %Packet{
seq_number: seq_number,
types: [:nack_response]
}) do
{:retry, %{command | retries: n - 1}}
end

def handle_response(_command, %Packet{
body:
%{
command_class: Version,
command: :version_report
} = body
}) do
{:done, {:ok, Map.drop(body, [:command_class, :command])}}
end

def handle_response(
%__MODULE__{seq_number: seq_number} = command,
%Packet{
seq_number: seq_number,
types: [:nack_response, :nack_waiting]
} = packet
) do
if Packet.sleeping_delay?(packet) do
{:queued, command}
else
{:continue, command}
end
end

def handle_response(command, _), do: {:continue, command}
end
8 changes: 8 additions & 0 deletions lib/grizzly/packet/body_parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,14 @@ defmodule Grizzly.Packet.BodyParser do
}
end

def parse(<<0x86, 0x12, report::binary>>) do
{:ok, report_body} = Version.decode_version_report(report)

report_body
|> Map.put(:command_class, Version)
|> Map.put(:command, :version_report)
end

def parse(
<<0x7A, 0x02, manufacturer_id::size(2)-integer-unsigned-unit(8),
firmware_id::size(2)-integer-unsigned-unit(8), checksum::size(2)-binary-unit(8),
Expand Down
4 changes: 3 additions & 1 deletion mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ defmodule Grizzly.MixProject do
Grizzly.CommandClass.ThermostatSetback,
Grizzly.CommandClass.ThermostatSetpoint,
Grizzly.CommandClass.UserCode,
Grizzly.CommandClass.Version,
Grizzly.CommandClass.WakeUp,
Grizzly.CommandClass.ZwaveplusInfo
],
Expand All @@ -103,7 +104,6 @@ defmodule Grizzly.MixProject do
Grizzly.CommandClass.Basic.Get,
Grizzly.CommandClass.Basic.Set,
Grizzly.CommandClass.Battery.Get,
Grizzly.CommandClass.CommandClassVersion.Get,
Grizzly.CommandClass.Configuration.BulkGet,
Grizzly.CommandClass.Configuration.Get,
Grizzly.CommandClass.Configuration.Set,
Expand Down Expand Up @@ -168,6 +168,8 @@ defmodule Grizzly.MixProject do
Grizzly.CommandClass.UserCode.Get,
Grizzly.CommandClass.UserCode.Set,
Grizzly.CommandClass.UserCode.UsersNumberGet,
Grizzly.CommandClass.Version.CommandClassGet,
Grizzly.CommandClass.Version.Get,
Grizzly.CommandClass.WakeUp.IntervalCapabilitiesGet,
Grizzly.CommandClass.WakeUp.IntervalGet,
Grizzly.CommandClass.WakeUp.IntervalSet,
Expand Down
3 changes: 0 additions & 3 deletions test/grizzly/command_class/command_class_version_test.exs

This file was deleted.

86 changes: 86 additions & 0 deletions test/grizzly/command_class/version/get_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
defmodule Grizzly.CommandClass.Version.GetTest do
use ExUnit.Case, async: true

alias Grizzly.Packet
alias Grizzly.CommandClass.Version
alias Grizzly.CommandClass.Version.Get

describe "implements Grizzly.Command behaviour" do
test "initializes command state" do
assert {:ok, %Get{}} = Get.init([])
end

test "encodes correctly" do
{:ok, command} = Get.init(seq_number: 0x05, command_class: :switch_binary)

binary = <<35, 2, 128, 208, 5, 0, 0, 3, 2, 0, 0x86, 0x11>>

assert {:ok, binary} == Get.encode(command)
end

test "handles ack response" do
{:ok, command} = Get.init(seq_number: 0x05)
packet = Packet.new(seq_number: 0x05, types: [:ack_response])

assert {:continue, ^command} = Get.handle_response(command, packet)
end

test "handles nack response" do
{:ok, command} = Get.init(seq_number: 0x05, retries: 0)

packet = Packet.new(seq_number: 0x05, types: [:nack_response])
assert {:done, {:error, :nack_response}} == Get.handle_response(command, packet)
end

test "handles retries" do
{:ok, command} = Get.init(seq_number: 0x05)
packet = Packet.new(seq_number: 0x05, types: [:nack_response])

assert {:retry, %Get{retries: 1}} = Get.handle_response(command, packet)
end

test "handles command class version report response" do
report = %{
command_class: Version,
command: :version_report,
protocol_library: :controller,
protocol_version: 1,
protocol_sub_version: 1,
firmware_version: 1,
firmware_sub_version: 1
}

packet = Packet.new(body: report)
{:ok, command} = Get.init(seq_number: 0x05)

assert {:done, {:ok, Map.drop(report, [:command_class, :command])}} ==
Get.handle_response(command, packet)
end

test "handles queued for wake up nodes" do
{:ok, command} = Get.init(seq_number: 0x01, command_class: :switch_binary)

packet =
Packet.new(seq_number: 0x01, types: [:nack_response, :nack_waiting])
|> Packet.put_expected_delay(5000)

assert {:queued, ^command} = Get.handle_response(command, packet)
end

test "handles nack waiting when delay is 1 or less" do
{:ok, command} = Get.init(seq_number: 0x01)

packet =
Packet.new(seq_number: 0x01, types: [:nack_response, :nack_waiting])
|> Packet.put_expected_delay(1)

assert {:continue, ^command} = Get.handle_response(command, packet)
end

test "handles other response" do
{:ok, command} = Get.init(seq_number: 0x05)

assert {:continue, ^command} = Get.handle_response(command, %{command: :ice_cream})
end
end
end
63 changes: 63 additions & 0 deletions test/grizzly/command_class/version_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
defmodule Grizzly.CommandClass.Version.Test do
use ExUnit.Case, async: true

alias Grizzly.CommandClass.Version

describe "decoding library types" do
test "static controller" do
assert {:ok, :static_controller} == Version.decode_library_type(0x01)
end

test "controller" do
assert {:ok, :controller} == Version.decode_library_type(0x02)
end

test "enhanced slave" do
assert {:ok, :enhanced_slave} == Version.decode_library_type(0x03)
end

test "slave" do
assert {:ok, :slave} == Version.decode_library_type(0x04)
end

test "installer" do
assert {:ok, :installer} == Version.decode_library_type(0x05)
end

test "routing slave" do
assert {:ok, :routing_slave} == Version.decode_library_type(0x06)
end

test "bridge controller" do
assert {:ok, :bridge_controller} == Version.decode_library_type(0x07)
end

test "device under test" do
assert {:ok, :device_under_test} == Version.decode_library_type(0x08)
end

test "av remote" do
assert {:ok, :av_remote} == Version.decode_library_type(0x0A)
end

test "av device" do
assert {:ok, :av_device} == Version.decode_library_type(0x0B)
end

test "invalid library type" do
assert {:error, :invalid_library_type, 0xCC} == Version.decode_library_type(0xCC)
end
end

test "decoding the version report" do
report = %{
protocol_library: :controller,
protocol_version: 1,
protocol_sub_version: 1,
firmware_version: 1,
firmware_sub_version: 1
}

assert {:ok, report} == Version.decode_version_report(<<0x02, 0x01, 0x01, 0x01, 0x01>>)
end
end
Loading

0 comments on commit 5dc35a2

Please sign in to comment.