Packages
grizzly
5.2.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/unsolicited_server/response_handler.ex
defmodule Grizzly.UnsolicitedServer.ResponseHandler do
@moduledoc false
# module helper for handling various different responses from the Z-Wave PAN
# network
require Logger
alias Grizzly.{Transport, VersionReports}
alias Grizzly.{Associations, ZWave}
alias Grizzly.ZWave.Command
alias Grizzly.ZWave.Commands.{
AssociationReport,
AssociationGroupingsReport,
AssociationGroupNameReport,
AssociationGroupCommandListReport,
AssociationGroupInfoReport,
AssociationSpecificGroupReport,
MultiChannelAssociationGroupingsReport,
MultiChannelAssociationReport,
SupervisionReport
}
@type opt() :: {:association_server, GenServer.name()}
@type action() ::
{:notify, Command.t()} | {:send, Command.t()} | {:forward_to_controller, Command.t()}
@doc """
When a transport receives a response from the Z-Wave network handle it
and send any other commands back over the Z-Wave PAN if needed
"""
@spec handle_response(Transport.Response.t(), [opt()]) :: [action()] | {:error, reason :: any()}
def handle_response(response, opts \\ []) do
internal_command = Command.param!(response.command, :command)
case handle_command(internal_command, opts) do
{:error, _any} = error ->
error
actions when is_list(actions) ->
actions
end
end
defp handle_command(%Command{name: :supervision_get} = command, opts) do
{:ok, supervision_report} = make_supervision_report(command)
encapsulated_command = Command.param!(command, :encapsulated_command)
case ZWave.from_binary(encapsulated_command) do
{:ok, report} ->
# We need to process the internal command and get the actions that need
# to be preformed. The supervision report must come last in the chain of
# actions. See SDS13783 section 3.7.2.2 for more information.
actions = handle_command(report, opts)
actions ++ [{:send, supervision_report}]
{:error, reason} ->
Logger.warn(
"Failed to parse: #{inspect(encapsulated_command)} notification for reason: #{inspect(reason)}"
)
[]
end
end
defp handle_command(%Command{name: :association_specific_group_get}, _) do
case AssociationSpecificGroupReport.new(group: 0) do
{:ok, command} -> [{:send, command}]
end
end
defp handle_command(%Command{name: :association_get}, opts) do
# According the the Z-Wave specification if a get request contains an
# unsupported grouping identifier then we should report back the grouping
# information for group number 1. Since right now we only support that
# group we don't need to check because we will always just send back the
# grouping information for group id 1.
associations_server = Keyword.get(opts, :associations_server, Associations)
{:ok, respond_with_command} =
case Associations.get(associations_server, 1) do
nil ->
AssociationReport.new(
grouping_identifier: 1,
max_nodes_supported: 1,
nodes: []
)
association ->
AssociationReport.new(
grouping_identifier: association.grouping_id,
max_nodes_supported: 1,
nodes: association.node_ids
)
end
[{:send, respond_with_command}]
end
defp handle_command(%Command{name: :association_set} = command, opts) do
associations_server = Keyword.get(opts, :associations_server, Associations)
grouping_identifier = Command.param!(command, :grouping_identifier)
# According the Z-Wave specification we should just ignore grouping ids that
# we don't support. As of right now we are only supporting grouping id 1, so
# if the command contains something other than one we will just move along.
if grouping_identifier == 1 do
nodes = Command.param!(command, :nodes)
:ok = Associations.save(associations_server, grouping_identifier, nodes)
end
[]
end
defp handle_command(%Command{name: :association_groupings_get}, _opts) do
case AssociationGroupingsReport.new(supported_groupings: 1) do
{:ok, command} -> [{:send, command}]
end
end
defp handle_command(%Command{name: :association_remove} = command, opts) do
associations_server = Keyword.get(opts, :associations_server, Associations)
grouping_id = Command.param!(command, :grouping_identifier)
nodes = Command.param!(command, :nodes)
# This case matching is based off a table from the Z-Wave specification
# Right now we only have one association grouping, so the first and third
# match are the same right now. I want to keep the matching explicit right
# now as I hope it will enable quicker understand to any future work that
# will be as it maps nicely to the documentation found in the Z-Wave
# specification.
case {grouping_id, nodes} do
{1, []} ->
:ok = Associations.delete_all_nodes_from_grouping(associations_server, grouping_id)
[]
{1, nodes} ->
case Associations.delete_nodes_from_grouping(associations_server, grouping_id, nodes) do
:ok -> []
error -> error
end
{0, []} ->
:ok = Associations.delete_all(associations_server)
[]
{0, nodes} ->
:ok = Associations.delete_nodes_from_all_groupings(associations_server, nodes)
[]
_ ->
[]
end
end
defp handle_command(%Command{name: :association_group_name_get}, _opts) do
# Always just return the lifeline group (group_id == 1) as of right now
# because that is all that Grizzly supports right now.
case AssociationGroupNameReport.new(group_id: 1, name: "Lifeline") do
{:ok, command} -> [{:send, command}]
end
end
defp handle_command(%Command{name: :association_group_info_get} = command, _opts) do
{:ok, report} =
AssociationGroupInfoReport.new(
dynamic: false,
groups_info: [[group_id: 1, profile: :general_lifeline]],
list_mode: Command.param(command, :all, false)
)
[{:send, report}]
end
defp handle_command(%Command{name: :association_group_command_list_get}, _opts) do
{:ok, report} =
AssociationGroupCommandListReport.new(
group_id: 0x01,
commands: [:device_reset_locally_notification]
)
[{:send, report}]
end
defp handle_command(%Command{name: :multi_channel_association_groupings_get}, _opts) do
{:ok, report} = MultiChannelAssociationGroupingsReport.new(supported_groupings: 1)
[{:send, report}]
end
defp handle_command(%Command{name: :multi_channel_association_get}, opts) do
# According the the Z-Wave specification if a get request contains an
# unsupported grouping identifier then we should report back the grouping
# information for group number 1. Since right now we only support that
# group we don't need to check because we will always just send back the
# grouping information for group id 1.
associations_server = Keyword.get(opts, :associations_server, Associations)
{:ok, respond_with_command} =
case Associations.get(associations_server, 1) do
nil ->
MultiChannelAssociationReport.new(
grouping_identifier: 1,
max_nodes_supported: 1,
nodes: []
)
association ->
MultiChannelAssociationReport.new(
grouping_identifier: association.grouping_id,
max_nodes_supported: 1,
nodes: association.node_ids
)
end
[{:send, respond_with_command}]
end
defp handle_command(%Command{name: :multi_channel_association_set} = command, opts) do
associations_server = Keyword.get(opts, :associations_server, Associations)
grouping_identifier = Command.param!(command, :grouping_identifier)
# According the Z-Wave specification we should just ignore grouping ids that
# we don't support. As of right now we are only supporting grouping id 1, so
# if the command contains something other than one we will just move along.
if grouping_identifier == 1 do
nodes = Command.param!(command, :nodes)
:ok = Associations.save(associations_server, grouping_identifier, nodes)
end
[]
end
defp handle_command(%Command{name: :multi_channel_association_remove} = command, opts) do
associations_server = Keyword.get(opts, :associations_server, Associations)
grouping_id = Command.param!(command, :grouping_identifier)
nodes = Command.param!(command, :nodes)
# This case matching is based off a table from the Z-Wave specification
# Right now we only have one association grouping, so the first and third
# match are the same right now. I want to keep the matching explicit right
# now as I hope it will enable quicker understand to any future work that
# will be as it maps nicely to the documentation found in the Z-Wave
# specification.
case {grouping_id, nodes} do
{1, []} ->
:ok = Associations.delete_all_nodes_from_grouping(associations_server, grouping_id)
[]
{1, nodes} ->
case Associations.delete_nodes_from_grouping(associations_server, grouping_id, nodes) do
:ok -> []
error -> error
end
{0, []} ->
:ok = Associations.delete_all(associations_server)
[]
{0, nodes} ->
:ok = Associations.delete_nodes_from_all_groupings(associations_server, nodes)
[]
_ ->
[]
end
end
defp handle_command(%Command{name: :multi_command_encapsulated} = command, opts) do
commands = Command.param!(command, :commands)
extra_commands = [
:supervision_get,
:association_group_command_list_get,
:association_group_name_get,
:association_group_info_get,
:association_get,
:association_set,
:association_remove,
:association_groupings_get,
:association_specific_group_get,
:multi_channel_association_groupings_get,
:multi_channel_association_set,
:multi_channel_association_get,
:multi_channel_association_remove
]
Enum.reduce(commands, [], fn cmd, actions ->
if Enum.member?(extra_commands, cmd) do
new_actions = handle_command(cmd, opts)
actions ++ new_actions
else
if cmd.name == :alarm_report do
actions ++ [{:notify, cmd}]
else
actions ++ [{:forward_to_controller, cmd}]
end
end
end)
end
defp handle_command(%Command{name: :version_command_class_get} = command, _opts) do
command_class = Command.param!(command, :command_class)
case VersionReports.version_report_for(command_class) do
{:ok, report} ->
[{:send, report}]
_ ->
[]
end
end
defp handle_command(command, _opts), do: [{:notify, command}]
def make_supervision_report(%Command{name: :supervision_get} = command) do
session_id = Command.param!(command, :session_id)
SupervisionReport.new(
session_id: session_id,
status: :success,
more_status_updates: :last_report,
duration: 0
)
end
end