lib/grizzly/zwave/commands/thermostat_setpoint_supported_report.ex

defmodule Grizzly.ZWave.Commands.ThermostatSetpointSupportedReport do
  @moduledoc """
  This command is used to report the thermostat's supported setpoint types.

  Params:

    * `:setpoint_types` - A list of supported setpoint types. See `t:Grizzly.ZWave.CommandClasses.ThermostatSetpoint.type/0`.

  """

  @behaviour Grizzly.ZWave.Command

  use Bitwise, only_operators: true

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

  @type param :: {:setpoint_types, [{ThermostatSetpoint.type(), boolean()}]}

  @impl Grizzly.ZWave.Command
  @spec new([param()]) :: {:ok, Command.t()}
  def new(params) do
    command = %Command{
      name: :thermostat_setpoint_supported_report,
      command_byte: 0x05,
      command_class: ThermostatSetpoint,
      params: params,
      impl: __MODULE__
    }

    {:ok, command}
  end

  @impl Grizzly.ZWave.Command
  @spec encode_params(Command.t()) :: binary()
  def encode_params(command) do
    setpoint_types = Command.param!(command, :setpoint_types)

    bit_masks =
      for byte_index <- 1..2 do
        for bit_index <- 7..0, into: <<>> do
          setpoint_type = bitmask_field_to_setpoint_type(byte_index, bit_index)

          if Keyword.get(setpoint_types, setpoint_type) == true,
            do: <<1::size(1)>>,
            else: <<0::size(1)>>
        end
      end

    for bit_mask <- bit_masks, into: <<>>, do: bit_mask
  end

  @impl Grizzly.ZWave.Command
  @spec decode_params(binary()) :: {:ok, [param()]} | {:error, DecodeError.t()}
  def decode_params(<<first_bitmask::size(8)>>),
    do: decode_params(<<first_bitmask, 0x0::size(8)>>)

  def decode_params(bitmasks) do
    bitmasks_as_indexed_list =
      :erlang.binary_to_list(bitmasks) |> Enum.take(2) |> Enum.with_index(1)

    setpoint_types =
      Enum.flat_map(bitmasks_as_indexed_list, fn {bitmask_byte, byte_index} ->
        for bit_index <- 0..7, into: [] do
          {bitmask_field_to_setpoint_type(byte_index, bit_index),
           (bitmask_byte &&& 1 <<< bit_index) !== 0}
        end
      end)
      |> Keyword.drop([nil])

    {:ok, [setpoint_types: setpoint_types]}
  end

  @spec bitmask_field_to_setpoint_type(pos_integer(), 0..7) ::
          ThermostatSetpoint.type() | :reserved
  defp bitmask_field_to_setpoint_type(byte_index, bit_index)

  defp bitmask_field_to_setpoint_type(1, 1), do: :heating
  defp bitmask_field_to_setpoint_type(1, 2), do: :cooling
  defp bitmask_field_to_setpoint_type(1, 3), do: :furnace
  defp bitmask_field_to_setpoint_type(1, 4), do: :dry_air
  defp bitmask_field_to_setpoint_type(1, 5), do: :moist_air
  defp bitmask_field_to_setpoint_type(1, 6), do: :auto_changeover
  defp bitmask_field_to_setpoint_type(1, 7), do: :energy_save_heating
  defp bitmask_field_to_setpoint_type(2, 0), do: :energy_save_cooling
  defp bitmask_field_to_setpoint_type(2, 1), do: :away_heating
  defp bitmask_field_to_setpoint_type(2, 2), do: :away_cooling
  defp bitmask_field_to_setpoint_type(2, 3), do: :full_power
  defp bitmask_field_to_setpoint_type(_, _), do: nil
end