From b118a6ec51a34ac11b2a5e9f981d349a5d47d1fa Mon Sep 17 00:00:00 2001 From: "jf@collaboration-planners.com" Date: Mon, 26 Aug 2019 10:28:32 -0400 Subject: [PATCH] Support NoOperation command class Fixes SRHUB-122 --- .../no_operation/no_operation.ex | 76 +++++++++++++++++++ .../no_operation/no_operation_test.exs | 71 +++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 lib/grizzly/command_class/no_operation/no_operation.ex create mode 100644 test/grizzly/command_class/no_operation/no_operation_test.exs diff --git a/lib/grizzly/command_class/no_operation/no_operation.ex b/lib/grizzly/command_class/no_operation/no_operation.ex new file mode 100644 index 00000000..d4d04f80 --- /dev/null +++ b/lib/grizzly/command_class/no_operation/no_operation.ex @@ -0,0 +1,76 @@ +defmodule Grizzly.CommandClass.NoOperation do + @moduledoc """ + Command module to work with the NO_OPERATION command class + + Command Options: + + * `:seq_number` - The sequence number of the Z/IP Packet + * `:retries` - The number of times to try to send the command (default 2) + """ + @behaviour Grizzly.Command + + alias Grizzly.Packet + + @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 + binary = Packet.header(seq_number) <> <<0x00>> + + {:ok, binary} + end + + @spec handle_response(t, Packet.t()) :: + {:continue, t} | {:done, {:error, :nack_response}} | {:done, :ok} | {:retry, t} + def handle_response(%__MODULE__{seq_number: seq_number}, %Packet{ + seq_number: seq_number, + types: [:ack_response] + }) do + {:done, :ok} + 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( + %__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 diff --git a/test/grizzly/command_class/no_operation/no_operation_test.exs b/test/grizzly/command_class/no_operation/no_operation_test.exs new file mode 100644 index 00000000..eea5e854 --- /dev/null +++ b/test/grizzly/command_class/no_operation/no_operation_test.exs @@ -0,0 +1,71 @@ +defmodule Grizzly.CommandClass.NetworkManagementInstallationMaintenance.NoOperationTest do + use ExUnit.Case, async: true + + alias Grizzly.Packet + alias Grizzly.CommandClass.NoOperation + + describe "implements Grizzly.Command behaviour" do + test "initializes to the correct command state" do + assert {:ok, %NoOperation{}} = NoOperation.init([]) + end + + test "encodes correctly" do + {:ok, command} = NoOperation.init(seq_number: 0x08) + binary = <<35, 2, 128, 208, 8, 0, 0, 3, 2, 0, 0x00>> + + assert {:ok, binary} == NoOperation.encode(command) + end + + test "handles ack response" do + {:ok, command} = NoOperation.init(seq_number: 0x10) + packet = Packet.new(seq_number: 0x10, types: [:ack_response]) + + assert {:done, :ok} = NoOperation.handle_response(command, packet) + end + + test "handles nack response" do + {:ok, command} = NoOperation.init(seq_number: 0x10, retries: 0) + packet = Packet.new(seq_number: 0x10, types: [:nack_response]) + + assert {:done, {:error, :nack_response}} == + NoOperation.handle_response(command, packet) + end + + test "handles retries" do + {:ok, command} = NoOperation.init(seq_number: 0x10) + packet = Packet.new(seq_number: 0x10, types: [:nack_response]) + + assert {:retry, _command} = NoOperation.handle_response(command, packet) + end + + test "handles queued for wake up nodes" do + {:ok, command} = NoOperation.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} = NoOperation.handle_response(command, packet) + end + + test "handles nack waiting when delay is 1 or less" do + {:ok, command} = NoOperation.init(seq_number: 0x01) + + packet = + Packet.new(seq_number: 0x01, types: [:nack_response, :nack_waiting]) + |> Packet.put_expected_delay(1) + + assert {:continue, ^command} = NoOperation.handle_response(command, packet) + end + + test "handles response" do + {:ok, command} = NoOperation.init([]) + + assert {:continue, %NoOperation{}} == + NoOperation.handle_response( + command, + %{command_class: :door_lock, value: :foo, command: :report} + ) + end + end +end