-
Notifications
You must be signed in to change notification settings - Fork 9
/
association_group_info_report.ex
160 lines (137 loc) · 5.57 KB
/
association_group_info_report.ex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
defmodule Grizzly.ZWave.Commands.AssociationGroupInfoReport do
@moduledoc """
This command is used to advertise the properties of one or more association groups.
Params:
* `:dynamic` - whether the group info is subject to be changed by the device
* `:groups_info` - a list of group info
* `:list_mode` - a boolean if the report should be in list mode. In some
cases this needs to be forced, like if you are reporting one group but the
get query requested list mode. If you are reporting more than one group
then this parameter is optional as it will know that more than one group
can only be reported with list mode `true`. If you don't set this option
to `true` when reporting one group then this will default to `false`.
"""
@behaviour Grizzly.ZWave.Command
alias Grizzly.ZWave.{Command, DecodeError}
alias Grizzly.ZWave.CommandClasses.AssociationGroupInfo
@type group_info() :: [group_id: byte(), profile: atom()]
@type param() ::
{:groups_info, [group_info()]} | {:dynamic, boolean() | {:list_mode, boolean()}}
@impl Command
@spec new([param()]) :: {:ok, Command.t()}
def new(params) do
command = %Command{
name: :association_group_info_report,
command_byte: 0x04,
command_class: AssociationGroupInfo,
params: params,
impl: __MODULE__
}
{:ok, command}
end
@impl Command
def encode_params(command) do
list_mode = get_list_mode(command)
dynamic? = Command.param!(command, :dynamic)
dynamic_bit = if dynamic?, do: 0x01, else: 0x00
groups_info = Command.param!(command, :groups_info)
group_count = Enum.count(groups_info)
encoded_groups_info =
for group_info <- groups_info, into: <<>> do
group_id = Keyword.fetch!(group_info, :group_id)
profile = Keyword.fetch!(group_info, :profile)
<<group_id, 0x00>> <> encode_profile(Atom.to_string(profile)) <> <<0x00, 0x00, 0x00>>
end
<<list_mode::size(1), dynamic_bit::size(1), group_count::size(6)>> <> encoded_groups_info
end
@impl Command
def decode_params(
<<list_mode::size(1), dynamic_bit::size(1), group_count::size(6),
encoded_groups_info::binary>>
) do
dynamic? = dynamic_bit == 0x01
list_mode? = list_mode == 0x01
case decode_groups_info(group_count, encoded_groups_info) do
{:ok, groups_info} ->
{:ok, [dynamic: dynamic?, groups_info: groups_info, list_mode: list_mode?]}
{:error, %DecodeError{} = error} ->
error
end
end
defp decode_groups_info(group_count, encoded_groups_info) do
list = :erlang.binary_to_list(encoded_groups_info)
chunks = Enum.chunk_every(list, 7)
if Enum.count(chunks) != group_count do
%DecodeError{}
else
result =
Enum.reduce_while(
chunks,
[],
fn chunk, acc ->
binary = :erlang.list_to_binary(chunk)
case binary do
<<
group_id,
0x00,
profile_msb,
profile_lsb,
_reserved,
_event_code::size(16)
>> ->
profile = decode_profile(profile_msb, profile_lsb)
{:cont, [[group_id: group_id, profile: profile] | acc]}
_other ->
{:halt,
{:error,
%DecodeError{
value: binary,
param: :groups_info,
command: :association_group_info_report
}}}
end
end
)
case result do
{:error, error} -> {:error, error}
_groups_info -> {:ok, Enum.reverse(result)}
end
end
end
defp decode_profile(0x00, 0x00), do: :general_na
defp decode_profile(0x00, 0x01), do: :general_lifeline
defp decode_profile(0x20, key), do: :"control_key_#{key}"
defp decode_profile(0x31, 0x01), do: :sensor_air_temperature
defp decode_profile(0x31, 0x05), do: :sensor_humidity
defp decode_profile(0x71, 0x01), do: :notification_smoke_alarm
defp decode_profile(0x71, 0x03), do: :notification_co2_alarm
defp decode_profile(0x71, _), do: :notification_unknown
defp decode_profile(0x32, 0x01), do: :meter_electric_kwh
defp decode_profile(0x32, 0x02), do: :meter_gas
defp decode_profile(0x32, 0x03), do: :meter_water
defp decode_profile(0x32, _), do: :meter_unknown
defp decode_profile(0x6B, key), do: :"irrigation_channel_#{key}"
defp decode_profile(_profile_msb, _profile_lsb), do: :unknown
defp encode_profile("general_na"), do: <<0x00, 0x00>>
defp encode_profile("general_lifeline"), do: <<0x00, 0x01>>
defp encode_profile(<<"control_key_", key::binary>>), do: <<0x20, elem(Integer.parse(key), 0)>>
defp encode_profile("sensor_air_temperature"), do: <<0x31, 0x01>>
defp encode_profile("sensor_humidity"), do: <<0x31, 0x05>>
defp encode_profile("notification_smoke_alarm"), do: <<0x71, 0x01>>
defp encode_profile("notification_co2_alarm"), do: <<0x71, 0x03>>
defp encode_profile("notification_unknown"), do: <<0x71, 0x00>>
defp encode_profile("meter_electric_kwh"), do: <<0x32, 0x01>>
defp encode_profile("meter_gas"), do: <<0x32, 0x02>>
defp encode_profile("meter_water"), do: <<0x32, 0x03>>
defp encode_profile("meter_unknown"), do: <<0x32, 0x00>>
defp encode_profile(<<"irrigation_channel_", key::binary>>),
do: <<0x6B, elem(Integer.parse(key), 0)>>
defp get_list_mode(command) do
if Command.param(command, :list_mode, false) do
0x01
else
groups_info = Command.param!(command, :groups_info)
if length(groups_info) > 1, do: 0x01, else: 0x00
end
end
end