Skip to content

Commit dc3e49e

Browse files
committed
Use seconds or :default to give the duration when setting a switch
Whether a binary or multilevel switch, instead of passing the duration pre-encoded. Required for certification [SRH-1797]
1 parent 64421de commit dc3e49e

File tree

8 files changed

+153
-40
lines changed

8 files changed

+153
-40
lines changed
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
defmodule Grizzly.ZWave.CommandClasses.SwitchSupport do
2+
@moduledoc """
3+
Support for Switch Binary and Switch Multilevel Command Classes
4+
"""
5+
6+
alias Grizzly.ZWave.DecodeError
7+
8+
# Duration in seconds, or the manufacturer's default
9+
@type duration :: :default | 0..7620
10+
11+
# Duration encoding
12+
# * 0 -> instantly
13+
# * 1..127 -> seconds
14+
# * 128..254 -> minutes + 127
15+
# * 255 -> factory default (option v2)
16+
@doc """
17+
Encode a duration into a byte according to the Z-Wave specs
18+
"""
19+
@spec duration_to_byte(duration()) :: byte()
20+
def duration_to_byte(:default), do: 255
21+
def duration_to_byte(seconds) when seconds in 0..127, do: seconds
22+
23+
def duration_to_byte(seconds) when seconds in 128..7620 do
24+
minutes = div(seconds, 60)
25+
127 + minutes
26+
end
27+
28+
# Force duration values within supported bounds
29+
def duration_to_byte(seconds) when seconds > 7620, do: 254
30+
def duration_to_byte(seconds) when seconds < 0, do: 0
31+
32+
@doc """
33+
Decode a duration from a byte according to the Z-Wave specs
34+
"""
35+
@spec duration_from_byte(byte()) ::
36+
{:error, DecodeError.t()} | {:ok, duration()}
37+
def duration_from_byte(255), do: {:ok, :default}
38+
def duration_from_byte(byte) when byte in 0..127, do: {:ok, byte}
39+
def duration_from_byte(byte) when byte in 128..254, do: {:ok, (byte - 127) * 60}
40+
41+
def duration_from_byte(byte),
42+
do: {:error, %DecodeError{value: byte, param: :duration, command: :switch_multilevel_report}}
43+
end

lib/grizzly/zwave/commands/switch_binary_report.ex

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,22 @@ defmodule Grizzly.ZWave.Commands.SwitchBinaryReport do
55
Params:
66
77
* `:target_value` - `:on`, `:off`, or `:unknown` (required)
8-
* `:duration` - 0-255 (required V2)
98
* `:current_value` - `:on`, `:off`, or `:unknown` (required V2)
9+
* `:duration` - How long in seconds the switch should take to reach target value or the factory default (:default)
10+
Beyond 127 seconds, the duration is truncated to the minute. E.g. 179s is 2 minutes and 180s is 3 minutes
11+
(required V2)
1012
"""
1113
@behaviour Grizzly.ZWave.Command
1214

1315
alias Grizzly.ZWave.{Command, DecodeError}
14-
alias Grizzly.ZWave.CommandClasses.SwitchBinary
16+
alias Grizzly.ZWave.CommandClasses.{SwitchBinary, SwitchSupport}
1517

1618
@type value() :: :on | :off | :unknown
1719

18-
@type param() :: {:target_value, value()} | {:duration, byte()} | {:current_value, value()}
20+
@type param() ::
21+
{:target_value, value()}
22+
| {:duration, SwitchSupport.duration()}
23+
| {:current_value, value()}
1924

2025
@impl Grizzly.ZWave.Command
2126
def new(opts) do
@@ -41,8 +46,9 @@ defmodule Grizzly.ZWave.Commands.SwitchBinaryReport do
4146

4247
current_value ->
4348
duration = Command.param!(command, :duration)
49+
duration_byte = SwitchSupport.duration_to_byte(duration)
4450
current_value_byte = encode_target_value(current_value)
45-
<<current_value_byte, target_value_byte, duration>>
51+
<<current_value_byte, target_value_byte, duration_byte>>
4652
end
4753
end
4854

@@ -61,9 +67,11 @@ defmodule Grizzly.ZWave.Commands.SwitchBinaryReport do
6167
end
6268
end
6369

64-
def decode_params(<<current_value, target_value, duration>>) do
70+
def decode_params(<<current_value, target_value, duration_byte>>) do
6571
with {:ok, target_value} <- value_from_byte(target_value),
66-
{:ok, current_value} <- value_from_byte(current_value) do
72+
{:ok, duration} <- SwitchSupport.duration_from_byte(duration_byte),
73+
{:ok, current_value} <-
74+
value_from_byte(current_value) do
6775
{:ok,
6876
[
6977
target_value: target_value,

lib/grizzly/zwave/commands/switch_binary_set.ex

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ defmodule Grizzly.ZWave.Commands.SwitchBinarySet do
44
55
Params:
66
7-
* `:target_value` - `:on` or `:off`(required)
8-
* `:duration` - 0-255 (optional v2)
7+
* `:target_value` - `:on` or `:off`(required)
8+
* `:duration` - How long in seconds the switch should take to reach target value or the factory default (:default)
9+
Beyond 127 seconds, the duration is truncated to the minute. E.g. 179s is 2 minutes and 180s is 3 minutes
10+
(optional V2)
911
"""
1012
@behaviour Grizzly.ZWave.Command
1113

1214
alias Grizzly.ZWave.{Command, DecodeError}
13-
alias Grizzly.ZWave.CommandClasses.SwitchBinary
15+
alias Grizzly.ZWave.CommandClasses.{SwitchBinary, SwitchSupport}
1416

15-
@type param :: {:target_value, non_neg_integer()} | {:duration, non_neg_integer()}
17+
@type param :: {:target_value, :on | :off} | {:duration, SwitchSupport.duration()}
1618

1719
@impl Grizzly.ZWave.Command
1820
def new(opts) do
@@ -36,7 +38,8 @@ defmodule Grizzly.ZWave.Commands.SwitchBinarySet do
3638
nil ->
3739
<<target_value_byte>>
3840

39-
duration_byte ->
41+
duration ->
42+
duration_byte = SwitchSupport.duration_to_byte(duration)
4043
<<target_value_byte, duration_byte>>
4144
end
4245
end
@@ -55,11 +58,11 @@ defmodule Grizzly.ZWave.Commands.SwitchBinarySet do
5558
end
5659
end
5760

58-
def decode_params(<<target_value_byte, duration>>) do
59-
case target_value_from_byte(target_value_byte) do
60-
{:ok, target_value} ->
61-
{:ok, [target_value: target_value, duration: duration]}
62-
61+
def decode_params(<<target_value_byte, duration_byte>>) do
62+
with {:ok, target_value} <- target_value_from_byte(target_value_byte),
63+
{:ok, duration} <- SwitchSupport.duration_from_byte(duration_byte) do
64+
{:ok, [target_value: target_value, duration: duration]}
65+
else
6366
{:error, %DecodeError{}} = error ->
6467
error
6568
end

lib/grizzly/zwave/commands/switch_multilevel_report.ex

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,17 @@ defmodule Grizzly.ZWave.Commands.SwitchMultilevelReport do
55
Params:
66
77
* `:value` - `:off`, 0 (off) and 99 (100% on), or `:unknown`
8-
* `:duration` - How long the switch should take to reach target value, 0 -> instantly, 1..127 -> seconds, 128..253 -> minutes, 255 -> unknown (optional v2)
8+
* `:duration` - How long in seconds the switch should take to reach target value or the factory default (:default)
9+
Beyond 127 seconds, the duration is truncated to the minute. E.g. 179s is 2 minutes and 180s is 3 minutes
10+
(optional v2)
911
"""
1012

1113
@behaviour Grizzly.ZWave.Command
1214
require Logger
1315
alias Grizzly.ZWave.{Command, DecodeError}
14-
alias Grizzly.ZWave.CommandClasses.SwitchMultilevel
16+
alias Grizzly.ZWave.CommandClasses.{SwitchMultilevel, SwitchSupport}
1517

16-
@type param :: {:value, 0..99 | :off | :unknown} | {:duration, non_neg_integer()}
18+
@type param :: {:value, 0..99 | :off | :unknown} | {:duration, SwitchSupport.duration()}
1719

1820
@impl Grizzly.ZWave.Command
1921
@spec new([param()]) :: {:ok, Command.t()}
@@ -38,7 +40,8 @@ defmodule Grizzly.ZWave.Commands.SwitchMultilevelReport do
3840
<<value_byte>>
3941

4042
# version 2
41-
duration_byte ->
43+
duration ->
44+
duration_byte = SwitchSupport.duration_to_byte(duration)
4245
<<value_byte, duration_byte>>
4346
end
4447
end
@@ -59,25 +62,26 @@ defmodule Grizzly.ZWave.Commands.SwitchMultilevelReport do
5962
end
6063

6164
# version 2
62-
def decode_params(<<value_byte, duration>>) do
63-
case value_from_byte(value_byte) do
64-
{:ok, value} ->
65-
{:ok, [value: value, duration: duration]}
66-
65+
def decode_params(<<value_byte, duration_byte>>) do
66+
with {:ok, value} <- value_from_byte(value_byte),
67+
{:ok, duration} <- SwitchSupport.duration_from_byte(duration_byte) do
68+
{:ok, [value: value, duration: duration]}
69+
else
6770
{:error, %DecodeError{}} = error ->
6871
error
6972
end
7073
end
7174

7275
# version 4
73-
def decode_params(<<value_byte, target_value_byte, duration, rest::binary>>) do
76+
def decode_params(<<value_byte, target_value_byte, duration_byte, rest::binary>>) do
7477
if byte_size(rest) > 0 do
7578
Logger.warning(
7679
"[Grizzly] Unexpected trailing bytes in SwitchMultilevelReport: #{inspect(rest)}"
7780
)
7881
end
7982

8083
with {:ok, value} <- value_from_byte(value_byte),
84+
{:ok, duration} <- SwitchSupport.duration_from_byte(duration_byte),
8185
{:ok, target_value} <- value_from_byte(target_value_byte) do
8286
{:ok,
8387
[

lib/grizzly/zwave/commands/switch_multilevel_set.ex

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,17 @@ defmodule Grizzly.ZWave.Commands.SwitchMultilevelSet do
55
Params:
66
77
* `:target_value` - '`:off`, `:previous` or a value between 0 and 99
8-
* `:duration` - How long the switch should take to reach target value,
9-
* 0 -> instantly
10-
* 1..127 -> seconds
11-
* 128..254 -> minutes + 127
12-
* 255 -> factory default (option v2)
13-
8+
* `:duration` - How long in seconds the switch should take to reach target value or the factory default (:default)
9+
Beyond 127 seconds, the duration is truncated to the minute. E.g. 179s is 2 minutes and 180s is 3 minutes
10+
(optional v2)
1411
"""
1512

1613
@behaviour Grizzly.ZWave.Command
1714

1815
alias Grizzly.ZWave.{Command, DecodeError}
19-
alias Grizzly.ZWave.CommandClasses.SwitchMultilevel
16+
alias Grizzly.ZWave.CommandClasses.{SwitchMultilevel, SwitchSupport}
2017

21-
@type param :: {:target_value, :off | :previous | 0..99} | {:duration, 0..255}
18+
@type param :: {:target_value, :off | :previous | 0..99} | {:duration, SwitchSupport.duration()}
2219

2320
@impl Grizzly.ZWave.Command
2421
@spec new([param()]) :: {:ok, Command.t()}
@@ -43,7 +40,8 @@ defmodule Grizzly.ZWave.Commands.SwitchMultilevelSet do
4340
nil ->
4441
<<target_value_byte>>
4542

46-
duration_byte ->
43+
duration ->
44+
duration_byte = SwitchSupport.duration_to_byte(duration)
4745
<<target_value_byte, duration_byte>>
4846
end
4947
end
@@ -66,11 +64,11 @@ defmodule Grizzly.ZWave.Commands.SwitchMultilevelSet do
6664
end
6765
end
6866

69-
def decode_params(<<target_value_byte, duration>>) do
70-
case target_value_from_byte(target_value_byte) do
71-
{:ok, target_value} ->
72-
{:ok, [target_value: target_value, duration: duration]}
73-
67+
def decode_params(<<target_value_byte, duration_byte>>) do
68+
with {:ok, target_value} <- target_value_from_byte(target_value_byte),
69+
{:ok, duration} <- SwitchSupport.duration_from_byte(duration_byte) do
70+
{:ok, [target_value: target_value, duration: duration]}
71+
else
7472
{:error, %DecodeError{}} = error ->
7573
error
7674
end

test/grizzly/zwave/commands/switch_binary_report_test.exs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,23 @@ defmodule Grizzly.ZWave.Commands.SwitchBinaryReportTest do
2323
assert <<0x25, 0x03, 0xFE>> == ZWave.to_binary(report)
2424
end
2525

26+
test "encodes duration param correctly" do
27+
params = [current_value: :on, target_value: :off, duration: 10]
28+
{:ok, command} = SwitchBinaryReport.new(params)
29+
expected_binary = <<0xFF, 0x00, 0x0A>>
30+
assert expected_binary == SwitchBinaryReport.encode_params(command)
31+
32+
params = [current_value: :on, target_value: :off, duration: :default]
33+
{:ok, command} = SwitchBinaryReport.new(params)
34+
expected_binary = <<0xFF, 0x00, 0xFF>>
35+
assert expected_binary == SwitchBinaryReport.encode_params(command)
36+
37+
params = [current_value: :on, target_value: :off, duration: 180]
38+
{:ok, command} = SwitchBinaryReport.new(params)
39+
expected_binary = <<0xFF, 0x00, 0x82>>
40+
assert expected_binary == SwitchBinaryReport.encode_params(command)
41+
end
42+
2643
test "decodes correctly on" do
2744
binary = <<0x25, 0x03, 0xFF>>
2845
expected_report = SwitchBinaryReport.new(target_value: :on)

test/grizzly/zwave/commands/switch_multilevel_report_test.exs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ defmodule Grizzly.ZWave.Commands.SwitchMultilevelReportTest do
2020
{:ok, command} = SwitchMultilevelReport.new(params)
2121
expected_binary = <<0x63, 0x0A>>
2222
assert expected_binary == SwitchMultilevelReport.encode_params(command)
23+
24+
params = [value: 99, duration: :default]
25+
{:ok, command} = SwitchMultilevelReport.new(params)
26+
expected_binary = <<0x63, 0xFF>>
27+
assert expected_binary == SwitchMultilevelReport.encode_params(command)
28+
29+
params = [value: 99, duration: 180]
30+
{:ok, command} = SwitchMultilevelReport.new(params)
31+
expected_binary = <<0x63, 0x82>>
32+
assert expected_binary == SwitchMultilevelReport.encode_params(command)
2333
end
2434

2535
test "decodes v1 params correctly" do
@@ -33,6 +43,16 @@ defmodule Grizzly.ZWave.Commands.SwitchMultilevelReportTest do
3343
{:ok, params} = SwitchMultilevelReport.decode_params(binary_params)
3444
assert Keyword.get(params, :value) == 0x32
3545
assert Keyword.get(params, :duration) == 0x0A
46+
47+
binary_params = <<0x32, 0xFF>>
48+
{:ok, params} = SwitchMultilevelReport.decode_params(binary_params)
49+
assert Keyword.get(params, :value) == 0x32
50+
assert Keyword.get(params, :duration) == :default
51+
52+
binary_params = <<0x32, 0x81>>
53+
{:ok, params} = SwitchMultilevelReport.decode_params(binary_params)
54+
assert Keyword.get(params, :value) == 0x32
55+
assert Keyword.get(params, :duration) == 120
3656
end
3757

3858
test "decodes v4 params correctly" do

test/grizzly/zwave/commands/switch_multilevel_set_test.exs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,16 @@ defmodule Grizzly.ZWave.Commands.SwitchMultilevelSetTest do
2020
{:ok, command} = SwitchMultilevelSet.new(params)
2121
expected_binary = <<0x63, 0x0A>>
2222
assert expected_binary == SwitchMultilevelSet.encode_params(command)
23+
24+
params = [target_value: 99, duration: :default]
25+
{:ok, command} = SwitchMultilevelSet.new(params)
26+
expected_binary = <<0x63, 0xFF>>
27+
assert expected_binary == SwitchMultilevelSet.encode_params(command)
28+
29+
params = [target_value: 99, duration: 180]
30+
{:ok, command} = SwitchMultilevelSet.new(params)
31+
expected_binary = <<0x63, 0x82>>
32+
assert expected_binary == SwitchMultilevelSet.encode_params(command)
2333
end
2434

2535
test "encodes v2 params correctly - accept level 100" do
@@ -40,5 +50,15 @@ defmodule Grizzly.ZWave.Commands.SwitchMultilevelSetTest do
4050
{:ok, params} = SwitchMultilevelSet.decode_params(binary_params)
4151
assert Keyword.get(params, :target_value) == 0x32
4252
assert Keyword.get(params, :duration) == 0x0A
53+
54+
binary_params = <<0x32, 0xFF>>
55+
{:ok, params} = SwitchMultilevelSet.decode_params(binary_params)
56+
assert Keyword.get(params, :target_value) == 0x32
57+
assert Keyword.get(params, :duration) == :default
58+
59+
binary_params = <<0x32, 0x81>>
60+
{:ok, params} = SwitchMultilevelSet.decode_params(binary_params)
61+
assert Keyword.get(params, :target_value) == 0x32
62+
assert Keyword.get(params, :duration) == 120
4363
end
4464
end

0 commit comments

Comments
 (0)