lib/grizzly/zwave/commands/alarm_report.ex

defmodule Grizzly.ZWave.Commands.AlarmReport do
  @moduledoc """
  This command notifies the application of the alarm state (v1, v2) or the
  notification state (v8).

  Params:

    * `:type` - the specific alarm type being reported - this different for
      each application, see device user manual for more details (required)
    * `:level` - the level is device specific, see device user manual for more
      details (required)
    * `:zensor_net_node_id` - the Zensor net node, if the device is not
     based off of Zensor Net, then this field is `0` (v2, optional, default 0)
    * `:zwave_status` - if the device alarm status is :enabled or :disabled (v2)
    * `:zwave_type` - part of `Grizzly.ZWave.Notifications` spec (v2)
    * `:zwave_event` - part of the `Grizzly.ZWave.Notifications` spec (v2)
    * `:event_parameters` - additional parameters for the event as keyword list,
      see user manual for more information (v2+, optional, default `[]`)
  """

  @behaviour Grizzly.ZWave.Command

  alias Grizzly.ZWave
  alias Grizzly.ZWave.{Command, DecodeError, Notifications}
  alias Grizzly.ZWave.CommandClasses.Alarm

  @type param ::
          {:type, byte()}
          | {:level, byte()}
          | {:zensor_net_node_id, ZWave.node_id()}
          | {:zwave_status, Notifications.status()}
          | {:zwave_event, Notifications.event()}
          | {:zwave_type, Notifications.type()}
          | {:sequence_number, byte()}
          | {:event_parameters, [byte()]}

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

    {:ok, command}
  end

  @impl true
  @spec encode_params(Command.t()) :: binary()
  def encode_params(command) do
    cond do
      Command.param(command, :sequence_number) != nil ->
        encode_v8(command)

      Command.param(command, :zensor_net_node_id) != nil ->
        encode_v2(command)

      true ->
        encode_v1(command)
    end
  end

  @impl true
  @spec decode_params(binary()) :: {:ok, [param()]} | {:error, DecodeError.t()}
  def decode_params(<<type, level>>) do
    {:ok, [type: type, level: level]}
  end

  def decode_params(<<
        type,
        level,
        zensor_node_id,
        status_byte,
        zwave_type_byte,
        zwave_event_byte,
        0x00::size(1),
        0x00::size(2),
        params_length::size(5),
        event_params::binary-size(params_length),
        # Sometimes a 0 seq_number is added
        _::binary
      >>) do
    with {:ok, zwave_type} <- Notifications.type_from_byte(zwave_type_byte),
         {:ok, zwave_event} <- Notifications.event_from_byte(zwave_type, zwave_event_byte),
         {:ok, zwave_status} <- Notifications.status_from_byte(status_byte),
         {:ok, event_parameters} <-
           Notifications.decode_event_params(zwave_type, zwave_event, event_params) do
      {:ok,
       [
         type: type,
         level: level,
         zensor_net_node_id: zensor_node_id,
         zwave_status: zwave_status,
         zwave_type: zwave_type,
         zwave_event: zwave_event,
         event_parameters: event_parameters
       ]}
    else
      {:error, :invalid_type_byte} ->
        {:error, %DecodeError{value: zwave_type_byte, param: :zwave_type, command: :alarm_report}}

      {:error, :invalid_event_byte} ->
        {:error,
         %DecodeError{value: zwave_event_byte, param: :zwave_event, command: :alarm_report}}

      error ->
        error
    end
  end

  def decode_params(
        <<type, level, zensor_node_id, status_byte, zwave_type_byte, zwave_event_byte,
          0x01::size(1), 0x00::size(2), params_length::size(5),
          event_params::binary-size(params_length), sequence_number>>
      ) do
    with {:ok, zwave_type} <- Notifications.type_from_byte(zwave_type_byte),
         {:ok, zwave_event} <- Notifications.event_from_byte(zwave_type, zwave_event_byte),
         {:ok, zwave_status} <- Notifications.status_from_byte(status_byte),
         {:ok, event_parameters} <-
           Notifications.decode_event_params(zwave_type, zwave_event, event_params) do
      {:ok,
       [
         type: type,
         level: level,
         zensor_net_node_id: zensor_node_id,
         zwave_status: zwave_status,
         zwave_type: zwave_type,
         zwave_event: zwave_event,
         event_parameters: event_parameters,
         sequence_number: sequence_number
       ]}
    else
      {:error, :invalid_type_byte} ->
        {:error, %DecodeError{value: zwave_type_byte, param: :zwave_type, command: :alarm_report}}

      {:error, :invalid_event_byte} ->
        {:error,
         %DecodeError{value: zwave_event_byte, param: :zwave_event, command: :alarm_report}}

      error ->
        error
    end
  end

  # Params sent by Schlage 468ZP 
  def decode_params(
        <<type, level, zensor_node_id, status_byte, zwave_type_byte, zwave_event_byte>>
      ) do
    with {:ok, zwave_type} <- Notifications.type_from_byte(zwave_type_byte),
         {:ok, zwave_event} <- Notifications.event_from_byte(zwave_type, zwave_event_byte),
         {:ok, zwave_status} <- Notifications.status_from_byte(status_byte) do
      {:ok,
       [
         type: type,
         level: level,
         zensor_net_node_id: zensor_node_id,
         zwave_status: zwave_status,
         zwave_type: zwave_type,
         zwave_event: zwave_event,
         event_parameters: []
       ]}
    else
      {:error, :invalid_type_byte} ->
        {:error, %DecodeError{value: zwave_type_byte, param: :zwave_type, command: :alarm_report}}

      {:error, :invalid_event_byte} ->
        {:error,
         %DecodeError{value: zwave_event_byte, param: :zwave_event, command: :alarm_report}}

      error ->
        error
    end
  end

  defp encode_v1(command) do
    type = Command.param!(command, :type)
    level = Command.param!(command, :level)

    <<type, level>>
  end

  defp encode_v2(command) do
    type = Command.param!(command, :type)
    level = Command.param!(command, :level)
    zensor_node_id = Command.param!(command, :zensor_net_node_id)
    zwave_status = Command.param!(command, :zwave_status)
    zwave_type = Command.param!(command, :zwave_type)
    zwave_event = Command.param!(command, :zwave_event)
    event_params = Command.param!(command, :event_parameters)

    encoded_event_params =
      Notifications.encode_event_params(zwave_type, zwave_event, event_params)

    params_length = byte_size(encoded_event_params)

    <<type, level, zensor_node_id, Notifications.status_to_byte(zwave_status),
      Notifications.type_to_byte(zwave_type),
      Notifications.event_to_byte(zwave_type, zwave_event),
      params_length>> <>
      encoded_event_params
  end

  defp encode_v8(command) do
    type = Command.param!(command, :type)
    level = Command.param!(command, :level)
    zwave_status = Command.param!(command, :zwave_status)
    zwave_type = Command.param!(command, :zwave_type)
    zwave_event = Command.param!(command, :zwave_event)
    sequence_number = Command.param!(command, :sequence_number)
    event_params = Command.param!(command, :event_parameters)

    encoded_event_params =
      Notifications.encode_event_params(zwave_type, zwave_event, event_params)

    params_length = byte_size(encoded_event_params)

    <<type, level, 0x00, Notifications.status_to_byte(zwave_status),
      Notifications.type_to_byte(zwave_type),
      Notifications.event_to_byte(zwave_type, zwave_event), 0x01::size(1), 0x00::size(2),
      params_length::size(5)>> <>
      encoded_event_params <>
      <<sequence_number>>
  end

  # TODO - Actually translate into report commands
  # defp params_from_binary(<<0>>), do: []
  # defp params_from_binary(params_bin), do: :erlang.binary_to_list(params_bin)

  defp build_params(params) do
    if Keyword.has_key?(params, :zwave_type) do
      Keyword.merge([zensor_net_node_id: 0, event_parameters: []], params)
    else
      params
    end
  end
end