Packages
grizzly
8.2.2
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/connections/command_list.ex
defmodule Grizzly.Connections.CommandList do
@moduledoc false
# A list of running command processes and their waiter's for the
# connections to track
#### TODO separate out keep alive stuff ####
alias Grizzly.{Commands, Report, ZWave}
alias Grizzly.Commands.CommandRunner
alias Grizzly.ZWave.Command, as: ZWaveCommand
# this command is the process or GenServer waiting for a response
# from the command
@type command_waiter :: pid() | GenServer.from()
@type command_list_item :: {pid(), command_waiter(), reference()}
@type t :: %__MODULE__{
commands: [command_list_item()],
keep_alive_command: ZWaveCommand.t() | nil
}
# right now using a list but can probably use an erlang array for a
# better implementation long term
defstruct commands: [], keep_alive_command: nil
def empty(), do: %__MODULE__{}
def to_list(command_list), do: command_list.commands
@doc """
Create the command runtime and update the list of commands
Returns `{:ok, command_runner_pid, command_reference, new_command_list}`
"""
@spec create(t(), ZWaveCommand.t(), ZWave.node_id(), command_waiter(), keyword()) ::
{:ok, pid(), reference(), t()}
def create(command_list, command, node_id, waiter, command_opts \\ []) do
case new_command(command_list, command, node_id, waiter, command_opts) do
{:ok, command_runner, reference, new_command_list} ->
{:ok, command_runner, reference, new_command_list}
end
end
@spec response_for_zip_packet(t(), ZWaveCommand.t()) ::
{:continue, t()}
| {:retry, command_runner :: pid(), t()}
| {command_waiter(), {Report.t(), t()}}
| {command_waiter(), {:error, :nack_response, t()}}
def response_for_zip_packet(command_list, zip_packet) do
case get_response_for_command(command_list, zip_packet) do
{{:retry, command_runner}, command_list} ->
{:retry, command_runner, %__MODULE__{commands: command_list}}
{:continue, command_list} ->
{:continue, %__MODULE__{commands: command_list}}
{{%Report{queued: true, status: :inflight, type: :queued_ping} = report, command},
command_list} ->
waiter = waiter_as_pid(command_waiter(command))
{waiter, {report, %__MODULE__{commands: command_list}}}
{{%Report{queued: true, status: :complete} = report, command}, command_list} ->
waiter = waiter_as_pid(command_waiter(command))
{waiter, {report, %__MODULE__{commands: command_list}}}
{{%Report{} = report, command}, command_list} ->
waiter = command_waiter(command)
{waiter, {report, %__MODULE__{commands: command_list}}}
{nil, command_list} ->
{:continue, %__MODULE__{commands: command_list}}
# if commands that are queued, nacked, or complete will not have the command
# back in the commands and will will return who to send the response to
{{response, data, command}, new_command_list} ->
waiter = command_waiter(command)
{waiter, {response, data, %__MODULE__{commands: new_command_list}}}
end
end
@doc """
Get the waiter for the command runner
"""
@spec get_waiter_for_runner(t(), command_runner_pid :: pid()) :: command_waiter()
def get_waiter_for_runner(command_list, runner_pid) do
case find_item_for_runner(command_list, runner_pid) do
{^runner_pid, waiter, _ref} -> waiter
end
end
@doc """
Drop the item for the command runner
"""
@spec drop_command_runner(t(), command_runner_pid :: pid()) :: t()
def drop_command_runner(command_list, runner_pid) do
new_commands =
Enum.reject(command_list.commands, fn {test_runner_pid, _, _ref} ->
test_runner_pid == runner_pid
end)
%__MODULE__{commands: new_commands}
end
@doc """
This is useful to stop a command runner when you have a command
reference
"""
@spec stop_command_by_ref(t(), reference()) :: {:ok, t()}
def stop_command_by_ref(command_list, command_ref) do
case find_item_for_ref(command_list, command_ref) do
{runner, _waiter, _ref} ->
Commands.stop(runner)
{:ok, drop_command_runner(command_list, runner)}
end
end
@doc """
Check to see if the command is in the command list by the command
ref
"""
@spec has_command_ref?(t(), reference()) :: boolean()
def has_command_ref?(command_list, command_ref) do
case find_item_for_ref(command_list, command_ref) do
nil -> false
{_, _, ^command_ref} -> true
end
end
defp find_item_for_runner(command_list, runner_pid) do
Enum.find(command_list.commands, fn {test_runner_pid, _waiter, _ref} ->
test_runner_pid == runner_pid
end)
end
defp find_item_for_ref(command_list, command_ref) do
Enum.find(command_list.commands, fn {_, _, test_command_ref} ->
test_command_ref == command_ref
end)
end
defp get_response_for_command(command_list, zip_packet) do
Enum.reduce(command_list.commands, {nil, []}, fn
# if a command has already completed we don't need to commands anymore, so
# we just put this one back into the list in for future incoming commands
command, {{%Report{} = report, completed_command}, new_command_list} ->
{{report, completed_command}, [command | new_command_list]}
{command_runner, _command_waiter, _ref} = command, {_result, new_command_list} ->
case CommandRunner.handle_zip_command(command_runner, zip_packet) do
# if the command says to continue we put it back into the command list
:continue ->
{:continue, [command | new_command_list]}
# if the command says it has a nack_response, we remove it from the command list
{:error, :nack_response} ->
{{:error, :nack_response, command}, new_command_list}
# if the command says to retry we put it back into the command list
:retry ->
{{:retry, command_runner}, [command | new_command_list]}
# if the command has been queued we can keep holding onto the command
%Report{status: :inflight, queued: true} = report ->
{{report, command}, [command | new_command_list]}
%Report{} = report ->
{{report, command}, new_command_list}
end
end)
end
defp command_waiter({_command_runner, waiter, _command_ref}), do: waiter
defp waiter_as_pid(pid) when is_pid(pid), do: pid
defp waiter_as_pid({pid, _tag}), do: pid
defp new_command(command_list, command, node_id, waiter, command_opts) do
# only create a new reference if we are going to need it
command_ref = Keyword.get_lazy(command_opts, :reference, fn -> make_ref() end)
command_opts = Keyword.put_new(command_opts, :reference, command_ref)
command_opts = Keyword.put_new(command_opts, :waiter, waiter)
case Commands.create_command(command, node_id, command_opts) do
{:ok, command_runner} ->
{:ok, command_runner, command_ref,
put_command(command_list, command_runner, waiter, command_ref)}
end
end
defp put_command(command_list, command, waiter, reference) do
%__MODULE__{command_list | commands: [{command, waiter, reference} | command_list.commands]}
end
end