lib/grizzly/zwave/commands/supervision_report.ex

defmodule Grizzly.ZWave.Commands.SupervisionReport do
  @moduledoc """
  This command is used to advertise the status of one or more command process(es).

  Params:

    * `:more_status_updates` - used to advertise if more Supervision Reports follow for the actual Session ID (required)
    * `:session_id` - carries the same value as the Session ID field of the Supervision Get Command which
                      initiated this session (required)
    * `:status` - the current status of the command process, one of :no_support, :working, :fail or :success (required)
    * `:duration` - the time in seconds needed to complete the current operation (required)
  """

  @behaviour Grizzly.ZWave.Command

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

  @type more_status_updates() :: :last_report | :more_reports
  @type status() :: :no_support | :working | :fail | :success
  @type param() ::
          {:more_status_updates, more_status_updates}
          | {:status, status}
          | {:duration, :unknown | non_neg_integer()}
          | {:session_id, byte()}

  @impl true
  @spec new([param()]) :: {:ok, Command.t()}
  def new(params) do
    command = %Command{
      name: :supervision_report,
      command_byte: 0x02,
      command_class: Supervision,
      params: params,
      impl: __MODULE__
    }

    {:ok, command}
  end

  @impl true
  def encode_params(command) do
    more_status_updates_bit =
      Command.param!(command, :more_status_updates) |> encode_more_status_updates()

    status_byte = Command.param!(command, :status) |> encode_status()
    duration_byte = Command.param!(command, :duration) |> encode_duration()
    session_id = Command.param!(command, :session_id)

    <<more_status_updates_bit::size(1), 0x00::size(1), session_id::size(6), status_byte,
      duration_byte>>
  end

  @impl true
  def decode_params(
        <<more_status_updates_byte::size(1), _::size(1), session_id::size(6), status_byte,
          duration_byte>>
      ) do
    with {:ok, more_status_updates} <- decode_more_status_updates(more_status_updates_byte),
         {:ok, status} <- decode_status(status_byte),
         {:ok, duration} <- decode_duration(duration_byte) do
      {:ok,
       [
         more_status_updates: more_status_updates,
         session_id: session_id,
         status: status,
         duration: duration
       ]}
    else
      {:error, %DecodeError{} = error} ->
        error
    end
  end

  defp encode_more_status_updates(:last_report), do: 0x00
  defp encode_more_status_updates(:more_reports), do: 0x01

  defp decode_more_status_updates(0x00), do: {:ok, :last_report}
  defp decode_more_status_updates(0x01), do: {:ok, :more_reports}

  defp decode_status(0x00), do: {:ok, :no_support}
  defp decode_status(0x01), do: {:ok, :working}
  defp decode_status(0x02), do: {:ok, :fail}
  defp decode_status(0xFF), do: {:ok, :success}

  defp decode_status(byte),
    do: {:error, %DecodeError{value: byte, param: :status, command: :supervision_report}}

  defp encode_status(:no_support), do: 0x00
  defp encode_status(:working), do: 0x01
  defp encode_status(:fail), do: 0x02
  defp encode_status(:success), do: 0xFF

  defp encode_duration(secs) when secs in 0..127, do: secs
  defp encode_duration(secs) when secs in 128..(126 * 60), do: round(secs / 60) + 0x7F
  defp encode_duration(:unknown), do: 0xFE

  defp decode_duration(byte) when byte in 0x00..0x7F, do: {:ok, byte}
  defp decode_duration(byte) when byte in 0x80..0xFD, do: {:ok, (byte - 0x7F) * 60}
  defp decode_duration(0xFE), do: :unknown

  defp decode_duration(byte),
    do: {:error, %DecodeError{value: byte, param: :duration, command: :supervision_report}}
end