Packages
grizzly
0.10.3
9.1.4
9.1.2
9.1.1
9.1.0
9.0.0
8.15.3
8.15.2
8.15.1
8.15.0
8.14.0
8.13.0
8.12.0
8.11.3
8.11.2
8.11.1
8.11.0
8.10.0
8.9.0
8.8.1
8.8.0
8.7.1
8.7.0
8.6.12
8.6.11
8.6.10
8.6.9
8.6.8
8.6.7
retired
8.6.6
8.6.5
8.6.4
8.6.3
8.6.2
8.6.1
8.6.0
8.5.3
8.5.2
8.5.1
8.5.0
8.4.0
8.3.0
8.2.3
8.2.2
8.2.1
8.2.0
8.1.0
8.0.1
8.0.0
7.4.3
7.4.2
7.4.1
7.4.0
7.3.0
7.2.0
7.1.4
7.1.3
7.1.2
7.1.1
7.1.0
7.0.4
7.0.3
7.0.2
7.0.1
7.0.0
6.8.8
6.8.7
6.8.6
6.8.5
6.8.4
6.8.3
6.8.2
6.8.1
6.8.0
6.7.1
6.7.0
6.6.1
6.6.0
6.5.1
6.5.0
6.4.0
6.3.0
6.2.0
6.1.1
6.1.0
6.0.1
6.0.0
5.4.1
5.4.0
5.3.0
5.2.8
5.2.7
5.2.6
5.2.5
5.2.4
5.2.3
5.2.2
5.2.1
5.2.0
5.1.2
5.1.1
5.1.0
5.0.2
5.0.1
5.0.0
4.0.1
4.0.0
3.0.0
2.1.0
2.0.0
1.0.1
1.0.0
0.22.7
0.22.6
0.22.5
0.22.4
0.22.3
0.22.2
0.22.1
0.22.0
0.21.1
0.21.0
0.20.2
0.20.1
0.20.0
0.19.1
0.19.0
0.18.3
0.18.2
0.18.1
0.18.0
0.17.7
0.17.6
0.17.5
0.17.4
0.17.3
0.17.2
0.17.1
0.17.0
0.16.2
0.16.1
0.16.0
0.15.11
0.15.10
0.15.9
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.8
0.14.7
0.14.6
0.14.5
0.14.4
0.14.3
0.14.2
0.14.1
0.14.0
0.13.0
0.12.3
0.12.2
0.12.1
0.12.0
0.11.0
0.10.3
0.10.2
0.10.1
0.10.0
0.9.0
0.9.0-rc.4
0.9.0-rc.3
0.9.0-rc.2
0.9.0-rc.1
0.9.0-rc.0
0.8.8
0.8.7
0.8.6
0.8.5
0.8.4
0.8.3
0.8.2
0.8.1
0.8.0
0.7.0
0.6.6
0.6.5
0.6.4
0.6.3
0.6.2
0.6.1
0.6.0
0.5.0
0.4.3
0.4.2
Elixir Z-Wave library
Current section
Files
Jump to
Current section
Files
lib/grizzly/commands/command.ex
defmodule Grizzly.Commands.Command do
@moduledoc false
# Data structure for working with Z-Wave commands as they relate to the
# Grizzly runtime
alias Grizzly.SeqNumber
alias Grizzly.Commands.Table
alias Grizzly.ZWave.Command, as: ZWaveCommand
alias Grizzly.ZWave.Commands.ZIPPacket
@type status :: :inflight | :queued | :complete
@type t :: %__MODULE__{
owner: pid(),
retries: non_neg_integer(),
source: ZWaveCommand.t(),
handler_state: any(),
handler: module(),
seq_number: Grizzly.seq_number(),
timeout_ref: reference() | nil,
ref: reference(),
status: status()
}
@type opt ::
{:timeout_ref, reference()} | {:reference, reference()} | {:retries, non_neg_integer()}
defstruct owner: nil,
retries: 2,
source: nil,
handler_state: nil,
handler: nil,
seq_number: nil,
timeout_ref: nil,
ref: nil,
status: :inflight
def from_zwave_command(zwave_command, owner, opts \\ []) do
{handler, handler_init_args} = get_handler_spec(zwave_command, opts)
{:ok, handler_state} = handler.init(handler_init_args)
retries = Keyword.get(opts, :retries, 2)
command_ref = Keyword.get(opts, :reference, make_ref())
timeout_ref = Keyword.get(opts, :timeout_ref)
%__MODULE__{
handler: handler,
handler_state: handler_state,
source: zwave_command,
owner: owner,
seq_number: get_seq_number(zwave_command),
timeout_ref: timeout_ref,
retries: retries,
ref: command_ref
}
end
@spec to_binary(t()) :: binary()
def to_binary(command) do
zwave_command = command.source
case zwave_command.name do
:keep_alive ->
<<zwave_command.command_class.byte(), zwave_command.command_byte>> <>
ZWaveCommand.encode_params(zwave_command)
_other ->
{:ok, zip_packet_command} =
ZIPPacket.with_zwave_command(zwave_command, command.seq_number,
seq_number: command.seq_number
)
ZWaveCommand.to_binary(zip_packet_command)
end
end
@spec handle_zip_command(t(), ZWaveCommand.t()) ::
{:continue, t()}
| {:error, :nack_response, t()}
| {:queued_complete, any(), t()}
| {:queued_ping, non_neg_integer(), t()}
| {:queued, non_neg_integer(), t()}
| {:complete, any(), t()}
| {:retry, t()}
def handle_zip_command(command, zip_command) do
case ZWaveCommand.param!(zip_command, :flag) do
:ack_response ->
handle_ack_response(command, zip_command)
:nack_response ->
handle_nack_response(command, zip_command)
:nack_waiting ->
handle_nack_waiting(command, zip_command)
flag when flag in [nil, :ack_request] ->
do_handle_zip_command(command, zip_command)
end
end
defp handle_ack_response(command, zip_packet) do
seq_number = ZWaveCommand.param!(zip_packet, :seq_number)
if command.seq_number == seq_number do
handle_ack_response(command)
else
{:continue, command}
end
end
defp handle_ack_response(command) do
case command.handler.handle_ack(command.handler_state) do
{:continue, new_handler_state} ->
{:continue, %__MODULE__{command | handler_state: new_handler_state}}
{:complete, _response} = result ->
build_complete_reply(command, result)
end
end
defp handle_nack_response(command, zip_packet) do
seq_number = ZWaveCommand.param!(zip_packet, :seq_number)
if command.seq_number == seq_number do
handle_nack_response(command)
else
{:continue, command}
end
end
defp handle_nack_response(%__MODULE__{retries: 0} = command),
do: {:error, :nack_response, command}
defp handle_nack_response(%__MODULE__{retries: n} = command),
do: {:retry, %__MODULE__{command | retries: n - 1}}
defp handle_nack_waiting(command, zip_packet) do
seq_number = ZWaveCommand.param!(zip_packet, :seq_number)
if command.seq_number == seq_number do
# SDS13784 Network specification states that a default of 90 seconds
# should be used if no expected delay is provided.
make_queued_response(command, zip_packet)
else
{:continue, command}
end
end
defp do_handle_zip_command(command, zip_packet_command) do
zwave_command = ZWaveCommand.param!(zip_packet_command, :command)
case command.handler.handle_command(zwave_command, command.handler_state) do
{:continue, new_handler_state} ->
{:continue, %__MODULE__{command | handler_state: new_handler_state}}
{:complete, _response} = result ->
build_complete_reply(command, result)
end
end
defp get_handler_spec(zwave_command, opts) do
case Keyword.get(opts, :handler) do
nil ->
format_handler_spec(Table.handler(zwave_command.name))
handler ->
format_handler_spec(handler)
end
end
defp format_handler_spec({_handler, _args} = spec), do: spec
defp format_handler_spec(handler), do: {handler, []}
defp get_seq_number(zwave_command) do
case ZWaveCommand.param(zwave_command, :seq_number) do
nil ->
SeqNumber.get_and_inc()
seq_number ->
seq_number
end
end
defp make_queued_response(command, zip_packet) do
case ZIPPacket.extension(zip_packet, :expected_delay, 90) do
delay when delay > 1 ->
make_queued_or_queued_ping_response(command, delay)
_other ->
{:continue, command}
end
end
defp make_queued_or_queued_ping_response(command, delay) do
case command.status do
:inflight ->
{:queued, delay, %__MODULE__{command | status: :queued}}
:queued ->
{:queued_ping, delay, command}
end
end
defp build_complete_reply(command, {:complete, result}) do
case command.status do
:inflight ->
{:complete, result, %__MODULE__{command | status: :complete}}
:queued ->
{:queued_complete, format_result(result), %__MODULE__{command | status: :complete}}
end
end
defp format_result({:ok, command}), do: command
defp format_result(:ok), do: :ok
end