Packages
grizzly
9.1.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.ex
defmodule Grizzly.ZWave.CommandClasses do
@moduledoc """
Utilities for encoding and decoding command classes and command class lists.
"""
import Grizzly.ZWave.GeneratedMappings, only: [command_class_mappings: 0]
require Logger
@type command_class_list :: [
non_secure_supported: list(atom()),
non_secure_controlled: list(atom()),
secure_supported: list(atom()),
secure_controlled: list(atom())
]
@type command_class :: atom()
@doc """
Get the byte representation of the command class
"""
@spec to_byte(command_class()) :: byte()
def to_byte(command_class) do
Map.fetch!(command_class_mappings(), command_class)
end
@spec from_byte(byte()) :: {:ok, command_class()} | {:error, :unsupported_command_class}
def from_byte(byte) do
Enum.find_value(command_class_mappings(), {:error, :unsupported_command_class}, fn {cc, b} ->
if b == byte, do: {:ok, cc}
end)
end
@spec valid?(command_class()) :: boolean()
def valid?(command_class) do
Map.has_key?(command_class_mappings(), command_class)
end
@doc """
Merges command class lists in a way that's more resilient to some Z/IP Gateway
bugs. The list provided in the second argument is merged into the first.
In particular, there's a Z/IP Gateway bug where it can lose track of a node's
supported command classes. If this happens while the node is failing, a Node
Info Cached Report can come back with an empty command class list. If requesting
the NIF works but the controller doesn't receive an S0/S2 Commands Supported Report
(e.g. due to interference), the secure command class lists will be empty.
"""
@spec merge(command_class_list(), command_class_list()) :: command_class_list()
def merge(list1, list2) do
Keyword.merge(list1, list2, fn _key, val1, val2 ->
if val2 == [] do
val1
else
val2
end
end)
end
@doc """
Turn the list of command classes into the binary representation outlined in
the Network-Protocol command class specification.
TODO: add more details
"""
@spec command_class_list_to_binary([command_class_list()]) :: binary()
def command_class_list_to_binary(command_class_list) do
non_secure_supported = Keyword.get(command_class_list, :non_secure_supported, [])
non_secure_controlled = Keyword.get(command_class_list, :non_secure_controlled, [])
secure_supported = Keyword.get(command_class_list, :secure_supported, [])
secure_controlled = Keyword.get(command_class_list, :secure_controlled, [])
non_secure_supported_bin = for cc <- non_secure_supported, into: <<>>, do: <<to_byte(cc)>>
non_secure_controlled_bin = for cc <- non_secure_controlled, into: <<>>, do: <<to_byte(cc)>>
secure_supported_bin = for cc <- secure_supported, into: <<>>, do: <<to_byte(cc)>>
secure_controlled_bin = for cc <- secure_controlled, into: <<>>, do: <<to_byte(cc)>>
bin =
non_secure_supported_bin
|> maybe_concat_command_classes(:non_secure_controlled, non_secure_controlled_bin)
|> maybe_concat_command_classes(:secure_supported, secure_supported_bin)
|> maybe_concat_command_classes(:secure_controlled, secure_controlled_bin)
if bin == <<>> do
<<0>>
else
bin
end
end
@doc """
Turn the binary representation that is outlined in the Network-Protocol specs
"""
@spec command_class_list_from_binary(binary()) :: [command_class_list()]
def command_class_list_from_binary(binary) do
binary
|> :erlang.binary_to_list()
|> group_extended_command_classes()
|> parse_and_categorize_command_classes()
end
defp group_extended_command_classes(command_class_id_list) do
command_class_id_list
|> Enum.reduce({nil, []}, fn
# Extended command classes start with 0xF1..0xFF
byte, {nil, acc} when byte in 0xF1..0xFF ->
{byte, acc}
# Second byte of an extended command class
byte, {prev_byte, acc} when prev_byte in 0xF1..0xFF ->
{nil, [prev_byte * 2 ** 8 + byte | acc]}
# All other command classes
byte, {nil, acc} ->
{nil, [byte | acc]}
end)
|> elem(1)
|> Enum.reverse()
end
defp parse_and_categorize_command_classes(command_class_id_list) do
command_class_id_list
|> Enum.reduce(
{:non_secure_supported,
[
non_secure_supported: [],
non_secure_controlled: [],
secure_supported: [],
secure_controlled: []
]},
fn
0xEF, {:non_secure_supported, command_classes} ->
{:non_secure_controlled, command_classes}
0xF100, {_, command_classes} ->
{:secure_supported, command_classes}
0xEF, {:secure_supported, command_classes} ->
{:secure_controlled, command_classes}
# Skip extended command classes
command_class_id, acc when command_class_id > 0xFF ->
acc
command_class_id, {security, command_classes} ->
case from_byte(command_class_id) do
{:ok, command_class} ->
{security, Keyword.update(command_classes, security, [], &(&1 ++ [command_class]))}
{:error, :unsupported_command_class} ->
# Skip unsupported command classes
{security, command_classes}
end
end
)
|> elem(1)
end
defp maybe_concat_command_classes(binary, _, <<>>), do: binary
defp maybe_concat_command_classes(binary, :non_secure_controlled, ccs_bin),
do: binary <> <<0xEF, ccs_bin::binary>>
defp maybe_concat_command_classes(binary, :secure_supported, ccs_bin),
do: binary <> <<0xF1, 0x00, ccs_bin::binary>>
defp maybe_concat_command_classes(binary, :secure_controlled, ccs_bin),
do: binary <> <<0xEF, ccs_bin::binary>>
end