lib/grizzly/zwave/commands/node_add.ex

defmodule Grizzly.ZWave.Commands.NodeAdd do
  @moduledoc """
  Command for NODE_ADD

  Params:

    * `:seq_number` - the sequence number for the network command (required)
    * `:mode` - the inclusion mode (optional default `:node_add_any_s2`)
    * `:tx_opts` - the transmission options (optional default `:explore`)

  If your controller does not support the `NETWORK_MANAGEMENT_INCLUSION`
  version 2, you will ned to pass `:node_add_any` as the `:mode` parameter.

  This command should return the `NodeAddStatus` report after inclusion is
  complete.
  """
  @behaviour Grizzly.ZWave.Command

  alias Grizzly.ZWave.{Command, DecodeError}
  alias Grizzly.ZWave.CommandClasses.NetworkManagementInclusion

  @type mode :: :node_add_any | :node_add_stop | :node_add_any_s2

  @type tx_opt :: :null | :low_power | :explore

  @type param :: {:mode, mode()} | {:tx_opt, tx_opt()} | {:seq_number, Grizzly.seq_number()}

  @impl true
  @spec new([param]) :: {:ok, Command.t()}
  def new(params) do
    # TODO validate params
    command = %Command{
      name: :node_add,
      command_byte: 0x01,
      command_class: NetworkManagementInclusion,
      params: params,
      impl: __MODULE__
    }

    {:ok, command}
  end

  @impl true
  def encode_params(command) do
    seq_number = Command.param!(command, :seq_number)
    mode = Command.param(command, :mode, :node_add_s2_any)
    tx_opt = Command.param(command, :tx_opt, :explore)

    <<seq_number, 0x00, encode_mode(mode), encode_tx_opt(tx_opt)>>
  end

  @impl true
  def decode_params(<<seq_number, _reserved, mode_byte, tx_opt_byte>>) do
    with {:ok, mode} <- decode_mode(mode_byte),
         {:ok, tx_opt} <- decode_tx_opt(tx_opt_byte) do
      {:ok, [mode: mode, seq_number: seq_number, tx_opt: tx_opt]}
    end
  end

  @spec encode_mode(mode()) :: byte()
  def encode_mode(:node_add_any), do: 0x01
  def encode_mode(:node_add_stop), do: 0x05
  def encode_mode(:node_add_s2_any), do: 0x07

  @spec encode_tx_opt(tx_opt()) :: byte()
  def encode_tx_opt(:null), do: 0x00
  def encode_tx_opt(:low_power), do: 0x02
  def encode_tx_opt(:explore), do: 0x20

  @spec decode_mode(byte()) :: {:ok, mode()} | {:error, DecodeError.t()}
  def decode_mode(0x01), do: {:ok, :node_add_any}
  def decode_mode(0x05), do: {:ok, :node_add_stop}
  def decode_mode(0x07), do: {:ok, :node_add_s2_any}
  def decode_mode(byte), do: {:error, %DecodeError{value: byte, param: :mode, command: :node_add}}

  @spec decode_tx_opt(byte()) :: {:ok, tx_opt()} | {:error, DecodeError.t()}
  def decode_tx_opt(0x00), do: {:ok, :null}
  def decode_tx_opt(0x02), do: {:ok, :low_power}
  def decode_tx_opt(0x20), do: {:ok, :explore}

  def decode_tx_opt(byte),
    do: {:error, %DecodeError{value: byte, param: :tx_opt, command: :node_add}}
end