-
Notifications
You must be signed in to change notification settings - Fork 9
/
command.ex
146 lines (122 loc) · 3.81 KB
/
command.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
defmodule Grizzly.ZWave.Command do
@moduledoc """
Data struct and behaviour for working with Z-Wave commands
"""
alias Grizzly.ZWave.{CommandClass, DecodeError}
@type delay_seconds() :: non_neg_integer()
@type params() :: Keyword.t()
@typedoc """
Command struct
* `:name` - the name of the command
* `:command_class` - the command class module for the command
* `:command_byte` - the byte representation of the command
* `:params` - the parameters for the command as outlined by the Z-Wave
specification
* `:impl` - the module that implements the Command behaviour
"""
@type t() :: %__MODULE__{
name: atom(),
command_class: CommandClass.t(),
# Allow for the NoOperation command which has no command byte, only a command class byte
command_byte: byte() | nil,
params: params(),
impl: module()
}
@enforce_keys [
:name,
:command_class,
:command_byte,
:impl
]
defstruct name: nil,
command_byte: nil,
command_class: nil,
params: [],
impl: nil
@doc """
Make a new `Command.t()` from the params provided
Param validation should take place here.
"""
@callback new(params :: keyword()) :: {:ok, t()} | {:error, reason :: any()}
@doc """
Encode the command parameters
"""
@callback encode_params(t()) :: binary()
@doc """
Encode the command parameters with encoding options
The encoding options help pass extra context to how the parameters for the
command should be encoded.
This is an optional callback.
"""
@callback encode_params(t(), opts :: keyword()) :: binary()
@doc """
Decode the binary string of command params
"""
@callback decode_params(binary()) :: {:ok, keyword()} | {:error, DecodeError.t()}
@optional_callbacks encode_params: 2
@doc """
Encode the `Command.t()` into it's binary representation
"""
@spec to_binary(t()) :: binary()
def to_binary(command) do
command_class_byte = command.command_class.byte()
if command.command_byte != nil do
params_bin = encode_params(command)
<<command_class_byte, command.command_byte>> <> params_bin
else
# NoOperation command, for example, has no command byte or parameters
<<command_class_byte>>
end
end
@doc """
Get the command param value out the params list
"""
@spec param(t(), atom(), term()) :: term() | nil
def param(command, param, default \\ nil) do
if command do
Keyword.get(command.params, param) || default
else
default
end
end
@spec has_param?(t(), atom()) :: boolean()
def has_param?(command, param) do
Keyword.has_key?(command.params, param)
end
@doc """
Just like `param/3` but will raise if the the param is not in the param list
"""
@spec param!(t(), atom()) :: term() | no_return()
def param!(command, param) do
Keyword.fetch!(command.params, param)
rescue
KeyError ->
raise KeyError,
"""
It looks like you tried to get the #{inspect(param)} from your command.
Here is a list of available params for your command:
""" <> list_of_command_params(command)
end
@doc """
Put the param value into the params list, updating pervious value if there is
one
"""
@spec put_param(t(), atom(), any()) :: t()
def put_param(command, param, new_value) do
new_params = Keyword.put(command.params, param, new_value)
%__MODULE__{command | params: new_params}
end
@spec encode_params(t()) :: binary()
def encode_params(command) do
if command.params == [] do
<<>>
else
command.impl.encode_params(command)
end
end
defp list_of_command_params(command) do
Enum.reduce(command.params, "", fn {param_name, _}, str_list ->
str_list <> " * #{inspect(param_name)}\n"
end)
end
end