defmodule Grizzly.ZWave.CommandClasses do
require Logger
@type command_class_list :: [
non_secure_supported: list(atom()),
non_secure_controlled: list(atom()),
secure_supported: list(atom()),
secure_controlled: list(atom())
]
defmodule Generate do
@moduledoc false
import Grizzly.ZWave.GeneratedMappings, only: [command_class_mappings: 0]
defmacro __before_compile__(_) do
mappings = command_class_mappings()
to_byte =
for {byte, command_class} <- mappings do
quote do
def to_byte(unquote(command_class)), do: unquote(byte)
end
end
from_byte =
for {byte, command_class} <- mappings do
quote do
def from_byte(unquote(byte)), do: {:ok, unquote(command_class)}
end
end
command_classes =
mappings
|> Enum.map(&elem(&1, 1))
|> Enum.reverse()
|> Enum.reduce(&{:|, [], [&1, &2]})
quote do
@type command_class :: unquote(command_classes)
@doc """
Try to parse the byte into a command class
"""
@spec from_byte(byte()) :: {:ok, command_class()} | {:error, :unsupported_command_class}
unquote(from_byte)
def from_byte(byte) do
Logger.warning(
"[Grizzly] Unsupported command class from byte #{inspect(byte, base: :hex)}"
)
{:error, :unsupported_command_class}
end
@doc """
Get the byte representation of the command class
"""
@spec to_byte(command_class()) :: byte()
unquote(to_byte)
end
end
end
@before_compile Generate
@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_list = :erlang.binary_to_list(binary)
{_, command_classes} =
Enum.reduce(
binary_list,
{: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}
0xF1, {_, command_classes} ->
{:secure_supported, command_classes}
0x00, {_, command_classes} ->
{:secure_supported, command_classes}
0xEF, {:secure_supported, command_classes} ->
{:secure_controlled, command_classes}
command_class_byte, {security, command_classes}
when command_class_byte not in [0xF1, 0xEF, 0x00] ->
case from_byte(command_class_byte) 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
)
command_classes
end
def maybe_concat_command_classes(binary, _, <<>>), do: binary
def maybe_concat_command_classes(binary, :non_secure_controlled, ccs_bin),
do: binary <> <<0xEF, ccs_bin::binary>>
def maybe_concat_command_classes(binary, :secure_supported, ccs_bin),
do: binary <> <<0xF1, 0x00, ccs_bin::binary>>
def maybe_concat_command_classes(binary, :secure_controlled, ccs_bin),
do: binary <> <<0xEF, ccs_bin::binary>>
end