somfy_sdn 2.4.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,18 +2,32 @@
2
2
 
3
3
  module SDN
4
4
  class Message
5
+ # Request for a motor's configured group address at a given slot.
5
6
  class GetGroupAddr < Message
7
+ # (see Ack::MSG)
6
8
  MSG = 0x41
9
+ # (see Ack::PARAMS_LENGTH)
7
10
  PARAMS_LENGTH = 1
8
11
 
12
+ # @!attribute [rw] group_index
13
+ #
14
+ # Group index in the range 1..16
15
+ #
16
+ # @return [Integer]
9
17
  attr_reader :group_index
10
18
 
19
+ # Creates a request for a specific group slot.
20
+ #
21
+ # @param dest [(Integer, Integer, Integer), nil] destination address
22
+ # @param group_index [Integer] group slot to query
23
+ # @param kwargs [Hash] additional message options
11
24
  def initialize(dest = nil, group_index = 1, **kwargs)
12
25
  kwargs[:dest] ||= dest
13
26
  super(**kwargs)
14
27
  self.group_index = group_index
15
28
  end
16
29
 
30
+ # (see Message#parse)
17
31
  def parse(params)
18
32
  super
19
33
  self.group_index = to_number(params[0]) + 1
@@ -25,27 +39,44 @@ module SDN
25
39
  @group_index = value
26
40
  end
27
41
 
42
+ # (see Message#params)
28
43
  def params
29
44
  transform_param(group_index - 1)
30
45
  end
31
46
  end
32
47
 
48
+ # Request for a motor's rotation direction setting.
33
49
  class GetMotorDirection < SimpleRequest
50
+ # (see Ack::MSG)
34
51
  MSG = 0x22
35
52
  end
36
53
 
54
+ # Request for a motor's stored intermediate position at a given slot.
37
55
  class GetMotorIP < Message
56
+ # (see Ack::MSG)
38
57
  MSG = 0x25
58
+ # (see Ack::PARAMS_LENGTH)
39
59
  PARAMS_LENGTH = 1
40
60
 
61
+ # @!attribute [rw] ip
62
+ #
63
+ # Intermediate position slot being queried.
64
+ #
65
+ # @return [Integer]
41
66
  attr_reader :ip
42
67
 
68
+ # Creates a request for a specific intermediate position slot.
69
+ #
70
+ # @param dest [(Integer, Integer, Integer), nil] destination address
71
+ # @param ip [Integer] intermediate position slot
72
+ # @param kwargs [Hash] additional message options
43
73
  def initialize(dest = nil, ip = 1, **kwargs)
44
74
  kwargs[:dest] ||= dest
45
75
  super(**kwargs)
46
76
  self.ip = ip
47
77
  end
48
78
 
79
+ # (see Message#parse)
49
80
  def parse(params)
50
81
  super
51
82
  self.ip = to_number(params[0])
@@ -57,54 +88,80 @@ module SDN
57
88
  @ip = value
58
89
  end
59
90
 
91
+ # (see Message#params)
60
92
  def params
61
93
  transform_param(ip)
62
94
  end
63
95
  end
64
96
 
97
+ # Request for a motor's configured travel limits.
65
98
  class GetMotorLimits < SimpleRequest
99
+ # (see Ack::MSG)
66
100
  MSG = 0x21
67
101
  end
68
102
 
103
+ # Request for a motor's current position.
69
104
  class GetMotorPosition < SimpleRequest
105
+ # (see Ack::MSG)
70
106
  MSG = 0x0c
71
107
  end
72
108
 
109
+ # Request for a motor's rolling speed configuration.
73
110
  class GetMotorRollingSpeed < SimpleRequest
111
+ # (see Ack::MSG)
74
112
  MSG = 0x23
75
113
  end
76
114
 
115
+ # Request for a motor's current run state and last action metadata.
77
116
  class GetMotorStatus < SimpleRequest
117
+ # (see Ack::MSG)
78
118
  MSG = 0x0e
79
119
  end
80
120
 
121
+ # Request for the current network lock state.
81
122
  class GetNetworkLock < SimpleRequest
123
+ # (see Ack::MSG)
82
124
  MSG = 0x26
83
125
  end
84
126
 
127
+ # Discovery request for a node address, usually sent as a broadcast.
85
128
  class GetNodeAddr < Message
129
+ # (see Ack::MSG)
86
130
  MSG = 0x40
131
+ # (see Ack::PARAMS_LENGTH)
87
132
  PARAMS_LENGTH = 0
88
133
 
134
+ # Creates a node discovery request, defaulting to broadcast.
135
+ #
136
+ # @param dest [(Integer, Integer, Integer), nil] destination address
137
+ # @param kwargs [Hash] additional message options
89
138
  def initialize(dest = [0xff, 0xff, 0xff], **kwargs)
90
139
  kwargs[:dest] ||= dest
91
140
  super(**kwargs)
92
141
  end
93
142
  end
94
143
 
144
+ # Request for a node's application version.
95
145
  class GetNodeAppVersion < SimpleRequest
146
+ # (see Ack::MSG)
96
147
  MSG = 0x74
97
148
  end
98
149
 
150
+ # Request for a node's configured label.
99
151
  class GetNodeLabel < SimpleRequest
152
+ # (see Ack::MSG)
100
153
  MSG = 0x45
101
154
  end
102
155
 
156
+ # Request for a node's serial number.
103
157
  class GetNodeSerialNumber < SimpleRequest
158
+ # (see Ack::MSG)
104
159
  MSG = 0x4c
105
160
  end
106
161
 
162
+ # Request for a node's protocol stack version.
107
163
  class GetNodeStackVersion < SimpleRequest
164
+ # (see Ack::MSG)
108
165
  MSG = 0x70
109
166
  end
110
167
  end
@@ -2,19 +2,40 @@
2
2
 
3
3
  module SDN
4
4
  class Message
5
+ # Shared conversions for SDN addresses, encoded parameters, strings, and checksums.
5
6
  module Helpers
7
+ # Converts a printable SDN address into its byte representation.
8
+ #
9
+ # @param addr_string [String] address such as `01.02.03`
10
+ #
11
+ # @return [(Integer, Integer, Integer)] three-byte address
6
12
  def parse_address(addr_string)
7
13
  addr_string.match(/^(\h{2})[:.]?(\h{2})[:.]?(\h{2})$/).captures.map { |byte| byte.to_i(16) }
8
14
  end
9
15
 
16
+ # Formats an address byte array using dotted hexadecimal notation.
17
+ #
18
+ # @param addr_bytes [(Integer, Integer, Integer)] address bytes
19
+ #
20
+ # @return [String]
10
21
  def print_address(addr_bytes)
11
22
  format("%02X.%02X.%02X", *addr_bytes)
12
23
  end
13
24
 
25
+ # Checks whether an address is a group address.
26
+ #
27
+ # @param addr_bytes [(Integer, Integer, Integer)] address bytes
28
+ #
29
+ # @return [true, false]
14
30
  def group_address?(addr_bytes)
15
31
  addr_bytes[0..1] == [1, 1]
16
32
  end
17
33
 
34
+ # Maps a numeric node type identifier to a symbolic name when known.
35
+ #
36
+ # @param number [Integer] protocol node type identifier
37
+ #
38
+ # @return [Symbol, Integer]
18
39
  def node_type_from_number(number)
19
40
  case number
20
41
  when 1 then :st50ilt2
@@ -27,6 +48,11 @@ module SDN
27
48
  end
28
49
  end
29
50
 
51
+ # Maps a symbolic node type name to its numeric protocol identifier.
52
+ #
53
+ # @param type [Symbol, Integer] node type
54
+ #
55
+ # @return [Integer]
30
56
  def node_type_to_number(type)
31
57
  case type
32
58
  when :st50ilt2 then 1
@@ -39,20 +65,42 @@ module SDN
39
65
  end
40
66
  end
41
67
 
68
+ # Formats a node type for inspection output.
69
+ #
70
+ # @param type [Symbol, Integer] node type
71
+ #
72
+ # @return [String]
42
73
  def node_type_to_string(type)
43
74
  type.is_a?(Integer) ? format("%02xh", type) : type.inspect
44
75
  end
45
76
 
77
+ # Applies the SDN byte inversion and endianness transformation to a parameter value.
78
+ #
79
+ # @param param [Integer, <Integer>] parameter value
80
+ #
81
+ # @return [<Integer>]
46
82
  def transform_param(param)
47
83
  Array(param).reverse.map { |byte| 0xff - byte }
48
84
  end
49
85
 
86
+ # Converts transformed SDN bytes into an integer.
87
+ #
88
+ # @param param [Integer, <Integer>] transformed bytes
89
+ # @param nillable [true, false] whether all-`0xff` values should become `nil`
90
+ #
91
+ # @return [Integer, nil]
50
92
  def to_number(param, nillable: false)
51
93
  result = Array(param).reverse.inject(0) { |sum, byte| (sum << 8) + 0xff - byte }
52
94
  result = nil if nillable && result == (1 << (8 * Array(param).length)) - 1
53
95
  result
54
96
  end
55
97
 
98
+ # Converts an integer into SDN-transformed bytes.
99
+ #
100
+ # @param number [Integer, nil] numeric value
101
+ # @param bytes [Integer] number of bytes to emit
102
+ #
103
+ # @return [<Integer>]
56
104
  def from_number(number, bytes = 1)
57
105
  number ||= (1**(bytes * 8)) - 1
58
106
  number = number.to_i
@@ -62,17 +110,33 @@ module SDN
62
110
  end
63
111
  end
64
112
 
113
+ # Decodes transformed SDN bytes into a trimmed string.
114
+ #
115
+ # @param param [<Integer>] transformed string bytes
116
+ #
117
+ # @return [String]
65
118
  def to_string(param)
66
119
  chars = param.map { |b| 0xff - b }
67
120
  chars.pack("C*").sub(/\0+$/, "").strip
68
121
  end
69
122
 
123
+ # Encodes a string into a fixed-width SDN byte array.
124
+ #
125
+ # @param string [String]
126
+ # @param bytes [Integer] output width
127
+ #
128
+ # @return [<Integer>]
70
129
  def from_string(string, bytes)
71
130
  chars = string.bytes
72
131
  chars = chars[0...bytes].fill(" ".ord, chars.length, bytes - chars.length)
73
132
  chars.map { |b| 0xff - b }
74
133
  end
75
134
 
135
+ # Computes the SDN checksum bytes for the provided payload.
136
+ #
137
+ # @param bytes [<Integer>]
138
+ #
139
+ # @return [(Integer, Integer)] two-byte checksum
76
140
  def checksum(bytes)
77
141
  result = bytes.sum
78
142
  [result >> 8, result & 0xff]
@@ -2,21 +2,39 @@
2
2
 
3
3
  module SDN
4
4
  class Message
5
+ # ILT2-specific message namespace.
5
6
  module ILT2
7
+ # Request for the configured IR channel bitmask.
6
8
  class GetIRConfig < SimpleRequest
9
+ # (see Ack::MSG)
7
10
  MSG = 0x49
8
11
  end
9
12
 
13
+ # Request for the current ILT2 lock status.
10
14
  class GetLockStatus < SimpleRequest
15
+ # (see Ack::MSG)
11
16
  MSG = 0x4b
12
17
  end
13
18
 
19
+ # Request for an ILT2 intermediate position at a given slot.
14
20
  class GetMotorIP < Message
21
+ # (see Ack::MSG)
15
22
  MSG = 0x43
23
+ # (see Ack::PARAMS_LENGTH)
16
24
  PARAMS_LENGTH = 1
17
25
 
26
+ # @!attribute [rw] ip
27
+ #
28
+ # Intermediate position slot being queried in the range 1..16.
29
+ #
30
+ # @return [Integer]
18
31
  attr_reader :ip
19
32
 
33
+ # Creates an ILT2 request for a specific intermediate position slot.
34
+ #
35
+ # @param dest [(Integer, Integer, Integer), nil] destination address
36
+ # @param ip [Integer] intermediate position slot
37
+ # @param kwargs [Hash] additional message options
20
38
  def initialize(dest = nil, ip = 1, **kwargs)
21
39
  kwargs[:dest] ||= dest
22
40
  super(**kwargs)
@@ -29,21 +47,27 @@ module SDN
29
47
  @ip = value
30
48
  end
31
49
 
50
+ # (see Message#parse)
32
51
  def parse(params)
33
52
  super
34
53
  self.ip = to_number(params[0]) + 1
35
54
  end
36
55
 
56
+ # (see Message#params)
37
57
  def params
38
58
  transform_param(@ip - 1)
39
59
  end
40
60
  end
41
61
 
62
+ # Request for an ILT2 motor's current position.
42
63
  class GetMotorPosition < SimpleRequest
64
+ # (see Ack::MSG)
43
65
  MSG = 0x44
44
66
  end
45
67
 
68
+ # Request for ILT2 motor settings such as the configured limit.
46
69
  class GetMotorSettings < SimpleRequest
70
+ # (see Ack::MSG)
47
71
  MSG = 0x42
48
72
  end
49
73
  end
@@ -3,10 +3,17 @@
3
3
  module SDN
4
4
  class Message
5
5
  module ILT2
6
+ # Special five-byte ILT2 master-control frame parser.
6
7
  class MasterControl
7
8
  class << self
8
9
  include Helpers
9
10
 
11
+ # Parses a five-byte ILT2 master control frame when present.
12
+ #
13
+ # @param data [<Integer>] buffered input bytes
14
+ #
15
+ # @return [(SDN::Message::ILT2::MasterControl, Integer), nil]
16
+ # (see Message#parse)
10
17
  def parse(data)
11
18
  return unless data.length >= 5
12
19
  return unless checksum(data[0..2]) == data[3..4]
@@ -24,12 +31,15 @@ module SDN
24
31
  end
25
32
  end
26
33
 
34
+ # Master-control frame representing a down command.
27
35
  class Down < MasterControl
28
36
  end
29
37
 
38
+ # Master-control frame representing a stop command.
30
39
  class Stop < MasterControl
31
40
  end
32
41
 
42
+ # Master-control frame representing an up command.
33
43
  class Up < MasterControl
34
44
  end
35
45
  end
@@ -3,14 +3,25 @@
3
3
  module SDN
4
4
  class Message
5
5
  module ILT2
6
+ # Response carrying the configured ILT2 IR channel bitmask.
6
7
  class PostIRConfig < Message
8
+ # (see Ack::MSG)
7
9
  MSG = 0x69
10
+ # (see Ack::PARAMS_LENGTH)
8
11
  PARAMS_LENGTH = 1
9
12
 
13
+ # @!attribute [rw] channels
14
+ #
15
+ # IR channel bitmask.
16
+ #
17
+ # @return [Integer, nil]
10
18
  attr_reader :channels
11
19
 
12
- def initialize(channels = nil, **kwargs)
13
- super(**kwargs)
20
+ # Creates an ILT2 IR configuration response.
21
+ #
22
+ # @param channels [Integer, nil] bitmask of enabled IR channels
23
+ def initialize(channels = nil, **)
24
+ super(**)
14
25
  self.channels = channels
15
26
  end
16
27
 
@@ -18,110 +29,165 @@ module SDN
18
29
  @channels = value&.& 0xff
19
30
  end
20
31
 
32
+ # (see Message#parse)
21
33
  def parse(params)
22
34
  super
23
35
  self.channels = to_number(params[0])
24
36
  end
25
37
 
38
+ # (see Message#params)
26
39
  def params
27
40
  transform_param(channels)
28
41
  end
29
42
 
43
+ # (see Message#class_inspect)
30
44
  def class_inspect
31
45
  ", @channels=#{channels.chr.unpack1("b8")}"
32
46
  end
33
47
  end
34
48
 
49
+ # Response carrying the current ILT2 lock priority.
35
50
  class PostLockStatus < Message
51
+ # (see Ack::MSG)
36
52
  MSG = 0x6B
53
+ # (see Ack::PARAMS_LENGTH)
37
54
  PARAMS_LENGTH = 1
38
55
 
39
- attr_accessor :priority # 0 for not locked
56
+ # Reported lock priority, or `0` when unlocked.
57
+ #
58
+ # @return [Integer, nil]
59
+ attr_accessor :priority
40
60
 
41
- def initialize(priority = nil, **kwargs)
42
- super(**kwargs)
61
+ # Creates an ILT2 lock status response.
62
+ #
63
+ # @param priority [Integer, nil] current lock priority
64
+ def initialize(priority = nil, **)
65
+ super(**)
43
66
  self.priority = priority
44
67
  end
45
68
 
69
+ # (see Message#parse)
46
70
  def parse(params)
47
71
  super
48
72
  self.current_lock_priority = to_number(params[0])
49
73
  end
50
74
 
75
+ # (see Message#params)
51
76
  def params
52
77
  transform_param(priority)
53
78
  end
54
79
  end
55
80
 
81
+ # Response carrying a stored ILT2 intermediate position.
56
82
  class PostMotorIP < Message
83
+ # (see Ack::MSG)
57
84
  MSG = 0x63
85
+ # (see Ack::PARAMS_LENGTH)
58
86
  PARAMS_LENGTH = 3
59
87
 
60
- attr_accessor :ip, :position_pulses
88
+ # Reported intermediate position slot in the range 1..16.
89
+ #
90
+ # @return [Integer, nil]
91
+ attr_accessor :ip
61
92
 
62
- def initialize(ip = nil, position_pulses = nil, **kwargs)
63
- super(**kwargs)
93
+ # @return [Integer, nil]
94
+ attr_accessor :position_pulses
95
+
96
+ # Creates an ILT2 motor IP response.
97
+ #
98
+ # @param ip [Integer, nil] intermediate position slot
99
+ # @param position_pulses [Integer, nil] stored pulse value
100
+ def initialize(ip = nil, position_pulses = nil, **)
101
+ super(**)
64
102
  self.ip = ip
65
103
  self.position_pulses = position_pulses
66
104
  end
67
105
 
106
+ # (see Message#parse)
68
107
  def parse(params)
69
108
  super
70
109
  self.ip = to_number(params[0]) + 1
71
110
  self.position_pulses = to_number(params[1..2], nillable: true)
72
111
  end
73
112
 
113
+ # (see Message#params)
74
114
  def params
75
115
  transform_param(ip - 1) + from_number(position_pulses, 2)
76
116
  end
77
117
  end
78
118
 
119
+ # Response carrying an ILT2 motor's current position.
79
120
  class PostMotorPosition < Message
121
+ # (see Ack::MSG)
80
122
  MSG = 0x64
123
+ # (see Ack::PARAMS_LENGTH)
81
124
  PARAMS_LENGTH = 3
82
125
 
83
- attr_accessor :position_pulses, :position_percent
126
+ # @return [Integer, nil]
127
+ attr_accessor :position_pulses
128
+
129
+ # @return [Numeric, nil]
130
+ attr_accessor :position_percent
84
131
 
85
- def initialize(position_pulses = nil, position_percent = nil, **kwargs)
86
- super(**kwargs)
132
+ # Creates an ILT2 motor position response.
133
+ #
134
+ # @param position_pulses [Integer, nil] current position in pulses
135
+ # @param position_percent [Numeric, nil] current position in percent
136
+ def initialize(position_pulses = nil, position_percent = nil, **)
137
+ super(**)
87
138
  self.position_pulses = position_pulses
88
139
  self.position_percent = position_percent
89
140
  end
90
141
 
142
+ # (see Message#parse)
91
143
  def parse(params)
92
144
  super
93
145
  self.position_pulses = to_number(params[0..1])
94
146
  self.position_percent = to_number(params[2]).to_f / 255 * 100
95
147
  end
96
148
 
149
+ # (see Message#params)
97
150
  def params
98
151
  from_number(position_pulses, 2) +
99
152
  from_number(position_percent && (position_percent * 255 / 100))
100
153
  end
101
154
  end
102
155
 
156
+ # Response carrying ILT2 motor settings in a partially decoded form.
103
157
  class PostMotorSettings < UnknownMessage
158
+ # (see Ack::MSG)
104
159
  MSG = 0x62
160
+ # (see Ack::PARAMS_LENGTH)
105
161
  PARAMS_LENGTH = 3
106
162
 
163
+ # Reported configured lower limit.
164
+ #
165
+ # @return [Integer, nil]
107
166
  attr_accessor :limit
108
167
 
109
- def initialize(limit = nil, **kwargs)
110
- super(**kwargs)
168
+ # Creates an ILT2 motor settings response.
169
+ #
170
+ # @param limit [Integer, nil] configured lower limit
171
+ def initialize(limit = nil, **)
172
+ super(**)
111
173
  self.limit = limit
112
174
  end
113
175
 
176
+ # (see Message#parse)
114
177
  def parse(params)
115
178
  super
116
179
  self.limit = to_number(params[1..2])
117
180
  end
118
181
 
182
+ # (see Message#params)
119
183
  def params
120
184
  transform_param(0) + from_number(limit, 2)
121
185
  end
122
186
  end
123
187
 
188
+ # ILT2 node-label response variant with a larger maximum payload.
124
189
  class PostNodeLabel < ::SDN::Message::PostNodeLabel
190
+ # Maximum label length, in bytes
125
191
  MAX_LENGTH = 32
126
192
  end
127
193
  end