Packages
grizzly
8.8.1
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/zwave/command_classes/network_management_inclusion.ex
defmodule Grizzly.ZWave.CommandClasses.NetworkManagementInclusion do
@moduledoc """
Network Management Inclusion Command Class
This command class provides the commands for adding and removing Z-Wave nodes
to the Z-Wave network
"""
require Logger
@behaviour Grizzly.ZWave.CommandClass
alias Grizzly.ZWave.{CommandClasses, DecodeError, DeviceClasses, DSK, Security}
@typedoc """
The status of the inclusion process
* `:done` - the inclusion process is done without error
* `:failed` - the inclusion process is done with failure, the device is not
included
* `:security_failed` - the inclusion process is done, the device is included
but their was an error during the security negotiations. Device \
functionality will be degraded.
"""
@type node_add_status() :: :done | :failed | :security_failed
@type tx_opt :: :null | :low_power | :explore
@impl Grizzly.ZWave.CommandClass
def byte(), do: 0x34
@impl Grizzly.ZWave.CommandClass
def name(), do: :network_management_inclusion
@doc """
Parse the node add status byte into an atom
"""
@spec parse_node_add_status(0x06 | 0x07 | 0x09) :: node_add_status()
def parse_node_add_status(0x06), do: :done
def parse_node_add_status(0x07), do: :failed
def parse_node_add_status(0x09), do: :security_failed
@doc """
Encode a `node_add_status()` to a byte
"""
@spec node_add_status_to_byte(node_add_status()) :: 0x06 | 0x07 | 0x09
def node_add_status_to_byte(:done), do: 0x06
def node_add_status_to_byte(:failed), do: 0x07
def node_add_status_to_byte(:security_failed), do: 0x09
@spec tx_opt_to_byte(tx_opt()) :: byte()
def tx_opt_to_byte(:null), do: 0x00
def tx_opt_to_byte(:low_power), do: 0x02
def tx_opt_to_byte(:explore), do: 0x20
@spec tx_opt_from_byte(byte()) :: {:ok, tx_opt()} | {:error, DecodeError.t()}
def tx_opt_from_byte(0x00), do: {:ok, :null}
def tx_opt_from_byte(0x02), do: {:ok, :low_power}
def tx_opt_from_byte(0x20), do: {:ok, :explore}
def tx_opt_from_byte(byte),
do: {:error, %DecodeError{value: byte, param: :tx_opt}}
@typedoc """
Command classes have different ways they are support for each device
"""
@type tagged_command_classes() ::
{:non_secure_supported, [CommandClasses.command_class()]}
| {:non_secure_controlled, [CommandClasses.command_class()]}
| {:secure_supported, [CommandClasses.command_class()]}
| {:secure_controlled, [CommandClasses.command_class()]}
@typedoc """
Node info report
node information from a node add status report
* `:basic_device_class` - the basic device class
* `:generic_device_class` - the generic device class
* `:specific_device_class` - the specific device class
* `:command_classes` - list of command classes the new device supports
* `:granted_keys` - S2 keys granted by the user during the time of inclusion
version 2 and above
* `:kex_fail_type` - the type of key exchange failure if there is one version
2 and above
* `input_dsk` - the DSK of the device version 3 and above. If the info report is used
"""
@type node_info_report() :: %{
required(:seq_number) => byte(),
required(:node_id) => Grizzly.ZWave.node_id(),
required(:status) => node_add_status(),
required(:basic_device_class) => DeviceClasses.basic_device_class(),
required(:generic_device_class) => DeviceClasses.generic_device_class(),
required(:specific_device_class) => DeviceClasses.specific_device_class(),
required(:command_classes) => [tagged_command_classes()],
optional(:granted_keys) => [Security.key()],
optional(:kex_fail_type) => Security.key_exchange_fail_type(),
optional(:input_dsk) => Security.key_exchange_fail_type()
}
@typedoc """
Extended node info report
Node information from an extended node add status report
* `:basic_device_class` - the basic device class
* `:generic_device_class` - the generic device class
* `:specific_device_class` - the specific device class
* `:command_classes` - list of command classes the new device supports
* `:granted_keys` - S2 keys granted by the user during the time of inclusion
* `:kex_fail_type` - the type of key exchange failure if there is one
"""
@type extended_node_info_report() :: %{
required(:basic_device_class) => DeviceClasses.basic_device_class(),
required(:generic_device_class) => DeviceClasses.generic_device_class(),
required(:specific_device_class) => DeviceClasses.specific_device_class(),
required(:command_classes) => [tagged_command_classes()],
required(:granted_keys) => [Security.key()],
required(:kex_fail_type) => Security.key_exchange_fail_type()
}
@doc """
Parse node information from node add status and extended node add status reports
"""
@spec parse_node_info(binary()) :: node_info_report() | extended_node_info_report()
def parse_node_info(
<<node_info_length, _extra_ccs?::1, _::7, _opt_func, basic_device_class,
generic_device_class, specific_device_class,
command_classes::binary-size(node_info_length - 6), rest::binary>>
) do
# TODO: decode the command classes correctly (currently assuming no extended command classes)
{:ok, basic_device_class} = DeviceClasses.basic_device_class_from_byte(basic_device_class)
{:ok, generic_device_class} =
DeviceClasses.generic_device_class_from_byte(generic_device_class)
{:ok, specific_device_class} =
DeviceClasses.specific_device_class_from_byte(
generic_device_class,
specific_device_class
)
# node info length includes: node_info_length, extra_ccs?, opt_func, basic class,
# generic class, specific class, and the command class list. Granted keys, kex fail
# type, and DSK are not included.
%{
basic_device_class: basic_device_class,
generic_device_class: generic_device_class,
specific_device_class: specific_device_class,
command_classes: CommandClasses.command_class_list_from_binary(command_classes)
}
|> parse_optional_fields(rest)
end
# This accounts for an off-by-one error observed (rarely) in node add status
# frames sent by Z/IP Gateway when a SmartStart node fails security bootstrapping.
# node_info_length is supposed to include itself, but in that case, it sometimes
# doesn't.
def parse_node_info(<<node_info_length, rest::binary-size(node_info_length - 2)>>),
do: parse_node_info(<<node_info_length - 1, rest::binary>>)
# node_info_length can be 1 (note that it's self-inclusive) when an inclusion error
# occurs and no node info is available
def parse_node_info(<<1>>), do: %{}
defp parse_optional_fields(info, <<>>), do: info
defp parse_optional_fields(info, <<granted_keys, kex_fail_type>>),
do: put_security_info(info, granted_keys, kex_fail_type)
defp parse_optional_fields(info, <<granted_keys, kex_fail_type, 0x00>>),
do: put_security_info(info, granted_keys, kex_fail_type)
defp parse_optional_fields(info, <<granted_keys, kex_fail_type, 16, dsk::binary-size(16)>>),
do: info |> put_security_info(granted_keys, kex_fail_type) |> put_dsk(dsk)
defp parse_optional_fields(info, extra) do
Logger.warning(
"[Grizzly] Unable to parse fields after command class list in (Ext.) Node Add Status: #{inspect(extra, base: :hex)}"
)
info
end
defp put_security_info(info, granted_keys, kex_fail_type) do
info
|> Map.put(:granted_keys, Security.byte_to_keys(granted_keys))
|> Map.put(:kex_fail_type, Security.failed_type_from_byte(kex_fail_type))
end
defp put_dsk(info, dsk_bin) do
info
|> Map.put(:input_dsk, DSK.new(dsk_bin))
end
end