Packages
grizzly
0.12.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/connections/sync_connection.ex
defmodule Grizzly.Connections.SyncConnection do
@moduledoc false
# Module for establishing a "connection" to a Z-Wave Node
use GenServer
require Logger
alias Grizzly.{ZIPGateway, Connections}
alias Grizzly.Commands.CommandRunner
alias Grizzly.Connections.{KeepAlive, CommandList}
alias Grizzly.ZWave
alias Grizzly.ZWave.Command
alias Grizzly.ZWave.Commands.ZIPPacket
@type opt :: {:transport, module()}
@type send_opt :: {:timeout, non_neg_integer()} | {:retries, non_neg_integer()}
defmodule State do
@moduledoc false
defstruct transport: nil,
socket: nil,
commands: CommandList.empty(),
keep_alive: nil
end
def child_spec(node_id, opts \\ []) do
%{id: __MODULE__, start: {__MODULE__, :start_link, [node_id, opts]}, restart: :transient}
end
@spec start_link(ZWave.node_id(), [opt]) :: GenServer.on_start()
def start_link(node_id, opts \\ []) do
name = Connections.make_name(node_id)
GenServer.start_link(__MODULE__, [node_id, opts], name: name)
end
@spec send_command(ZWave.node_id() | pid(), Command.t(), [send_opt()]) ::
:ok | {:ok, Command.t()} | {:queued, reference(), Command.delay_seconds()}
def send_command(node_id, command, opts \\ []) do
name = Connections.make_name(node_id)
GenServer.call(name, {:send_command, command, opts}, 140_000)
end
@doc """
Close the connection
"""
@spec close(ZWave.node_id() | pid()) :: :ok
def close(node_id_or_pid) do
name = Connections.make_name(node_id_or_pid)
# when stop this process the socket port that is owned
# this process gets cleaned up for us.
GenServer.stop(name, :normal)
end
def init([node_id, opts]) do
host = ZIPGateway.host_for_node(node_id)
port = ZIPGateway.port()
transport = Connections.get_transport_from_opts(opts)
case transport.open(host, port) do
{:ok, socket} ->
{:ok, %State{socket: socket, transport: transport, keep_alive: KeepAlive.init(25_000)}}
{:error, :timeout} ->
{:stop, :timeout}
end
end
def handle_call({:send_command, command, command_opts}, from, state) do
{:ok, command_runner, _, new_command_list} =
CommandList.create(state.commands, command, from, command_opts)
case do_send_command(command_runner, state) do
:ok ->
{:noreply,
%State{
state
| commands: new_command_list,
keep_alive: KeepAlive.timer_restart(state.keep_alive)
}}
end
end
def handle_info(:keep_alive_tick, state) do
%State{keep_alive: keep_alive} = state
new_keep_alive =
keep_alive
|> KeepAlive.make_command()
|> KeepAlive.run(&do_send_command(&1, state))
{:noreply, %State{state | keep_alive: new_keep_alive}}
end
# handle when there is a timeout and command runner stops
def handle_info(
{:grizzly, :command_timeout, command_runner_pid, grizzly_command},
state
) do
if grizzly_command.source.name == :keep_alive do
{:noreply, state}
else
waiter = CommandList.get_waiter_for_runner(state.commands, command_runner_pid)
do_timeout_reply(waiter, grizzly_command)
{:noreply,
%State{
state
| commands: CommandList.drop_command_runner(state.commands, command_runner_pid)
}}
end
end
def handle_info(data, state) do
case state.transport.parse_response(data) do
{:ok, zip_packet} ->
_ = Logger.debug("Recv Z/IP Packet: #{inspect(zip_packet)}")
new_state = handle_commands(zip_packet, state)
{:noreply, new_state}
end
end
defp handle_commands(%Command{name: :keep_alive}, state) do
%State{state | keep_alive: KeepAlive.timer_restart(state.keep_alive)}
end
defp handle_commands(zip_packet, state) do
case Command.param!(zip_packet, :flag) do
:ack_request ->
## TODO clean up
# Something we will get a UDP ping from the controller that requests
# use to respond back, this handles that. This is mostly used
# when there are messages in the Z/IP Gateway's mailbox to unsure
# there is still someone waiting for the queued command.
header_extensions = Command.param!(zip_packet, :header_extensions)
seq_number = Command.param!(zip_packet, :seq_number)
secure = Command.param!(zip_packet, :secure)
{:ok, ack_response} =
ZIPPacket.new(
secure: secure,
header_extensions: header_extensions,
seq_number: seq_number,
flag: :ack_response
)
binary = ZWave.to_binary(ack_response)
state.transport.send(state.socket, binary)
state
_ ->
do_handle_commands(zip_packet, state)
end
end
defp do_handle_commands(zip_packet, state) do
updated_state =
case CommandList.response_for_zip_packet(state.commands, zip_packet) do
{:retry, command_runner, new_comamnd_list} ->
:ok = do_send_command(command_runner, state)
%State{state | commands: new_comamnd_list}
{:continue, new_comamnd_list} ->
%State{state | commands: new_comamnd_list}
{waiter, {:error, :nack_response, new_comamnd_list}} ->
GenServer.reply(waiter, {:error, :nack_response})
%State{state | commands: new_comamnd_list}
{waiter, {:queued_complete, ref, response, new_command_list}} ->
send(waiter, {:grizzly, :queued_command_response, ref, response})
%State{state | commands: new_command_list}
{waiter, {:queued_ping, ref, queued_seconds, new_comamnd_list}} ->
send(waiter, {:grizzly, :queued_ping, ref, queued_seconds})
%State{state | commands: new_comamnd_list}
{waiter, {:queued, ref, queued_seconds, new_comamnd_list}} ->
GenServer.reply(waiter, {:queued, ref, queued_seconds})
%State{state | commands: new_comamnd_list}
{waiter, {:complete, response, new_command_list}} ->
GenServer.reply(waiter, response)
%State{state | commands: new_command_list}
end
%State{updated_state | keep_alive: KeepAlive.timer_restart(state.keep_alive)}
end
defp do_send_command(command_runner, state) do
binary = CommandRunner.encode_command(command_runner)
state.transport.send(state.socket, binary)
end
defp do_timeout_reply(waiter, grizzly_command) do
response = {:error, :timeout}
if grizzly_command.status == :queued do
{pid, _tag} = waiter
send(pid, {:grizzly, :queued_command_response, grizzly_command.ref, response})
else
GenServer.reply(waiter, response)
end
end
end