Current section

Files

Jump to
grizzly lib grizzly zwave command_classes.ex
Raw

lib/grizzly/zwave/command_classes.ex

defmodule Grizzly.ZWave.CommandClasses do
require Logger
defmodule Generate do
@moduledoc false
@mappings [
{0x00, :no_operation},
{0x02, :zensor_net},
{0x20, :basic},
{0x21, :controller_replication},
{0x22, :application_status},
{0x23, :zip},
{0x24, :security_panel_mode},
{0x25, :switch_binary},
{0x26, :switch_multilevel},
{0x27, :switch_all},
{0x28, :switch_toggle_binary},
{0x2A, :chimney_fan},
{0x2B, :scene_activation},
{0x2C, :scene_actuator_conf},
{0x2D, :scene_controller_conf},
{0x2E, :security_panel_zone},
{0x2F, :security_panel_zone_sensor},
{0x30, :sensor_binary},
{0x31, :sensor_multilevel},
{0x32, :meter},
{0x33, :switch_color},
{0x34, :network_management_inclusion},
{0x35, :meter_pulse},
{0x36, :basic_tariff_info},
{0x37, :hrv_status},
{0x38, :thermostat_heating},
{0x39, :hrv_control},
{0x3A, :dcp_config},
{0x3B, :dcp_monitor},
{0x3C, :meter_tbl_config},
{0x3D, :meter_tbl_monitor},
{0x3E, :meter_tbl_push},
{0x3F, :prepayment},
{0x40, :thermostat_mode},
{0x41, :prepayment_encapsulation},
{0x42, :thermostat_operating_state},
{0x43, :thermostat_setpoint},
{0x44, :thermostat_fan_mode},
{0x45, :thermostat_fan_state},
{0x46, :climate_control_schedule},
{0x47, :thermostat_setback},
{0x48, :rate_tbl_config},
{0x49, :rate_tbl_monitor},
{0x4A, :tariff_config},
{0x4B, :tariff_tbl_monitor},
{0x4C, :door_lock_logging},
{0x4D, :network_management_basic},
{0x4E, :schedule_entry_lock},
{0x4F, :zip_6lowpan},
{0x50, :basic_window_covering},
{0x51, :mtp_window_covering},
{0x52, :network_management_proxy},
{0x53, :schedule},
{0x54, :network_management_primary},
{0x55, :transport_service},
{0x56, :crc_16_encap},
{0x57, :application_capability},
{0x58, :zip_nd},
{0x59, :association_group_info},
{0x5A, :device_reset_locally},
{0x5B, :central_scene},
{0x5C, :ip_association},
{0x5D, :antitheft},
{0x5E, :zwaveplus_info},
{0x5F, :zip_gateway},
{0x60, :multi_channel},
{0x61, :zip_portal},
{0x62, :door_lock},
{0x63, :user_code},
{0x64, :humidity_control_setpoint},
{0x65, :dmx},
{0x66, :barrier_operator},
{0x67, :network_management_installation_maintenance},
{0x68, :zip_naming},
{0x69, :mailbox},
{0x6A, :window_covering},
{0x6B, :irrigation},
{0x6C, :supervision},
{0x6D, :humidity_control_mode},
{0x6E, :humidity_control_operating_state},
{0x6F, :entry_control},
{0x70, :configuration},
{0x71, :alarm},
{0x72, :manufacturer_specific},
{0x73, :powerlevel},
{0x74, :inclusion_controller},
{0x75, :protection},
{0x76, :lock},
{0x77, :node_naming},
{0x78, :node_provisioning},
{0x7A, :firmware_update_md},
{0x7B, :grouping_name},
{0x7C, :remote_association_activate},
{0x7D, :remote_association},
{0x7E, :antitheft_unlock},
{0x80, :battery},
{0x81, :clock},
{0x82, :hail},
{0x84, :wake_up},
{0x85, :association},
{0x86, :version},
{0x87, :indicator},
{0x88, :proprietary},
{0x89, :language},
{0x8A, :time},
{0x8B, :time_parameters},
{0x8C, :geographic_location},
{0x8E, :multi_channel_association},
{0x8F, :multi_cmd},
{0x90, :energy_production},
{0x91, :manufacturer_proprietary},
{0x92, :screen_md},
{0x93, :screen_attributes},
{0x94, :simple_av_control},
{0x95, :av_content_directory_md},
{0x96, :av_content_renderer_status},
{0x97, :av_content_search_md},
{0x98, :security},
{0x99, :av_tagging_md},
{0x9A, :ip_configuration},
{0x9B, :association_command_configuration},
{0x9C, :sensor_alarm},
{0x9D, :silence_alarm},
{0x9E, :sensor_configuration},
{0x9F, :security_2},
{0xEF, :mark},
{0xF0, :non_interoperable}
]
defmacro __before_compile__(_) do
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.warn("[Grizzly] Unsupported command class from byte #{byte}")
{: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()]) :: 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()]
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] ->
# Right now lets fail super hard so we can add support for
# new command classes quickly
{:ok, command_class} = from_byte(command_class_byte)
{security, Keyword.update(command_classes, security, [], &(&1 ++ [command_class]))}
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