Packages
grizzly
8.6.3
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.
"""
require Logger
import Grizzly.ZWave.GeneratedMappings, only: [command_class_mappings: 0]
@type command_class_list :: [
non_secure_supported: list(atom()),
non_secure_controlled: list(atom()),
secure_supported: list(atom()),
secure_controlled: list(atom())
]
mappings = command_class_mappings()
command_classes_union =
mappings
|> Enum.map(&elem(&1, 1))
|> Enum.reverse()
|> Enum.reduce(&{:|, [], [&1, &2]})
@type command_class :: unquote(command_classes_union)
@doc """
Get the byte representation of the command class
"""
@spec to_byte(command_class()) :: byte()
for {byte, command_class} <- mappings do
def to_byte(unquote(command_class)), do: unquote(byte)
end
@spec from_byte(byte()) :: {:ok, command_class()} | {:error, :unsupported_command_class}
for {byte, command_class} <- mappings do
def from_byte(unquote(byte)), do: {:ok, unquote(command_class)}
end
def from_byte(byte) do
Logger.warning("[Grizzly] Unsupported command class from byte #{inspect(byte, base: :hex)}")
{:error, :unsupported_command_class}
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