defmodule Grizzly.Network do
@moduledoc """
Module for working with the Z-Wave network
"""
alias Grizzly.{Associations, Connections, Report, SeqNumber, VirtualDevices, ZWave}
alias Grizzly.ZWave.Command
@typedoc """
Options for when you want to reset the device
- `:notify` - if the flag is set to true this will try to notify any node that
is part of the lifeline association group (default `true`)
"""
@type reset_opt() :: {:notify, boolean()}
@type opt() :: {:node_id, ZWave.node_id()} | {:seq_number, integer()}
@doc """
Get a list of node ids from the Z-Wave network
Just because a node id might be in the list does not mean the node is on the
network. A device might have been reset or unpaired from the controller with
out the controller knowing. However, in most use cases this shouldn't be an
issue.
Options
* `:node_id` - If your controller is part of another controller's network
you might want to issue network commands to that controller. By default
this option will chose your controller.
"""
@spec get_node_ids([opt()]) :: Grizzly.send_command_response()
def get_node_ids(opts \\ []) do
seq_number = opts[:seq_number] || SeqNumber.get_and_inc()
node_id = node_id_from_opts(opts)
Grizzly.send_command(node_id, :node_list_get, seq_number: seq_number)
end
@doc """
Gets all the node ids both from the Z-Wave network and any virtual nodes
If everything is okay the response will be `{:ok, list_of_node_ids}` where the
list of node ids will be a combination of actual Z-Wave devices and virtual
device ids.
"""
@doc since: "3.0.0"
@spec get_all_node_ids([opt()]) ::
{:ok, [ZWave.node_id() | VirtualDevices.id()]} | {:error, :timeout | :nack_response}
def get_all_node_ids(opts \\ []) do
case get_node_ids(opts) do
{:ok, %Report{type: :command, status: :complete, command: node_id_list}} ->
zwave_node_ids = Command.param!(node_id_list, :node_ids)
virtual_node_ids = VirtualDevices.list_nodes()
{:ok, zwave_node_ids ++ virtual_node_ids}
{:ok, %Report{type: :timeout}} ->
{:error, :timeout}
{:error, :nack_response} = error ->
error
end
end
@doc """
Reset the Z-Wave controller
This command takes a few seconds to run.
Options
* `:node_id` - If your controller is part of another controller's network
you might want to issue network commands to that controller. By default
this option will chose your controller.
"""
@spec reset_controller([reset_opt() | opt()]) :: Grizzly.send_command_response()
def reset_controller(opts \\ []) do
# close all the connections before resetting the controller. It's okay
# to blindly close all connections because when we send the command to
# the controller Grizzly will automatically reconnect to the controller
# at that point in time. We do this because the connections to the Z-Wave
# devices are still reachable after being removed and we will still be
# sending keep alive messages when we don't need to and will have
# unnecessary connections hanging out just taking up resources.
:ok = Connections.close_all()
seq_number = SeqNumber.get_and_inc()
node_id = node_id_from_opts(opts)
case Grizzly.send_command(node_id, :default_set, [seq_number: seq_number], timeout: 10_000) do
{:ok, %Report{type: :command, status: :complete}} = response ->
maybe_notify_reset(response, opts)
other ->
other
end
end
@doc """
Delete a node from the network's provisioning list via the node's DSK
Options
* `:node_id` - If your controller is part of another controller's network
you might want to issue network commands to that controller. By default
this option will chose your controller.
"""
@spec delete_node_provisioning(Grizzly.ZWave.DSK.t(), [opt()]) ::
Grizzly.send_command_response()
def delete_node_provisioning(dsk, opts \\ []) do
seq_number = SeqNumber.get_and_inc()
node_id = node_id_from_opts(opts)
Grizzly.send_command(node_id, :node_provisioning_delete, seq_number: seq_number, dsk: dsk)
end
@doc """
Get the nodes provisioning list information via the node's DSK
Options
* `:node_id` - If your controller is part of another controller's network
you might want to issue network commands to that controller. By default
this option will chose your controller.
"""
@spec get_node_provisioning(Grizzly.ZWave.DSK.t(), [opt()]) ::
Grizzly.send_command_response()
def get_node_provisioning(dsk, opts \\ []) do
seq_number = SeqNumber.get_and_inc()
node_id = node_id_from_opts(opts)
Grizzly.send_command(node_id, :node_provisioning_get, seq_number: seq_number, dsk: dsk)
end
@doc """
A node to the network provisioning list
Options
* `:node_id` - If your controller is part of another controller's network
you might want to issue network commands to that controller. By default
this option will chose your controller.
"""
@spec set_node_provisioning(
Grizzly.ZWave.DSK.t(),
[Grizzly.ZWave.SmartStart.MetaExtension.extension()],
[opt()]
) :: Grizzly.send_command_response()
def set_node_provisioning(dsk, meta_extensions, opts \\ []) do
seq_number = SeqNumber.get_and_inc()
node_id = node_id_from_opts(opts)
Grizzly.send_command(
node_id,
:node_provisioning_set,
seq_number: seq_number,
dsk: dsk,
meta_extensions: meta_extensions
)
end
@doc """
Add a long range device to the provisioning list
"""
@spec add_long_range_device(Grizzly.ZWave.DSK.t(), [opt()]) :: Grizzly.send_command_response()
def add_long_range_device(dsk, opts \\ []) do
extensions = [
bootstrapping_mode: :long_range,
smart_start_inclusion_setting: :pending,
advanced_joining: [:s2_unauthenticated, :s2_authenticated]
]
set_node_provisioning(dsk, extensions, opts)
end
@doc """
List all the nodes on the provisioning list
Options
* `:node_id` - If your controller is part of another controller's network
you might want to issue network commands to that controller. By default
this option will chose your controller.
"""
@spec list_node_provisionings(integer(), [opt()]) :: Grizzly.send_command_response()
def list_node_provisionings(remaining_counter, opts \\ []) do
seq_number = SeqNumber.get_and_inc()
node_id = node_id_from_opts(opts)
Grizzly.send_command(
node_id,
:node_provisioning_list_iteration_get,
seq_number: seq_number,
remaining_counter: remaining_counter
)
end
@doc """
Remove a (presumably) failed node
Options
* `:node_id` - If your controller is part of another controller's network
you might want to issue network commands to that controller. By default
this option will chose your controller.
"""
@spec remove_failed_node([opt()]) ::
Grizzly.send_command_response()
def remove_failed_node(opts \\ []) do
seq_number = SeqNumber.get_and_inc()
node_id = node_id_from_opts(opts)
Grizzly.send_command(:gateway, :failed_node_remove, seq_number: seq_number, node_id: node_id)
end
@doc """
Get the list of ids of all failed nodes.
"""
@spec report_failed_node_ids() :: {:ok, [Grizzly.ZWave.node_id()]} | {:error, atom}
def report_failed_node_ids() do
seq_number = Grizzly.SeqNumber.get_and_inc()
case Grizzly.send_command(1, :failed_node_list_get, seq_number: seq_number) do
{:ok,
%Grizzly.Report{
command: %Grizzly.ZWave.Command{
name: :failed_node_list_report,
params: params
},
status: :complete
}} ->
{:ok, Keyword.fetch!(params, :node_ids)}
{:ok, %Grizzly.Report{type: :timeout}} ->
{:error, :timeout}
{:error, reason} ->
{:error, reason}
end
end
@doc """
Request a network update (network healing)
Options
* `:node_id` - If your controller is part of another controller's network
you might want to issue network commands to that controller. By default
this option will chose your controller.
"""
@spec request_network_update([opt()]) ::
Grizzly.send_command_response()
def request_network_update(opts \\ []) do
seq_number = SeqNumber.get_and_inc()
node_id = node_id_from_opts(opts)
Grizzly.send_command(node_id, :network_update_request, seq_number: seq_number)
end
defp node_id_from_opts(opts) do
Keyword.get(opts, :node_id, :gateway)
end
defp maybe_notify_reset(response, opts) do
{:ok, %Report{command: command}} = response
case Command.param!(command, :status) do
:done ->
maybe_notify_reset(opts)
response
:busy ->
response
end
end
defp maybe_notify_reset(opts) do
if Keyword.get(opts, :notify, true) do
notify_reset()
else
:ok
end
end
defp notify_reset() do
# get the nodes in the lifeline group
case Associations.get(1) do
nil ->
:ok
association ->
Enum.each(association.node_ids, fn node_id ->
Grizzly.send_command(node_id, :device_reset_locally_notification)
end)
end
end
end