lib/grizzly/zwave/command_classes/barrier_operator.ex

defmodule Grizzly.ZWave.CommandClasses.BarrierOperator do
  @moduledoc """
  "BarrierOperator" Command Class

  The Barrier Operator Command Class is used to control and query the status of motorized barriers.
  """

  @behaviour Grizzly.ZWave.CommandClass

  alias Grizzly.ZWave.DecodeError

  use Bitwise

  @type target_value :: :open | :close
  @type state :: :closed | 0x01..0x63 | :closing | :stopped | :opening | :open
  @type subsystem_type :: :audible_notification | :visual_notification
  @type subsystem_state :: :on | :off

  @impl Grizzly.ZWave.CommandClass
  def byte(), do: 0x66

  @impl Grizzly.ZWave.CommandClass
  def name(), do: :barrier_operator

  @spec target_value_to_byte(:close | :open) :: 0x00 | 0xFF
  def target_value_to_byte(:close), do: 0x00
  def target_value_to_byte(:open), do: 0xFF

  @spec target_value_from_byte(byte) ::
          {:error, Grizzly.ZWave.DecodeError.t()} | {:ok, :close | :open}
  def target_value_from_byte(0x00), do: {:ok, :close}
  def target_value_from_byte(0xFF), do: {:ok, :open}
  def target_value_from_byte(byte), do: {:error, %DecodeError{value: byte, param: :target_value}}

  @spec state_to_byte(state) :: byte
  def state_to_byte(:closed), do: 0x00
  def state_to_byte(:closing), do: 0xFC
  def state_to_byte(:stopped), do: 0xFD
  def state_to_byte(:opening), do: 0xFE
  def state_to_byte(:open), do: 0xFF
  def state_to_byte(stopped_position) when stopped_position in 0x01..0x63, do: stopped_position

  @spec state_from_byte(byte) ::
          {:error, Grizzly.ZWave.DecodeError.t()}
          | {:ok, state}
  def state_from_byte(0x00), do: {:ok, :closed}
  def state_from_byte(0xFC), do: {:ok, :closing}
  def state_from_byte(0xFD), do: {:ok, :stopped}
  def state_from_byte(0xFE), do: {:ok, :opening}
  def state_from_byte(0xFF), do: {:ok, :open}
  def state_from_byte(byte) when byte in 0x01..0x63, do: {:ok, byte}
  def state_from_byte(byte), do: {:error, %DecodeError{value: byte, param: :state}}

  @doc "Converts subsystems into a bytes"
  @spec subsystem_types_to_bitmask([subsystem_type]) :: byte
  def subsystem_types_to_bitmask(subsystem_types) do
    subsystem_type_bytes =
      for subsystem_type <- subsystem_types, do: subsystem_type_to_byte(subsystem_type)

    integer = Enum.reduce(subsystem_type_bytes, 0x00, fn byte, acc -> acc ||| byte end)
    <<byte>> = <<integer>>
    byte
  end

  def bitmask_to_subsystem_types(byte) do
    bitmask = <<byte>>

    bits_on =
      for(<<x::1 <- bitmask>>, do: x)
      |> Enum.reverse()
      |> Enum.with_index(1)
      |> Enum.reduce([], fn {bit, index}, acc ->
        if bit == 1, do: [index | acc], else: acc
      end)

    Enum.reduce(bits_on, [], fn bit_on, acc ->
      case subsystem_type_from_byte(bit_on) do
        {:ok, subsystem_type} -> [subsystem_type | acc]
        _other -> acc
      end
    end)
  end

  @spec subsystem_type_to_byte(:audible_notification | :visual_notification) :: 0x01 | 0x02
  def subsystem_type_to_byte(:audible_notification), do: 0x01
  def subsystem_type_to_byte(:visual_notification), do: 0x02

  @spec subsystem_type_from_byte(any) ::
          {:error, Grizzly.ZWave.DecodeError.t()}
          | {:ok, subsystem_type}
  def subsystem_type_from_byte(0x01), do: {:ok, :audible_notification}
  def subsystem_type_from_byte(0x02), do: {:ok, :visual_notification}

  def subsystem_type_from_byte(byte),
    do: {:error, %DecodeError{value: byte, param: :subsystem_type}}

  @spec subsystem_state_to_byte(:off | :on) :: 0x00 | 0xFF
  def subsystem_state_to_byte(:off), do: 0x00
  def subsystem_state_to_byte(:on), do: 0xFF

  @spec subsystem_state_from_byte(byte) ::
          {:error, Grizzly.ZWave.DecodeError.t()} | {:ok, subsystem_state}
  def subsystem_state_from_byte(0x00), do: {:ok, :off}
  def subsystem_state_from_byte(0xFF), do: {:ok, :on}

  def subsystem_state_from_byte(byte),
    do: {:error, %DecodeError{value: byte, param: :subsystem_state}}
end