Current section

Files

Jump to
grizzly lib grizzly.ex
Raw

lib/grizzly.ex

defmodule Grizzly do
@moduledoc """
Grizzly functions for controlling Z-Wave devices and the
Z-Wave network.
## Sending commands to Z-Wave
The most fundamental function in `Grizzly` is `Grizzly.send_command/3`.
There are two ways of using this function.
First, by passing in a node id for a node on the network:
```elixir
Grizzly.send_command(10, Grizzly.CommandClass.SwitchBinary.Get)
{:ok, :on}
Grizzly.send_command(10, Grizzly.CommandClass.SwitchBinary.Set, value: :off)
```
This is useful for short lived deterministic communication like `iex`
and scripts. This is because there is the overhead of connecting and
disconnecting to the node for each call.
For long lived applications that have non-deterministic sending of
messages (some type of automated commands) and user expectations on
device action we recommend using this function by passing in a
`Grizzly.Node`, `Grizzly.Conn`, or `Grizzly.Controller`.
```elixir
{:ok, zw_node} = Grizzly.get_node(10)
{:ok, zw_node} = Grizzly.Node.connect(zw_node)
{:ok, :on} = Grizzly.send_command(zw_node, Grizzly.CommandClass.SwitchBinary.Get)
:ok = Grizzly.send_command(zw_node, Grizzly.CommandClass.SwitchBinary.Set, value: :on)
```
This is useful because we maintain a heart beat with the node and overhead
of establishing the connection is removed from `send_command`.
In order for the consumer of Grizzly to use this in a long running application they
will need to hold on to a reference to the connected Z-Wave Node.
To know more commands and their arguments see the modules under the
`Grizzly.CommandClass` name space.
"""
alias Grizzly.{Conn, Command, Node, Controller, Notifications}
alias Grizzly.Conn.Config
alias Grizzly.Client.DTLS
@type seq_number :: 0..255
@typedoc """
A type the repersents things the have/can establish connections
to the Z/IP network.
1. `Conn.t` - A Connection struct
2. `Grizzly.Controller` - The controller process, this is a global, started on
application start process
3. `Node.t` - This is a Z-Wave Node that has been connected to the network
"""
@type connected :: Conn.t() | Controller | Node.t()
@conn_opts [:owner]
@spec config() :: Config.t()
def config() do
case Application.get_env(:grizzly, Grizzly.Controller) do
nil ->
Config.new(
ip: {0xFD00, 0xAAAA, 0, 0, 0, 0, 0, 1},
port: 41230,
client: DTLS
)
opts ->
Config.new(opts)
end
end
@doc """
Send a command to the Z-Wave device, first checking if in inclusion/exclusion state.
See individual command modules for information about what options it takes.
"""
@spec send_command(
connected | Node.node_id(),
command_module :: module,
command_opts :: keyword
) ::
:ok | {:ok, any} | {:error, any}
def send_command(connected, command_module, command_opts \\ [])
def send_command(%Conn{} = conn, command_module, opts) do
# an option in opts is either a command or connection option
command_opts = Keyword.drop(opts, @conn_opts)
conn_opts = opts -- command_opts
with {:ok, command} <- Command.start(command_module, command_opts) do
Conn.send_command(conn, command, conn_opts)
else
{:error, reason} ->
{:error, reason}
end
end
def send_command(Controller, command_module, command_opts) do
send_command(Controller.conn(), command_module, command_opts)
end
def send_command(%Node{conn: nil, id: 1}, command_module, command_opts) do
send_command(Controller, command_module, command_opts)
end
def send_command(%Node{conn: conn}, command_module, command_opts) do
send_command(conn, command_module, command_opts)
end
def send_command(node_id, command_module, command_opts) when is_integer(node_id) do
with {:ok, zw_node} <- Grizzly.get_node(node_id),
{:ok, zw_node} <- Node.connect(zw_node) do
response = send_command(zw_node, command_module, command_opts)
:ok = Node.disconnect(zw_node)
response
else
error -> error
end
end
@doc """
Close a connection
"""
@spec close_connection(Conn.t()) :: :ok
def close_connection(%Conn{} = conn) do
Conn.close(conn)
end
@doc """
Get a node from the network
This does not make a DTLS connection to the `Node.t()`
and if you want to connect to the node use `Grizzly.Node.connect/1`.
"""
@spec get_node(Node.node_id()) :: {:ok, Node.t()} | {:error, :node_not_found}
defdelegate get_node(node_id), to: Grizzly.Network
@doc """
Reset the Z-Wave Module to a clean state
"""
@spec reset_controller() :: :ok | {:error, :network_busy}
defdelegate reset_controller(), to: Grizzly.Network, as: :reset
@doc """
List the nodes on the Z-Wave network
"""
@spec get_nodes() :: {:ok, [Node.t()]} | {:error, :unable_to_get_nodes}
defdelegate get_nodes(), to: Grizzly.Network
@doc """
Check to see if the network is busy
"""
@spec network_busy?() :: boolean()
defdelegate network_busy?(), to: Grizzly.Network, as: :busy?
@doc """
Check to see if the network is ready
"""
@spec network_ready?() :: boolean()
defdelegate network_ready?(), to: Grizzly.Network, as: :ready?
@doc """
Put network in inclusion mode
"""
@spec add_node([Grizzly.Inclusion.opt()]) ::
:ok | {:error, {:invalid_option, Grizzly.Inclusion.invalid_opts_reason()}}
defdelegate add_node(opts \\ []), to: Grizzly.Inclusion
@doc """
Put network in exclusion mode
"""
@spec remove_node([Grizzly.Inclusion.opt()]) :: :ok
defdelegate remove_node(opts \\ []), to: Grizzly.Inclusion
@doc """
Put network out of inclusion mode
"""
@spec add_node_stop() :: :ok
defdelegate add_node_stop(), to: Grizzly.Inclusion
@doc """
Put network out of exclusion mode
"""
@spec remove_node() :: :ok
defdelegate remove_node_stop(), to: Grizzly.Inclusion
@doc """
Whether the node's command class versions are known
"""
@spec command_class_versions_known?(Node.t()) :: boolean
defdelegate command_class_versions_known?(zw_node), to: Grizzly.Node
@doc """
Update the command class version of a node
"""
@spec update_command_class_versions(Node.t()) :: Node.t()
defdelegate update_command_class_versions(zw_node), to: Node
@doc """
Put the controller in learn mode for a few seconds
"""
@spec start_learn_mode([Grizzly.Inclusion.opt()]) :: :ok
defdelegate start_learn_mode(opts \\ []), to: Grizzly.Inclusion
@doc """
Get the version of a node's command class, if the node does not have a version for
this command class this function will try to get it from the Z-Wave network.
"""
@spec get_command_class_version(Node.t(), atom) ::
{:ok, non_neg_integer} | {:error, atom}
defdelegate get_command_class_version(node, command_class_name), to: Node
@doc """
Whether a node has a given command class
"""
@spec has_command_class?(Node.t(), atom) :: boolean
defdelegate has_command_class?(node, command_class_name), to: Node
@doc """
Whether a node is connected.
"""
@spec connected?(Node.t()) :: boolean
defdelegate connected?(node), to: Node
@doc """
Get the command classes supported by a node.
"""
@spec command_class_names(Node.t()) :: [atom()]
defdelegate command_class_names(node), to: Node
@doc """
Subscribe to notifications about a topic
See `Grizzly.Notifications` for more information
"""
@spec subscribe(Notifications.topic()) :: :ok | {:error, :already_subscribed}
defdelegate subscribe(topic), to: Notifications
end