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 quote do @type command_class :: :zensor_net | :basic | :controller_replication | :application_status | :zip | :security_panel_mode | :switch_binary | :switch_multilevel | :switch_all | :switch_toggle_binary | :chimney_fan | :scene_activation | :scene_actuator_conf | :scene_controller_conf | :security_panel_zone | :security_panel_zone_sensor | :sensor_binary | :sensor_multilevel | :meter | :switch_color | :network_management_inclusion | :meter_pulse | :basic_tariff_info | :hrv_status | :thermostat_heating | :hrv_control | :dcp_config | :dcp_monitor | :meter_tbl_config | :meter_tbl_monitor | :meter_tbl_push | :prepayment | :thermostat_mode | :prepayment_encapsulation | :operating_state | :thermostat_setpoint | :thermostat_fan_mode | :thermostat_fan_state | :climate_control_schedule | :thermostat_setback | :rate_tbl_config | :rate_tbl_monitor | :tariff_config | :tariff_tbl_monitor | :door_lock_logging | :network_management_basic | :schedule_entry_lock | :zip_6lowpan | :basic_window_covering | :mtp_window_covering | :network_management_proxy | :schedule | :network_management_primary | :transport_service | :crc_16_encap | :application_capability | :zip_nd | :association_group_info | :device_reset_locally | :central_scene | :ip_association | :antitheft | :zwaveplus_info | :zip_gateway | :zip_portal | :door_lock | :user_code | :humidity_control_setpoint | :dmx | :barrier_operator | :network_management_installation_maintenance | :zip_naming | :mailbox | :window_covering | :irrigation | :supervision | :humidity_control_mode | :humidity_control_operating_state | :entry_control | :configuration | :alarm | :manufacturer_specific | :powerlevel | :inclusion_controller | :protection | :lock | :node_naming | :node_provisioning | :firmware_update_md | :grouping_name | :remote_association_activate | :remote_association | :battery | :clock | :hail | :wake_up | :association | :version | :indicator | :proprietary | :language | :time | :time_parameters | :geographic_location | :multi_channel | :multi_channel_association | :multi_cmd | :energy_production | :manufacturer_proprietary | :screen_md | :screen_attributes | :simple_av_control | :av_content_directory_md | :av_content_renderer_status | :av_content_search_md | :security | :av_tagging_md | :ip_configuration | :association_command_configuration | :sensor_alarm | :silence_alarm | :sensor_configuration | :security_2 | :mark | :non_interoperable | :no_operation @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: <> non_secure_controlled_bin = for cc <- non_secure_controlled, into: <<>>, do: <> secure_supported_bin = for cc <- secure_supported, into: <<>>, do: <> secure_controlled_bin = for cc <- secure_controlled, into: <<>>, do: <> 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) 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_bin, 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