Packages
grizzly
8.6.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/virtual_devices.ex
defmodule Grizzly.VirtualDevices do
@moduledoc """
Virtual devices
Virtual devices are in-memory devices that act like a Z-Wave device
"""
alias Grizzly.UnsolicitedServer.Messages
alias Grizzly.VirtualDevicesRegistry
alias Grizzly.VirtualDevices.{Device, Reports}
alias Grizzly.ZWave.Command
alias Grizzly.ZWave.DeviceClass
@typedoc """
Options for adding a virtual devices
* `:inclusion_handler` - if an inclusion handler is provider via the add
options it will override the initial inclusion handle argument to the
network server if one was provided only for that one call to `add_device/2`.
You may also include other device options that will passed to your callback
functions for implementation specific support.
"""
@type add_opt() :: {:inclusion_handler, Grizzly.handler()} | Device.device_opt()
@typedoc """
Options for removing virtual devices
* `:inclusion_handler` - if an inclusion handler is provider via the add
options it will override the initial inclusion handle argument to the
network server if one was provided only for that one call to
`remove_device/2`.
"""
@type remove_opt() :: {:inclusion_handler, Grizzly.handler()}
@typedoc """
Id for a virtual device
"""
@type id() :: {:virtual, integer()}
@type device_entry() :: %{
device_impl: Device.t(),
device_class: DeviceClass.t(),
device_opts: [Device.device_opt()],
id: id(),
pid: pid()
}
@doc """
Add a new virtual device to the virtual device network
To add a virtual device you must supply a module that implements the
`Grizzly.VirtualDevices.Device` behaviour.
If the device takes any options you can pass a tuple of `{device, opts}`.
"""
@spec add_device(id(), Device.t(), [add_opt()]) ::
{:ok, id()} | {:error, {:already_registered, device_entry()}}
def add_device(device_id, device_impl, opts \\ []) do
device_opts = Keyword.drop(opts, [:inclusion_handler])
case register(device_id, device_impl, device_opts) do
{:ok, device_entry} ->
if handler = opts[:inclusion_handler] || VirtualDevicesRegistry.get_handler() do
Reports.send_node_add_status(device_entry, handler)
end
{:ok, device_entry.id}
{:error, {:already_registered, entry}} ->
{:error, {:already_registered, entry}}
end
end
@doc """
Same as `add_device/3` but raises an ArgumentError if a device with the requested
id is already registered.
"""
@spec add_device!(id(), Device.t(), [add_opt()]) :: id()
def add_device!(device_id, device_impl, opts \\ []) do
case add_device(device_id, device_impl, opts) do
{:ok, device_id} -> device_id
{:error, {:already_registered, _entry}} -> raise ArgumentError, "Device already registered"
end
end
defp register(device_id, device_impl, device_opts) do
device_class = device_impl.device_spec(device_opts)
VirtualDevicesRegistry.register(device_id, device_impl, device_class, device_opts)
end
@doc """
Broadcast a command to the rest of the Z-Wave network
"""
@spec broadcast_command(id(), Command.t()) :: :ok
def broadcast_command(device_id, command) do
Messages.broadcast(device_id, command)
end
@doc """
Remove a device from the virtual device network
"""
@spec remove_device(id(), [remove_opt()]) :: :ok
def remove_device(device_id, opts \\ []) do
:ok = VirtualDevicesRegistry.unregister(device_id)
if handler = opts[:inclusion_handler] || VirtualDevicesRegistry.get_handler() do
Reports.send_node_remove_status(device_id, handler)
end
:ok
end
@doc """
Get the pid for the device id
This is useful for when you device is processed-based and you need to send
messages to it.
"""
@spec whereis(id()) :: pid() | nil
def whereis(device_id) do
case VirtualDevicesRegistry.get(device_id) do
nil ->
nil
entry ->
entry.pid
end
end
@doc """
List the nodes in the virtual devices network
"""
@spec list_nodes() :: [id()]
def list_nodes() do
VirtualDevicesRegistry.list_ids()
end
@doc """
Send a Z-Wave command to the virtual device
"""
@spec send_command(id(), Command.t()) ::
{:ok, Grizzly.Report.t()} | {:error, :device_not_found | Grizzly.Report.t()}
def send_command(device_id, %Command{name: :node_info_cached_get} = node_info_get) do
with_entry(device_id, &Reports.build_node_info_cache_report(&1, node_info_get))
end
def send_command(device_id, %Command{name: :manufacturer_specific_get}) do
with_entry(device_id, &Reports.build_manufacturer_specific_report/1)
end
def send_command(device_id, %Command{name: :version_command_class_get} = command) do
with_entry(device_id, &Reports.build_version_command_class_get_report(&1, command))
end
def send_command(device_id, %Command{name: :association_get}) do
with_entry(device_id, &Reports.build_association_report/1)
end
def send_command(device_id, %Command{name: :battery_get}) do
with_entry(device_id, &Reports.build_battery_report/1)
end
def send_command(device_id, %Command{name: :version_get}) do
with_entry(device_id, &Reports.build_version_report/1)
end
def send_command(
device_id,
%Command{name: :manufacturer_specific_device_specific_get} = command
) do
with_entry(
device_id,
&Reports.build_manufacturer_specific_device_specific_report(&1, command)
)
end
def send_command(device_id, %Command{name: :association_set}) do
with_entry(device_id, &Reports.build_ack_response/1)
end
def send_command(device_id, command) do
case VirtualDevicesRegistry.get(device_id) do
nil ->
{:error, :device_not_found}
entry ->
case entry.device_impl.handle_command(command, entry.device_opts) do
:ok ->
{:ok, Reports.build_ack_response(entry)}
{:ok, command} ->
{:ok, Reports.build_report(entry, command)}
{:error, :timeout} ->
{:error, Reports.build_timeout_report(entry.id)}
{:notify, command} ->
broadcast_command(device_id, command)
{:ok, Reports.build_ack_response(entry)}
end
end
end
defp with_entry(device_id, callback) do
case VirtualDevicesRegistry.get(device_id) do
nil ->
{:ok, Reports.build_timeout_report(device_id)}
entry ->
result = callback.(entry)
{:ok, result}
end
end
end