Packages
grizzly
5.2.6
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.{Transport, ZIPGateway, Connections, Options, Report}
alias Grizzly.Commands.CommandRunner
alias Grizzly.Connections.{KeepAlive, CommandList}
alias Grizzly.ZWave
alias Grizzly.ZWave.Command
alias Grizzly.ZWave.Commands.ZIPPacket
@type send_opt() :: {:timeout, non_neg_integer()} | {:retries, non_neg_integer()}
defmodule State do
@moduledoc false
defstruct transport: nil,
commands: CommandList.empty(),
keep_alive: nil,
node_id: nil
end
def child_spec(node_id, opts \\ []) do
%{id: __MODULE__, start: {__MODULE__, :start_link, [node_id, opts]}, restart: :transient}
end
@doc """
Start connection to a device or the Z/IP Gateway
"""
@spec start_link(Options.t(), ZWave.node_id() | :gateway, [Grizzly.command_opt()]) ::
GenServer.on_start()
def start_link(grizzly_options, node_id_or_gateway, opts \\ []) do
name = Connections.make_name(node_id_or_gateway)
GenServer.start_link(__MODULE__, [grizzly_options, node_id_or_gateway, opts], name: name)
end
@spec send_command(ZWave.node_id() | pid(), Command.t(), [send_opt()]) ::
Grizzly.send_command_response()
def send_command(node_id, command, opts \\ []) do
name = Connections.make_name(node_id)
GenServer.call(name, {:send_command, command, node_id, 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
@impl GenServer
def init([grizzly_options, node_id_or_gateway, _opts]) do
host = ZIPGateway.host_for_node(node_id_or_gateway, grizzly_options)
transport_impl = grizzly_options.transport
transport_opts = [
ip_address: host,
port: grizzly_options.zipgateway_port
]
case Transport.open(transport_impl, transport_opts) do
{:ok, transport} ->
{:ok,
%State{
transport: transport,
keep_alive: KeepAlive.init(node_id_or_gateway, 25_000),
node_id: node_id_or_gateway
}}
{:error, reason} ->
{:stop, reason}
end
end
@impl GenServer
def handle_call({:send_command, command, node_id, command_opts}, from, state) do
{:ok, command_runner, _, new_command_list} =
CommandList.create(state.commands, command, node_id, 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
@impl GenServer
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, trace: false))
{: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
%State{transport: transport, node_id: node_id} = state
case Transport.parse_response(transport, data) do
{:ok, :connection_closed} ->
Logger.debug("[Grizzly] connection to node #{inspect(node_id)} closed")
{:stop, :normal, state}
{:ok, transport_response} ->
new_state = handle_commands(transport_response.command, state)
{:noreply, new_state}
{:error, error} ->
error_message = Exception.message(error)
Logger.warn("[Grizzly] #{inspect(error_message)}")
{:noreply, 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
Logger.debug("Recv Z/IP Packet: #{inspect(zip_packet)}")
case Command.param!(zip_packet, :flag) do
:ack_request ->
handle_ack_request(zip_packet, state)
_ ->
do_handle_commands(zip_packet, state)
end
end
defp handle_ack_request(zip_packet, state) do
%State{transport: transport} = state
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)
Transport.send(transport, binary)
if Command.param!(zip_packet, :command) != nil do
do_handle_commands(zip_packet, state)
else
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_command_list} ->
:ok = do_send_command(command_runner, state)
%State{state | commands: new_command_list}
{:continue, new_command_list} ->
%State{state | commands: new_command_list}
{waiter, {:error, :nack_response, new_command_list}} ->
GenServer.reply(waiter, {:error, :nack_response})
%State{state | commands: new_command_list}
{waiter, {%Report{} = report, new_command_list}} when is_pid(waiter) ->
send(waiter, {:grizzly, :report, report})
%State{state | commands: new_command_list}
{waiter, {%Report{} = report, new_command_list}} ->
GenServer.reply(waiter, {:ok, report})
%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, opts \\ []) do
%State{transport: transport} = state
binary = CommandRunner.encode_command(command_runner)
Transport.send(transport, binary, opts)
end
defp do_timeout_reply(waiter, grizzly_command) do
if grizzly_command.status == :queued do
{pid, _tag} = waiter
report =
Report.new(:complete, :timeout, grizzly_command.node_id,
command_ref: grizzly_command.ref,
queued: true
)
send(pid, {:grizzly, :report, report})
else
report =
Report.new(:complete, :timeout, grizzly_command.node_id, command_ref: grizzly_command.ref)
GenServer.reply(waiter, {:ok, report})
end
end
end