Packages
grizzly
9.1.2
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
@moduledoc """
Encoding and decoding functions for notification events and types from the
Notification command class.
"""
import Grizzly.ZWave.Encoding
alias Grizzly.ZWave
alias Grizzly.ZWave.Commands
alias Grizzly.ZWave.DecodeError
alias Grizzly.ZWave.ZWEnum
require Logger
@type event :: atom()
@type type :: atom()
@type zwave_alarm_status :: :deactivate | :activate
@type status :: :enabled | :disabled
table = %{
{0x01, :smoke_alarm} => %{
0x00 => :state_idle,
0x01 => :smoke_detected_location_provided,
0x02 => :smoked_detected,
0x03 => :smoke_alarm_test,
0x04 => :replacement_required,
0x05 => :replacement_required_end_of_life,
0x06 => :alarm_silenced,
0x07 => :maintenance_required_planned_periodic_inspection,
0x08 => :maintenance_required_dust_in_device,
0xFE => :unknown
},
{0x02, :co_alarm} => %{
0x00 => :state_idle,
0x01 => :co_detected_location_provided,
0x02 => :co_detected,
0x03 => :co_test,
0x04 => :replacement_required,
0x05 => :replacement_required_end_of_life,
0x06 => :alarm_silenced,
0x07 => :maintenance_required_planned_periodic_inspection,
0xFE => :unknown
},
{0x03, :co2_alarm} => %{
0x00 => :state_idle,
0x01 => :co2_detected_location_provided,
0x02 => :co2_detected,
0x03 => :co2_test,
0x04 => :replacement_required,
0x05 => :replacement_required_end_of_life,
0x06 => :alarm_silenced,
0x07 => :maintenance_required_planned_periodic_inspection,
0xFE => :unknown
},
{0x04, :heat_alarm} => %{
0x00 => :state_idle,
0x01 => :overheat_detected_location_provided,
0x02 => :overheat_detected,
0x03 => :rapid_temperature_rise_location_provided,
0x04 => :rapid_temperature_rise,
0x05 => :underheat_detected_location_provided,
0x06 => :underheat_detected,
0x07 => :heat_alarm_test,
0x08 => :replacement_required_end_of_life,
0x09 => :alarm_silenced,
0x0A => :maintenance_required_dust_in_device,
0x0B => :maintenance_required_planned_periodic_inspection,
0x0C => :rapid_temperature_fall_location_provided,
0x0D => :rapid_temperature_fall,
0xFE => :unknown
},
{0x05, :water_alarm} => %{
0x00 => :state_idle,
0x01 => :water_leak_detected_location_provided,
0x02 => :water_leak_detected,
0x03 => :water_level_dropped_location_provided,
0x04 => :water_level_dropped,
0x05 => :replace_water_filter,
0x06 => :water_flow_alarm,
0x07 => :water_pressure_alarm,
0x08 => :water_temperature_alarm,
0x09 => :water_level_alarm,
0x0A => :sump_pump_active,
0x0B => :sump_pump_failure,
0xFE => :unknown
},
{0x06, :access_control} => %{
0x00 => :state_idle,
0x01 => :manual_lock_operation,
0x02 => :manual_unlock_operation,
0x03 => :rf_lock_operation,
0x04 => :rf_unlock_operation,
0x05 => :keypad_lock_operation,
0x06 => :keypad_unlock_operation,
0x07 => :manual_not_fully_locked_operation,
0x08 => :rf_not_fully_locked_operation,
0x09 => :auto_lock_locked_operation,
0x0A => :auto_lock_not_fully_locked_operation,
0x0B => :lock_jammed,
0x0C => :all_user_codes_deleted,
0x0D => :single_user_code_deleted,
0x0E => :new_user_code_added,
0x0F => :new_user_code_not_added_duplicate,
0x10 => :keypad_temporarily_disabled,
0x11 => :keypad_busy,
0x12 => :new_program_code_entered,
0x13 => :manually_enter_user_code_exceeds_code_limit,
0x14 => :unlock_by_rf_with_invalid_user_code,
0x15 => :locked_by_rf_with_invalid_user_code,
0x16 => :window_or_door_is_open,
0x17 => :window_or_door_is_closed,
0x18 => :window_or_door_handle_is_open,
0x19 => :window_or_door_handle_is_closed,
0x20 => :messaging_user_code_entered_via_keypad,
0x21 => :lock_operation_with_user_code,
0x22 => :unlock_operation_with_user_code,
0x23 => :credential_lock_operation,
0x24 => :credential_unlock_operation,
0x25 => :all_users_deleted,
0x26 => :multiple_credentials_deleted,
0x27 => :user_added,
0x28 => :user_modified,
0x29 => :user_deleted,
0x2A => :user_unchanged,
0x2B => :credential_added,
0x2C => :credential_modified,
0x2D => :credential_deleted,
0x2E => :credential_unchanged,
0x2F => :valid_credential_denied_user_disabled,
0x30 => :valid_credential_denied_user_schedule_not_active,
0x31 => :user_access_denied_too_few_credentials,
0x32 => :invalid_credential,
0x33 => :non_access_credential_used,
0x40 => :barrier_performing_initialization_process,
0x41 => :barrier_operation_force_exceeded,
0x42 => :barrier_operation_exceeded_time_limit,
0x43 => :barrier_operation_exceeded_mechanical_limit,
0x44 => :barrier_unable_to_perform_operation_ul_requirements,
0x45 => :barrier_unattended_operation_disabled_ul_requirements,
0x46 => :barrier_failed_operation_malfunction,
0x47 => :barrier_vacation_mode,
0x48 => :barrier_safety_beam_obstacle,
0x49 => :barrier_sensor_not_detected,
0x4A => :barrier_sensor_low_battery,
0x4B => :barrier_short_in_wall_station_wires,
0x4C => :barrier_associated_with_non_zwave_remote,
0xFE => :unknown
},
{0x07, :home_security} => %{
0x00 => :state_idle,
0x01 => :intrusion_location_provided,
0x02 => :intrusion,
0x03 => :tampering_product_cover_removed,
0x04 => :tampering_invalid_code,
0x05 => :glass_breakage_location_provided,
0x06 => :glass_breakage,
0x07 => :motion_detection_location_provided,
0x08 => :motion_detection,
0x09 => :tampering_product_moved,
0x0A => :impact_detected,
0x0B => :magnetic_field_interface_detected,
0x0C => :rf_jamming_detected,
0xFE => :unknown
},
{0x08, :power_management} => %{
0x00 => :state_idle,
0x01 => :power_applied,
0x02 => :ac_mains_disconnected,
0x03 => :ac_mains_reconnected,
0x04 => :surge_detected,
0x05 => :voltage_drop,
0x06 => :over_current_detected,
0x07 => :over_voltage_detected,
0x08 => :over_load_detected,
0x09 => :load_error,
0x0A => :replace_battery_soon,
0x0B => :replace_battery_now,
0x0C => :battery_is_charging,
0x0D => :battery_is_fully_charged,
0x0E => :charge_battery_soon,
0x0F => :charge_battery_now,
0x10 => :back_up_battery_low,
0x11 => :battery_fluid_is_low,
0x12 => :back_up_battery_disconnected,
0x13 => :dc_connected,
0x14 => :dc_disconnected,
0xFE => :unknown
},
{0x09, :system} => %{
0x00 => :state_idle,
0x01 => :system_hardware_failure,
0x02 => :system_software_failure,
0x03 => :system_hardware_failure_code_provided,
0x04 => :system_software_failure_code_provided,
0x05 => :heartbeat,
0x06 => :tampering_product_cover_removed,
0x07 => :emergency_shutoff,
0x08 => :undefined,
0x09 => :digital_input_high_state,
0x0A => :digital_input_low_state,
0x0B => :digital_input_open,
0xFE => :unknown
},
{0x0A, :emergency_alarm} => %{
0x00 => :state_idle,
0x01 => :contact_police,
0x02 => :contact_fire_service,
0x03 => :contact_medical_service,
0x04 => :panic_alert,
0xFE => :unknown
},
{0x0B, :clock} => %{
0x00 => :state_idle,
0x01 => :wake_up_alert,
0x02 => :timer_ended,
0x03 => :time_remaining,
0xFE => :unknown
},
{0x0C, :appliance} => %{
0x00 => :state_idle,
0x01 => :program_started,
0x02 => :program_in_progress,
0x03 => :program_completed,
0x04 => :replace_main_filter,
0x05 => :failure_to_set_target_temperature,
0x06 => :supplying_water,
0x07 => :water_supply_failure,
0x08 => :boiling,
0x09 => :boiling_failure,
0x0A => :washing,
0x0B => :washing_failure,
0x0C => :rinsing,
0x0D => :rinsing_failure,
0x0E => :draining,
0x0F => :draining_failure,
0x10 => :spinning,
0x11 => :spinning_failure,
0x12 => :drying,
0x13 => :drying_failure,
0x14 => :fan_failure,
0x15 => :compressor_failure,
0xFE => :unknown
},
{0x0D, :home_health} => %{
0x00 => :state_idle,
0x01 => :leaving_bed,
0x02 => :sitting_on_bed,
0x03 => :lying_on_bed,
0x04 => :posture_changed,
0x05 => :sitting_on_bed_edge,
0x06 => :volatile_organic_compound_level,
0x07 => :sleep_apnea_detected,
0x08 => :sleep_stage_0_detected,
0x09 => :sleep_stage_1_detected,
0x0A => :sleep_stage_2_detected,
0x0B => :sleep_stage_3_detected,
0x0C => :fall_detected,
0xFE => :unknown
},
{0x0E, :siren} => %{
0x00 => :state_idle,
0x01 => :state_active,
0xFE => :unknown
},
{0x0F, :water_valve} => %{
0x00 => :state_idle,
0x01 => :valve_operation,
0x02 => :main_valve_operation,
0x03 => :valve_short_circuit,
0x04 => :main_valve_short_circuit,
0x05 => :valve_current_alarm,
0x06 => :main_current_alarm,
0x07 => :valve_jammed,
0xFE => :unknown
},
{0x10, :weather_alarm} => %{
0x00 => :state_idle,
0x01 => :rain_alarm,
0x02 => :moisture_alarm,
0x03 => :freeze_alarm,
0xFE => :unknown
},
{0x11, :irrigation} => %{
0x00 => :state_idle,
0x01 => :schedule_started,
0x02 => :schedule_finished,
0x03 => :valve_table_run_started,
0x04 => :valve_table_run_finished,
0x05 => :device_not_configured,
0xFE => :unknown
},
{0x12, :gas_alarm} => %{
0x00 => :state_idle,
0x01 => :combustible_gas_detected_location_provided,
0x02 => :combustible_gas_detected,
0x03 => :toxic_gas_detected_location_provided,
0x04 => :toxic_gas_detected,
0x05 => :gas_alarm_test,
0x06 => :replacement_required,
0xFE => :unknown
},
{0x13, :pest_control} => %{
0x00 => :state_idle,
0x01 => :trap_armed_location_provided,
0x02 => :trap_armed,
0x03 => :trap_rearm_required_location_provided,
0x04 => :trap_rearm_required,
0x05 => :pest_detected_location_provided,
0x06 => :pest_detected,
0x07 => :pest_exterminated_location_provided,
0x08 => :pest_exterminated,
0xFE => :unknown
},
{0x14, :light_sensor} => %{
0x00 => :state_idle,
0x01 => :light_detected,
0x02 => :light_color_transition_detected,
0xFE => :unknown
},
{0x15, :water_quality_monitoring} => %{
0x00 => :state_idle,
0x01 => :chlorine_alarm,
0x02 => :acidity_alarm,
0x03 => :water_oxidation_alarm,
0x04 => :chlorine_empty,
0x05 => :acidity_empty,
0x06 => :waterflow_measuring_station_shortage_detected,
0x07 => :waterflow_clear_water_shortage_detected,
0x08 => :disinfection_system_error_detected,
0x09 => :filter_cleaning_ongoing,
0x0A => :heating_operation_ongoing,
0x0B => :filter_pump_operation_ongoing,
0x0C => :freshwater_operation_ongoing,
0x0D => :dry_protection_operation_active,
0x0E => :water_tank_empty,
0x0F => :water_tank_level_unknown,
0x10 => :water_tank_full,
0x11 => :collective_disorder,
0xFE => :unknown
},
{0x16, :home_monitoring} => %{
0x00 => :state_idle,
0x01 => :home_occupied_location_provided,
0x02 => :home_occupied,
0xFE => :unknown
}
}
@types table |> Map.keys() |> ZWEnum.new(fn {k, v} -> {v, k} end)
@event_from_byte Map.new(table, fn {{_, type}, events} ->
{type, Map.new(events, fn {byte, event} -> {byte, event} end)}
end)
@event_to_byte Map.new(table, fn {{_, type}, events} ->
{type, Map.new(events, fn {byte, event} -> {event, byte} end)}
end)
@doc """
Lists all notification types.
"""
@spec all_notification_types() :: [atom()]
def all_notification_types(), do: ZWEnum.keys(@types)
@doc false
def types(), do: @types
@doc """
Lists all events associated with the given notification type.
"""
def all_events_by_type(type) do
@event_from_byte |> Map.fetch!(type) |> Map.values()
end
@doc """
Encode a notification type.
"""
@spec type_to_byte(type()) :: byte()
def type_to_byte(type) do
ZWEnum.fetch!(@types, type)
end
@doc """
Encode a notification event.
"""
@spec event_to_byte(type(), event()) :: byte()
# :state_idle is always encoded as 0x00 no matter the type
def event_to_byte(_type, :state_idle), do: 0x00
def event_to_byte(type, event) do
with nil <- get_in(@event_to_byte, [type, event]) do
raise KeyError, "Unknown notification event: #{inspect(type)} / #{inspect(event)}"
end
end
@doc """
Decode a notification type.
"""
@spec type_from_byte(byte()) :: {:ok, type()} | {:error, :invalid_type_byte}
def type_from_byte(byte) do
case ZWEnum.fetch_key(@types, byte) do
{:ok, type} -> {:ok, type}
# There is no decoding for Alarm CC V1 - the byte is the type
:error -> {:error, :invalid_type_byte}
end
end
@doc """
Decode a notification event.
"""
@spec event_from_byte(type(), byte()) :: {:ok, event()} | {:error, :invalid_event_byte}
# Event 0x00 is always :state_idle even if the type is not known
def event_from_byte(_type, 0), do: {:ok, :state_idle}
def event_from_byte(type, byte) do
with v when not is_nil(v) <- get_in(@event_from_byte, [type, byte]) do
{:ok, v}
else
# Alarm reports might not provide a defined event (e.g. event byte is 0)
_ -> {:error, :invalid_event_byte}
end
end
@spec status_to_byte(status()) :: 0x00 | 0xFF
def status_to_byte(:disabled), do: 0x00
def status_to_byte(:enabled), do: 0xFF
@spec status_from_byte(byte()) :: {:ok, status()} | {:error, :invalid_status_byte}
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}
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} = Commands.create(:user_code_report, event_params_list)
ZWave.to_binary(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} = Commands.create(:node_location_report, event_params_list)
ZWave.to_binary(node_location_report)
end
def encode_event_params(:smoke_alarm, :smoke_detected_location_provided, event_params_list) do
{:ok, node_location_report} = Commands.create(:node_location_report, event_params_list)
ZWave.to_binary(node_location_report)
end
def encode_event_params(:water_alarm, zwave_event, event_params_list)
when zwave_event in [
:water_leak_detected_location_provided,
:water_level_dropped_location_provided
] do
{:ok, node_location_report} = Commands.create(:node_location_report, event_params_list)
ZWave.to_binary(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
# Some locks do not encode an encapsulated UserCodeReport as event parameters; they only give the user id
def decode_event_params(:access_control, zwave_event, <<user_id>>)
when zwave_event in [:keypad_lock_operation, :keypad_unlock_operation] do
{:ok,
[
user_id: user_id,
user_id_status: :status_not_available,
user_code: ""
]}
end
# Keypad lock/unlock unlock operation
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} <- Grizzly.ZWave.from_binary(params_binary) do
{:ok, user_code_report.params}
else
{:error, %DecodeError{}} = decode_error ->
Logger.warning("[Grizzly] Failed to decode UserCodeReport from #{inspect(params_binary)}")
decode_error
end
end
def decode_event_params(:access_control, zwave_event, params_binary)
when zwave_event in [
:credential_lock_operation,
:credential_unlock_operation,
:valid_credential_denied_user_disabled,
:valid_credential_denied_user_schedule_not_active,
:messaging_user_code_entered_via_keypad
] do
case decode_credential_usage_data(params_binary) do
{:ok, data} ->
{:ok, data}
:error ->
Logger.warning(
"[Grizzly] Failed to decode credential usage data from #{inspect(params_binary)}"
)
{:ok, []}
end
end
# Yale Pro 2 locks send a 2-byte user id for new user code added events
def decode_event_params(:access_control, zwave_event, <<user_id::16>>)
when zwave_event in [
:new_user_code_added,
:single_user_code_deleted
] do
{:ok, [user_id: user_id]}
end
def decode_event_params(:access_control, zwave_event, params_binary)
when zwave_event in [
:single_user_code_deleted,
:new_user_code_added,
:new_user_code_not_added_duplicate
] do
with {:ok, report} <- Grizzly.ZWave.from_binary(params_binary) do
{:ok, report.params}
else
{:error, %DecodeError{}} = decode_error ->
Logger.error(
"[Grizzly] Failed to params for access code event #{inspect(zwave_event)}: #{inspect(params_binary)}"
)
decode_error
end
rescue
e ->
Logger.error(
"[Grizzly] Failed to decode access control event params\n\n#{Exception.format(:error, e, __STACKTRACE__)}\n\n#{inspect(binding(), pretty: true)}"
)
{:ok, []}
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} <- Grizzly.ZWave.from_binary(params_binary) do
{:ok, node_location_report.params}
else
{:error, %DecodeError{}} = decode_error ->
Logger.warning(
"[Grizzly] Failed to decode NodeLocationReport from #{inspect(params_binary)}"
)
decode_error
end
end
def decode_event_params(notification_type, :state_idle, <<byte::8>>) do
case event_from_byte(notification_type, byte) do
{:ok, event} ->
{:ok, [state: event]}
{:error, :invalid_event_byte} ->
Logger.warning(
"[Grizzly] Failed to decode state variable from #{inspect(notification_type)} state_idle event"
)
{:ok, []}
end
end
def decode_event_params(:water_alarm, zwave_event, params_binary)
when zwave_event in [
:water_leak_detected_location_provided,
:water_level_dropped_location_provided
] do
with {:ok, node_location_report} <- Grizzly.ZWave.from_binary(params_binary) do
{:ok, node_location_report.params}
else
{:error, %DecodeError{}} = decode_error ->
Logger.warning(
"[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} <- Grizzly.ZWave.from_binary(params_binary) do
{:ok, node_location_report.params}
else
{:error, %DecodeError{}} = decode_error ->
Logger.warning(
"[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, [raw: :erlang.binary_to_list(params_binary)]}
end
defp decode_credential_usage_data(<<user_id::16, _credentials_count::8, rest::binary>>) do
credentials_used =
for <<credential_type::8, credential_slot_id::16 <- rest>> do
credential_type =
case ZWEnum.decode(uc_credential_types(), credential_type) do
{:ok, ct} -> ct
:error -> credential_type
end
%{type: credential_type, slot_id: credential_slot_id}
end
{:ok, [user_id: user_id, credentials_used: credentials_used]}
end
defp decode_credential_usage_data(_binary), do: :error
end