Packages
grizzly
0.14.4
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/notifications.ex
defmodule Grizzly.ZWave.Notifications do
alias Grizzly.ZWave.Commands.{UserCodeReport, NodeLocationReport}
alias Grizzly.ZWave.CommandClasses.{UserCode, NodeNaming}
alias Grizzly.ZWave.{Decoder, DecodeError}
require Logger
@alarm_types [
# Byte 1
[
:unknown,
:smoke_alarm,
:co_alarm,
:co2_alarm,
:heat_alarm,
:water_alarm,
:access_control,
:home_security
],
# Byte 2
[
:power_management,
:system,
:emergency_alarm,
:clock,
:appliance,
:home_health,
:siren,
:water_valve
],
# Byte 3
[
:weather_alarm,
:irrigation,
:gas_alarm,
:pest_control,
:light_sensor,
:water_quality_monitoring,
:home_monitoring,
:request_pending_notification
]
]
@type_events [
smoke_alarm: [
# byte 1
[
:unknown,
:smoke_detected_location_provided,
:smoked_detected,
:smoke_alarm_test,
:replacement_required,
:replacement_required_end_of_life,
:alarm_silenced,
:maintenance_required_planned_periodic_inspection
],
# byte 2
[
:maintenance_required_dust_in_device,
:unknown,
:unknown,
:unknown,
:unknown,
:unknown,
:unknown,
:unknown
]
],
water: [
# byte 1
[
:unknown,
:water_leak_detected_location_provided,
:water_leak_detected,
:water_level_dropped_location_provided,
:water_level_dropped,
:replace_water_filter,
:water_flow_alarm,
:water_pressure_alarm
],
# byte 2
[
:water_temperature_alarm,
:water_level_alarm,
:sump_pump_active,
:sump_pump_failure,
:unknown,
:unknown,
:unknown,
:unknown
]
],
access_control: [
# byte 1
[
:unknown,
:manual_lock_operation,
:manual_unlock_operation,
:rf_lock_operation,
:rf_unlock_operation,
:keypad_lock_operation,
:keypad_unlock_operation,
:manual_not_fully_locked_operation
],
# byte 2
[
:rf_not_fully_locked_operation,
:auto_lock_locked_operation,
:auto_lock_not_fully_locked_operation,
:lock_jammed,
:all_user_codes_deleted,
:single_user_code_deleted,
:new_user_code_added,
:new_user_code_not_added_duplicate
],
# byte 3
[
:keypad_temporarily_disabled,
:keypad_busy,
:new_program_code_entered,
:manually_enter_user_code_exceeds_code_limit,
:unlock_by_rf_with_invalid_user_code,
:locked_by_rf_with_invalid_user_code,
:window_or_door_is_open,
:window_or_door_is_closed
],
# byte 4
[
:window_or_door_handle_is_open,
:window_or_door_handle_is_closed,
:messaging_user_code_entered_via_keypad,
:barrier_performing_initialization_process,
:barrier_operation_exceeded_time_limit,
:barrier_operation_force_exceeded,
:barrier_operation_exceeded_mechanical_limit,
:barrier_unable_to_perform_operation_ul_requirements
],
# byte 5
[
:barrier_unattended_operation_disabled_ul_requirements,
:barrier_failed_operation_malfunction,
:barrier_vacation_mode,
:barrier_safety_beam_obstacle,
:barrier_sensor_not_detected,
:barrier_sensor_low_battery,
:barrier_short_in_wall_station_wires,
:unknown
]
],
home_security: [
# byte 1
[
:unknown,
:intrusion_location_provided,
:intrusion,
:tampering_product_cover_removed,
:tampering_invalid_code,
:glass_breakage_location_provided,
:glass_breakage,
:motion_detection_location_provided
],
# byte 2
[
:motion_detection,
:tampering_product_moved,
:impact_detected,
:magnetic_field_interface_detected,
:unknown_state,
:unknown,
:unknown,
:unknown
]
],
siren: [
# byte 1
[
:unknown,
:state_active,
:unknown,
:unknown,
:unknown,
:unknown,
:unknown,
:unknown
]
]
]
defmodule Generate do
@moduledoc false
@table [
# {type_byte, type, event_byte, event}
# Smoke Alarm (0x01)
{0x01, :smoke_alarm, 0x00, :state_idle},
{0x01, :smoke_alarm, 0x01, :smoke_detected_location_provided},
{0x01, :smoke_alarm, 0x02, :smoked_detected},
{0x01, :smoke_alarm, 0x03, :smoke_alarm_test},
{0x01, :smoke_alarm, 0x04, :replacement_required},
{0x01, :smoke_alarm, 0x05, :replacement_required_end_of_life},
{0x01, :smoke_alarm, 0x06, :alarm_silenced},
{0x01, :smoke_alarm, 0x07, :maintenance_required_planned_periodic_inspection},
{0x01, :smoke_alarm, 0x08, :maintenance_required_dust_in_device},
# Water (0x05)
{0x05, :water, 0x00, :state_idle},
{0x05, :water, 0x01, :water_leak_detected_location_provided},
{0x05, :water, 0x02, :water_leak_detected},
{0x05, :water, 0x03, :water_level_dropped_location_provided},
{0x05, :water, 0x04, :water_level_dropped},
{0x05, :water, 0x05, :replace_water_filter},
{0x05, :water, 0x06, :water_flow_alarm},
{0x05, :water, 0x07, :water_pressure_alarm},
{0x05, :water, 0x08, :water_temperature_alarm},
{0x05, :water, 0x09, :water_level_alarm},
{0x05, :water, 0x0A, :sump_pump_active},
{0x05, :water, 0x0B, :sump_pump_failure},
{0x05, :water, 0xFE, :unknown},
# Access Control (0x06)
{0x06, :access_control, 0x00, :state_idle},
{0x06, :access_control, 0x01, :manual_lock_operation},
{0x06, :access_control, 0x02, :manual_unlock_operation},
{0x06, :access_control, 0x03, :rf_lock_operation},
{0x06, :access_control, 0x04, :rf_unlock_operation},
{0x06, :access_control, 0x05, :keypad_lock_operation},
{0x06, :access_control, 0x06, :keypad_unlock_operation},
{0x06, :access_control, 0x07, :manual_not_fully_locked_operation},
{0x06, :access_control, 0x08, :rf_not_fully_locked_operation},
{0x06, :access_control, 0x09, :auto_lock_locked_operation},
{0x06, :access_control, 0x0A, :auto_lock_not_fully_locked_operation},
{0x06, :access_control, 0x0B, :lock_jammed},
{0x06, :access_control, 0x0C, :all_user_codes_deleted},
{0x06, :access_control, 0x0D, :single_user_code_deleted},
{0x06, :access_control, 0x0E, :new_user_code_added},
{0x06, :access_control, 0x0F, :new_user_code_not_added_duplicate},
{0x06, :access_control, 0x10, :keypad_temporarily_disabled},
{0x06, :access_control, 0x11, :keypad_busy},
{0x06, :access_control, 0x12, :new_program_code_entered},
{0x06, :access_control, 0x13, :manually_enter_user_code_exceeds_code_limit},
{0x06, :access_control, 0x14, :unlock_by_rf_with_invalid_user_code},
{0x06, :access_control, 0x15, :locked_by_rf_with_invalid_user_code},
{0x06, :access_control, 0x16, :window_or_door_is_open},
{0x06, :access_control, 0x17, :window_or_door_is_closed},
{0x06, :access_control, 0x18, :window_or_door_handle_is_open},
{0x06, :access_control, 0x19, :window_or_door_handle_is_closed},
{0x06, :access_control, 0x20, :messaging_user_code_entered_via_keypad},
{0x06, :access_control, 0x40, :barrier_performing_initialization_process},
{0x06, :access_control, 0x41, :barrier_operation_exceeded_time_limit},
{0x06, :access_control, 0x42, :barrier_operation_force_exceeded},
{0x06, :access_control, 0x43, :barrier_operation_exceeded_mechanical_limit},
{0x06, :access_control, 0x44, :barrier_unable_to_perform_operation_ul_requirements},
{0x06, :access_control, 0x45, :barrier_unattended_operation_disabled_ul_requirements},
{0x06, :access_control, 0x46, :barrier_failed_operation_malfunction},
{0x06, :access_control, 0x47, :barrier_vacation_mode},
{0x06, :access_control, 0x48, :barrier_safety_beam_obstacle},
{0x06, :access_control, 0x49, :barrier_sensor_not_detected},
{0x06, :access_control, 0x4A, :barrier_sensor_low_battery},
{0x06, :access_control, 0x4B, :barrier_short_in_wall_station_wires},
# Home Security (0x07)
{0x07, :home_security, 0x00, :state_idle},
{0x07, :home_security, 0x01, :intrusion_location_provided},
{0x07, :home_security, 0x02, :intrusion},
{0x07, :home_security, 0x03, :tampering_product_cover_removed},
{0x07, :home_security, 0x04, :tampering_invalid_code},
{0x07, :home_security, 0x05, :glass_breakage_location_provided},
{0x07, :home_security, 0x06, :glass_breakage},
{0x07, :home_security, 0x07, :motion_detection_location_provided},
{0x07, :home_security, 0x08, :motion_detection},
{0x07, :home_security, 0x09, :tampering_product_moved},
{0x07, :home_security, 0x0A, :impact_detected},
{0x07, :home_security, 0x0B, :magnetic_field_interface_detected},
{0x07, :home_security, 0xFE, :unknown_state},
# Siren (0x0E)
{0x0E, :siren, 0x00, :state_idle},
{0x0E, :siren, 0x01, :state_active}
]
defmacro __before_compile__(_) do
type_to_byte =
for {alarm_type_byte, alarm_type_name, _, _} <-
Enum.uniq_by(@table, fn {_, type, _, _} -> type end) do
quote do
def type_to_byte(unquote(alarm_type_name)), do: unquote(alarm_type_byte)
end
end
event_to_byte =
for {_alarm_type_byte, alarm_type_name, alarm_event_byte, alarm_event_name} <- @table do
quote do
def event_to_byte(unquote(alarm_type_name), unquote(alarm_event_name)) do
unquote(alarm_event_byte)
end
end
end
type_from_byte =
for {alarm_type_byte, alarm_type_name, _, _} <-
Enum.uniq_by(@table, fn {byte, _, _, _} -> byte end) do
quote do
def type_from_byte(unquote(alarm_type_byte)), do: {:ok, unquote(alarm_type_name)}
end
end
event_from_byte =
for {_, type, event_byte, event} <- @table do
quote do
def event_from_byte(unquote(type), unquote(event_byte)), do: {:ok, unquote(event)}
end
end
quote do
@type event :: atom()
@type type :: atom()
@doc """
Get the byte for the event name
"""
@spec type_to_byte(type()) :: byte()
unquote(type_to_byte)
@doc """
Get the event byte given the event type
"""
@spec event_to_byte(type(), event()) :: byte()
unquote(event_to_byte)
@doc """
Get the type from the byte
"""
@spec type_from_byte(byte()) :: {:ok, type()} | {:error, :invalid_type_byte}
unquote(type_from_byte)
def type_from_byte(_), do: {:error, :invalid_type_byte}
@doc """
Get the event from the byte given the notification type
"""
@spec event_from_byte(type(), byte()) :: {:ok, event()} | {:error, :invalid_event_byte}
unquote(event_from_byte)
def event_from_byte(_, _), do: {:error, :invalid_event_byte}
end
end
end
@type zwave_alarm_status :: :deactivate | :activate
@before_compile Generate
def encode_event_params(_zwave_type, _zwave_event, []), do: <<>>
def encode_event_params(:access_control, zwave_event, event_params_list)
when zwave_event in [:keypad_lock_operation, :keypad_unlock_operation, :new_user_code_added] do
{:ok, user_code_report} = UserCodeReport.new(event_params_list)
<<UserCode.byte(), user_code_report.command_byte>> <>
UserCodeReport.encode_params(user_code_report)
end
def encode_event_params(:home_security, zwave_event, event_params_list)
when zwave_event in [
:intrusion_location_provided,
:glass_breakage_location_provided,
:motion_detection_location_provided
] do
{:ok, node_location_report} = NodeLocationReport.new(event_params_list)
<<NodeNaming.byte(), node_location_report.command_byte>> <>
NodeLocationReport.encode_params(node_location_report)
end
def encode_event_params(:smoke_alarm, :smoke_detected_location_provided, event_params_list) do
{:ok, node_location_report} = NodeLocationReport.new(event_params_list)
<<NodeNaming.byte(), node_location_report.command_byte>> <>
NodeLocationReport.encode_params(node_location_report)
end
def encode_event_params(:water, zwave_event, event_params_list)
when zwave_event in [
:water_leak_detected_location_provided,
:water_level_droppped_location_provided
] do
{:ok, node_location_report} = NodeLocationReport.new(event_params_list)
<<NodeNaming.byte(), node_location_report.command_byte>> <>
NodeLocationReport.encode_params(node_location_report)
end
def encode_event_params(zwave_type, zwave_event, event_params) do
Logger.info(
"[Grizzly] Encoding not implemented for event params #{inspect(event_params)} for zwave_type #{
inspect(zwave_type)
} and zwave_event #{inspect(zwave_event)}"
)
<<>>
end
def decode_event_params(zwave_type, zwave_event, <<>>) do
Logger.info(
"[Grizzly] No event parameters for #{inspect(zwave_type)} #{inspect(zwave_event)}"
)
{:ok, []}
end
def decode_event_params(:access_control, zwave_event, params_binary)
when zwave_event in [:keypad_lock_operation, :keypad_unlock_operation] do
with {:ok, user_code_report} <- Decoder.from_binary(params_binary) do
{:ok, user_code_report.params}
else
{:error, %DecodeError{}} = decode_error ->
Logger.warn("[Grizzly] Failed to decode UserCodeReport from #{inspect(params_binary)}")
decode_error
end
end
def decode_event_params(:home_security, zwave_event, params_binary)
when zwave_event in [
:intrusion_location_provided,
:glass_breakage_location_provided,
:motion_detection_location_provided
] do
with {:ok, node_location_report} <- Decoder.from_binary(params_binary) do
{:ok, node_location_report.params}
else
{:error, %DecodeError{}} = decode_error ->
Logger.warn(
"[Grizzly] Failed to decode NodeLocationReport from #{inspect(params_binary)}"
)
decode_error
end
end
def decode_event_params(:water, zwave_event, params_binary)
when zwave_event in [
:water_leak_detected_location_provided,
:water_level_droppped_location_provided
] do
with {:ok, node_location_report} <- Decoder.from_binary(params_binary) do
{:ok, node_location_report.params}
else
{:error, %DecodeError{}} = decode_error ->
Logger.warn(
"[Grizzly] Failed to decode NodeLocationReport from #{inspect(params_binary)}"
)
decode_error
end
end
def decode_event_params(:smoke_alarm, :smoke_detected_location_provided, params_binary) do
with {:ok, node_location_report} <- Decoder.from_binary(params_binary) do
{:ok, node_location_report.params}
else
{:error, %DecodeError{}} = decode_error ->
Logger.warn(
"[Grizzly] Failed to decode NodeLocationReport from #{inspect(params_binary)}"
)
decode_error
end
end
def decode_event_params(zwave_type, zwave_event, params_binary) do
Logger.info(
"Decoding not implemented for event params #{inspect(params_binary)} for zwave_type #{
inspect(zwave_type)
} and zwave_event #{inspect(zwave_event)}"
)
{:ok, []}
end
@type status :: :enabled | :disabled
def status_to_byte(:disabled), do: 0x00
def status_to_byte(:enabled), do: 0xFF
def status_from_byte(0x00), do: {:ok, :disabled}
def status_from_byte(0xFF), do: {:ok, :enabled}
def status_from_byte(_byte), do: {:error, :invalid_status_byte}
@spec encode_alarm_types([atom]) :: binary
def encode_alarm_types(alarm_types) do
for bit_list <- byte_indices(alarm_types, @alarm_types) do
for bit <- Enum.reverse(bit_list), into: <<>>, do: <<bit::1>>
end
|> :binary.list_to_bin()
end
@spec encode_type_events(atom, [atom]) :: binary
def encode_type_events(type, events) do
reference = Keyword.fetch!(@type_events, type)
for bit_list <- byte_indices(events, reference) do
for bit <- Enum.reverse(bit_list), into: <<>>, do: <<bit::1>>
end
|> :binary.list_to_bin()
end
defp byte_indices(list_of_lists, reference) do
for byte <- (Enum.count(reference) - 1)..0 do
items_per_byte = Enum.at(reference, byte)
for index <- 0..7 do
if Enum.at(items_per_byte, index) in list_of_lists, do: 1, else: 0
end
end
|> Enum.drop_while(fn indices -> Enum.all?(indices, &(&1 == 0)) end)
|> Enum.reverse()
end
@spec decode_alarm_types(binary) :: {:ok, [atom()]} | {:error, :invalid_type}
def decode_alarm_types(binary) do
alarm_types =
:binary.bin_to_list(binary)
|> Enum.map(&bit_set_indices(<<&1>>))
|> Enum.with_index()
|> Enum.map(fn {bit_indices, byte} ->
Enum.map(bit_indices, &decode_bit(byte, &1, @alarm_types))
end)
|> List.flatten()
if Enum.any?(alarm_types, &(&1 == nil)) do
{:error, :invalid_type}
else
{:ok, alarm_types}
end
end
@spec decode_type_events(atom, binary) ::
{:error, :invalid_type} | {:error, :invalid_type_event} | {:ok, [atom()]}
def decode_type_events(type, binary) do
with {:ok, reference} <- Keyword.fetch(@type_events, type) do
type_events =
:binary.bin_to_list(binary)
|> Enum.map(&bit_set_indices(<<&1>>))
|> Enum.with_index()
|> Enum.map(fn {bit_indices, byte} ->
Enum.map(bit_indices, &decode_bit(byte, &1, reference))
end)
|> List.flatten()
if Enum.any?(type_events, &(&1 == nil)) do
{:error, :invalid_type_event}
else
{:ok, type_events}
end
else
:error ->
{:error, :invalid_type_byte}
end
end
defp bit_set_indices(byte) do
for(<<x::1 <- byte>>, do: x)
|> Enum.reverse()
|> Enum.with_index()
|> Enum.reduce([], fn {bit, index}, acc ->
if bit == 1, do: [index | acc], else: acc
end)
end
defp decode_bit(byte, bit_index, reference) do
Enum.at(reference, byte) |> Enum.at(bit_index)
end
end