somfy_sdn 1.0.12 → 2.1.2

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.
@@ -0,0 +1,197 @@
1
+ module SDN
2
+ module CLI
3
+ class Simulator
4
+ class MockMotor
5
+ attr_accessor :address, :node_type, :label, :ips, :position_pulses, :up_limit, :down_limit, :groups, :network_lock_priority, :lock_priority, :ir_channels
6
+
7
+ def initialize(client)
8
+ @client = client
9
+ self.address = Message.parse_address("00.00.00")
10
+ self.node_type = :st30
11
+ self.label = ""
12
+ self.ips = Array.new(16)
13
+ self.groups = Array.new(16)
14
+ self.ir_channels = 0
15
+ self.lock_priority = 0
16
+ end
17
+
18
+ def process
19
+ loop do
20
+ @client.receive do |message|
21
+ SDN.logger.info "Received #{message.inspect}"
22
+ next unless message.is_a?(Message::ILT2::MasterControl) ||
23
+ message.dest == address ||
24
+ message.dest == BROADCAST_ADDRESS
25
+
26
+ case message
27
+ when Message::GetGroupAddr
28
+ next nack(message) unless (1..16).include?(message.group_index)
29
+ respond(message.src, Message::PostGroupAddr.new(message.group_index, groups[message.group_index - 1]))
30
+ when Message::GetMotorIP
31
+ next nack(message) unless (1..16).include?(message.ip)
32
+ respond(message.src, Message::PostMotorIP.new(message.ip, ips[message.ip - 1], to_percent(ips[message.ip - 1])))
33
+ when Message::GetMotorLimits
34
+ respond(message.src, Message::PostMotorLimits.new(up_limit, down_limit))
35
+ when Message::GetMotorPosition
36
+ respond(message.src, Message::PostMotorPosition.new(
37
+ position_pulses,
38
+ to_percent(position_pulses),
39
+ ips.index(position_pulses)&.+(1)
40
+ ))
41
+ when Message::GetNodeAddr; respond(message.src, Message::PostNodeAddr.new)
42
+ when Message::GetNodeLabel; respond(message.src, Message::PostNodeLabel.new(label))
43
+ when Message::ILT2::GetIRConfig; respond(message.src, Message::ILT2::PostIRConfig.new(ir_channels))
44
+ when Message::ILT2::GetLockStatus; respond(message.src, Message::ILT2::PostLockStatus.new(lock_priority))
45
+ when Message::ILT2::GetMotorIP; respond(message.src, Message::ILT2::PostMotorIP.new(message.ip, ips[message.ip - 1]))
46
+ when Message::ILT2::GetMotorPosition; respond(message.src, Message::ILT2::PostMotorPosition.new(position_pulses, to_percent(position_pulses)))
47
+ when Message::ILT2::GetMotorSettings; respond(message.src, Message::ILT2::PostMotorSettings.new(down_limit))
48
+ when Message::ILT2::SetIRConfig; self.ir_channels = message.channels
49
+ when Message::ILT2::SetLockStatus; self.lock_priority = message.priority
50
+ when Message::ILT2::SetMotorIP
51
+ next nack(message) unless (1..16).include?(message.ip)
52
+ ips[message.ip - 1] = message.value
53
+ ack(message)
54
+ when Message::ILT2::SetMotorPosition
55
+ next nack(message) unless down_limit
56
+
57
+ self.position_pulses = case message.target_type
58
+ when :up_limit; 0
59
+ when :down_limit; down_limit
60
+ when :ip
61
+ next nack(message) unless (1..16).include?(message.target)
62
+ next nack(message) unless ips[message.target]
63
+ ips[message.target]
64
+ when :position_pulses
65
+ next nack(message) if message.target - 1 > down_limit
66
+ message.target - 1
67
+ when :jog_up_pulses; [0, position_pulses - message.target].max
68
+ when :jog_down_pulses; [down_limit, position_pulses + message.target].min
69
+ when :position_percent
70
+ next nack(message) if message.target > 100
71
+ to_pulses(message.target.to_f)
72
+ end
73
+ ack(message)
74
+ when Message::ILT2::SetMotorSettings
75
+ if message.down_limit != 0
76
+ self.down_limit = message.down_limit
77
+ self.position_pulses = message.position_pulses
78
+ end
79
+ when Message::MoveTo
80
+ next nack(message) unless down_limit
81
+ next nack(message) unless %I{up_limit down_limit ip position_pulses position_percent}.include?(message.target_type)
82
+
83
+ self.position_pulses = case message.target_type
84
+ when :up_limit; 0
85
+ when :down_limit; down_limit;
86
+ when :ip
87
+ next nack(message) unless (1..16).include?(message.target)
88
+ next nack(message) unless ips[message.target - 1]
89
+ ips[message.target - 1]
90
+ when :position_pulses
91
+ next nack(message) if message.target > down_limit
92
+ message.target
93
+ when :position_percent
94
+ next nack(message) if message.target > 100
95
+ to_pulses(message.target)
96
+ end
97
+ ack(message)
98
+ when Message::SetGroupAddr
99
+ next nack(message) unless (1..16).include?(message.group_index)
100
+ groups[message.group_index - 1] = message.group_address == [0, 0, 0] ? nil : message.group_address
101
+ ack(message)
102
+ when Message::SetMotorIP
103
+ next nack(message) unless (1..16).include?(message.ip) || message.type == :distribute
104
+
105
+ case message.type
106
+ when :delete
107
+ ips[message.ip - 1] = nil
108
+ ack(message)
109
+ when :current_position
110
+ ips[message.ip - 1] = position_pulses
111
+ ack(message)
112
+ when :position_pulses
113
+ ips[message.ip - 1] = message.value
114
+ ack(message)
115
+ when :position_percent
116
+ pulses = to_pulses(message.value)
117
+ if pulses
118
+ ips[message.ip - 1] = pulses
119
+ ack(message)
120
+ else
121
+ nack(message)
122
+ end
123
+ when :distribute
124
+ next nack(message) unless down_limit
125
+ next nack(message) unless (1..15).include?(message.value)
126
+ span = down_limit / (message.value + 1)
127
+ current = 0
128
+ (0...message.value).each do |ip|
129
+ ips[ip] = (current += span)
130
+ end
131
+ (message.value...16).each do |ip|
132
+ ips[ip] = nil
133
+ end
134
+ ack(message)
135
+ end
136
+ when Message::SetMotorLimits
137
+ next nack(message) unless [:up, :down].include?(message.target)
138
+ next nack(message) unless [:jog_pulses].include?(message.type)
139
+
140
+ self.up_limit ||= 0
141
+ self.down_limit ||= 0
142
+ self.position_pulses ||= 0
143
+
144
+ next nack(message) if message.target == :up && position_pulses != 0
145
+ next nack(message) if message.target == :down && position_pulses != down_limit
146
+
147
+ case message.type
148
+ when :jog_pulses
149
+ self.down_limit += message.value
150
+ self.position_pulses += message.value if message.target == :down
151
+ end
152
+ ack(message)
153
+ when Message::SetNodeLabel; self.label = message.label; ack(message)
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ def to_percent(pulses)
160
+ pulses && down_limit ? 100.0 * pulses / down_limit : nil
161
+ end
162
+
163
+ def to_pulses(percent)
164
+ percent && down_limit ? down_limit * percent / 100 : nil
165
+ end
166
+
167
+ def ack(message)
168
+ return unless message.ack_requested
169
+ respond(Message::Ack.new(message.dest))
170
+ end
171
+
172
+ def nack(message, error_code = nil)
173
+ return unless message.ack_requested
174
+ respond(Message::Nack.new(message.dest))
175
+ end
176
+
177
+ def respond(dest, message)
178
+ message.src = address
179
+ message.node_type = node_type
180
+ message.dest = dest
181
+ SDN.logger.info "Sending #{message.inspect}"
182
+ @client.send(message)
183
+ end
184
+ end
185
+
186
+ def initialize(port, address = nil)
187
+ sdn = Client.new(port)
188
+
189
+ motor = MockMotor.new(sdn)
190
+ motor.address = Message.parse_address(address) if address
191
+ motor.node_type = :lt50
192
+
193
+ motor.process
194
+ end
195
+ end
196
+ end
197
+ end
data/lib/sdn/client.rb ADDED
@@ -0,0 +1,86 @@
1
+ require 'io/wait'
2
+
3
+ module SDN
4
+ class Client
5
+ def initialize(port)
6
+ uri = URI.parse(port)
7
+ @io = if uri.scheme == "tcp"
8
+ require 'socket'
9
+ TCPSocket.new(uri.host, uri.port)
10
+ elsif uri.scheme == "telnet" || uri.scheme == "rfc2217"
11
+ require 'net/telnet/rfc2217'
12
+ Net::Telnet::RFC2217.new(host: uri.host,
13
+ port: uri.port || 23,
14
+ baud: 4800,
15
+ data_bits: 8,
16
+ parity: :odd,
17
+ stop_bits: 1)
18
+ elsif port == "/dev/ptmx"
19
+ require 'pty'
20
+ io, slave = PTY.open
21
+ puts "Slave PTY available at #{slave.path}"
22
+ io
23
+ else
24
+ require 'ccutrer-serialport'
25
+ CCutrer::SerialPort.new(port, baud: 4800, data_bits: 8, parity: :odd, stop_bits: 1)
26
+ end
27
+ @buffer = ""
28
+ end
29
+
30
+ def send(message)
31
+ @io.write(message.serialize)
32
+ end
33
+
34
+ def transact(message)
35
+ message.ack_requested = true
36
+ send(message)
37
+ receive(1)
38
+ end
39
+
40
+ def ensure(message)
41
+ loop do
42
+ messages = transact(message)
43
+ next if messages.empty?
44
+ next unless message.class.expected_response?(messages.first)
45
+ return messages.first
46
+ end
47
+ end
48
+
49
+ WAIT_TIME = 0.25
50
+
51
+ def receive(timeout = nil)
52
+ messages = []
53
+
54
+ loop do
55
+ message, bytes_read = Message.parse(@buffer.bytes)
56
+ # discard how much we read
57
+ @buffer = @buffer[bytes_read..-1] if bytes_read
58
+ unless message
59
+ break unless messages.empty?
60
+
61
+ begin
62
+ block = @io.read_nonblock(64 * 1024)
63
+ SDN.logger.debug "read #{block.unpack("H*").first.gsub(/\h{2}/, "\\0 ")}"
64
+ @buffer.concat(block)
65
+ next
66
+ rescue IO::WaitReadable, EOFError
67
+ wait = @buffer.empty? ? timeout : WAIT_TIME
68
+ if @io.wait_readable(wait).nil?
69
+ # timed out; just discard everything
70
+ SDN.logger.debug "discarding #{@buffer.unpack("H*").first.gsub(/\h{2}/, "\\0 ")} due to timeout"
71
+ @buffer = ""
72
+ end
73
+ end
74
+ next
75
+ end
76
+ if block_given?
77
+ yield message
78
+ else
79
+ messages << message
80
+ end
81
+ end
82
+
83
+ messages
84
+ end
85
+ end
86
+ end
@@ -1,5 +1,46 @@
1
1
  module SDN
2
2
  class Message
3
+ class Lock < Message
4
+ MSG = 0x06
5
+ PARAMS_LENGTH = 5
6
+ TARGET_TYPE = { current: 0, up_limit: 1, down_limit: 2, ip: 4, unlock: 5, position_percent: 7 }
7
+
8
+ attr_reader :target_type, :target, :priority
9
+
10
+ def initialize(dest = nil, target_type = :unlock, target = nil, priority = 1, **kwargs)
11
+ kwargs[:dest] ||= dest
12
+ super(**kwargs)
13
+ self.target_type = target_type
14
+ self.target = target
15
+ self.priority = priority
16
+ end
17
+
18
+ def target_type=(value)
19
+ raise ArgumentError, "target_type must be one of :current, :up_limit, :down_limit, :ip, :unlock, or :position_percent" unless TARGET_TYPE.keys.include?(value)
20
+ @target_type = value
21
+ end
22
+
23
+ def target=(value)
24
+ @target = value&. & 0xffff
25
+ end
26
+
27
+ def priority=(value)
28
+ @priority = value & 0xff
29
+ end
30
+
31
+ def parse(params)
32
+ super
33
+ self.target_type = TARGET_TYPE.invert[to_number(params[0])]
34
+ target = to_number(params[1..2], nillable: true)
35
+ self.target = target
36
+ self.priority = to_number(params[3])
37
+ end
38
+
39
+ def params
40
+ transform_param(TARGET_TYPE[target_type]) + from_number(target, 2) + transform_param(priority) + transform_param(0)
41
+ end
42
+ end
43
+
3
44
  # Move in momentary mode
4
45
  class Move < Message
5
46
  MSG = 0x01
@@ -32,7 +73,7 @@ module SDN
32
73
  end
33
74
 
34
75
  def duration=(value)
35
- raise ArgumentError, "duration must be in range 0x0a to 0xff (#{value})" unless value && value >= 0x0a && value <= 0xff
76
+ raise ArgumentError, "duration must be in range 0x0a to 0xff (#{value})" if value && (value < 0x0a || value > 0xff)
36
77
  @duration = value
37
78
  end
38
79
 
@@ -48,18 +89,43 @@ module SDN
48
89
  end
49
90
  end
50
91
 
51
- # Stop movement
52
- class Stop < Message
53
- MSG = 0x02
54
- PARAMS_LENGTH = 1
92
+ # Move relative to current position
93
+ class MoveOf < Message
94
+ MSG = 0x04
95
+ PARAMS_LENGTH = 4
96
+ TARGET_TYPE = { next_ip: 0x00, previous_ip: 0x01, jog_down_pulses: 0x02, jog_up_pulses: 0x03, jog_down_ms: 0x04, jog_up_ms: 0x05 }
55
97
 
56
- def initialize(dest = nil, **kwargs)
98
+ attr_reader :target_type, :target
99
+
100
+ def initialize(dest = nil, target_type = nil, target = nil, **kwargs)
57
101
  kwargs[:dest] ||= dest
58
102
  super(**kwargs)
103
+ self.target_type = target_type
104
+ self.target = target
105
+ end
106
+
107
+ def parse(params)
108
+ super
109
+ self.target_type = TARGET_TYPE.invert[to_number(params[0])]
110
+ target = to_number(params[1..2], nillable: true)
111
+ target *= 10 if %I{jog_down_ms jog_up_ms}.include?(target_type)
112
+ self.target = target
113
+ end
114
+
115
+ def target_type=(value)
116
+ raise ArgumentError, "target_type must be one of :next_ip, :previous_ip, :jog_down_pulses, :jog_up_pulses, :jog_down_ms, :jog_up_ms" unless value.nil? || TARGET_TYPE.keys.include?(value)
117
+ @target_type = value
118
+ end
119
+
120
+ def target=(value)
121
+ value &= 0xffff if value
122
+ @target = value
59
123
  end
60
124
 
61
125
  def params
62
- transform_param(0)
126
+ param = target || 0xffff
127
+ param /= 10 if %I{jog_down_ms jog_up_ms}.include?(target_type)
128
+ transform_param(TARGET_TYPE[target_type]) + from_number(param, 2) + transform_param(0)
63
129
  end
64
130
  end
65
131
 
@@ -107,43 +173,18 @@ module SDN
107
173
  end
108
174
  end
109
175
 
110
- # Move relative to current position
111
- class MoveOf < Message
112
- MSG = 0x04
113
- PARAMS_LENGTH = 4
114
- TARGET_TYPE = { next_ip: 0x00, previous_ip: 0x01, jog_down_pulses: 0x02, jog_up_pulses: 0x03, jog_down_ms: 0x04, jog_up_ms: 0x05 }
115
-
116
- attr_reader :target_type, :target
176
+ # Stop movement
177
+ class Stop < Message
178
+ MSG = 0x02
179
+ PARAMS_LENGTH = 1
117
180
 
118
- def initialize(dest = nil, target_type = nil, target = nil, **kwargs)
181
+ def initialize(dest = nil, **kwargs)
119
182
  kwargs[:dest] ||= dest
120
183
  super(**kwargs)
121
- self.target_type = target_type
122
- self.target = target
123
- end
124
-
125
- def parse(params)
126
- super
127
- self.target_type = TARGET_TYPE.invert[to_number(params[0])]
128
- target = to_number(params[1..2], nillable: true)
129
- target *= 10 if %I{jog_down_ms jog_up_ms}.include?(target_type)
130
- self.target = target
131
- end
132
-
133
- def target_type=(value)
134
- raise ArgumentError, "target_type must be one of :next_ip, :previous_ip, :jog_down_pulses, :jog_up_pulses, :jog_down_ms, :jog_up_ms" unless value.nil? || TARGET_TYPE.keys.include?(value)
135
- @target_type = value
136
- end
137
-
138
- def target=(value)
139
- value &= 0xffff if value
140
- @target = value
141
184
  end
142
185
 
143
186
  def params
144
- param = target || 0xffff
145
- param /= 10 if %I{jog_down_ms jog_up_ms}.include?(target_type)
146
- transform_param(TARGET_TYPE[target_type]) + from_number(param, 2) + transform_param(0)
187
+ transform_param(0)
147
188
  end
148
189
  end
149
190
 
@@ -1,32 +1,43 @@
1
1
  module SDN
2
2
  class Message
3
- class GetMotorPosition < SimpleRequest
4
- MSG = 0x0c
5
- end
3
+ class GetGroupAddr < Message
4
+ MSG = 0x41
5
+ PARAMS_LENGTH = 1
6
6
 
7
- class GetMotorStatus < SimpleRequest
8
- MSG = 0x0e
9
- end
7
+ attr_reader :group_index
10
8
 
11
- class GetMotorLimits < SimpleRequest
12
- MSG = 0x21
9
+ def initialize(dest = nil, group_index = 1, **kwargs)
10
+ kwargs[:dest] ||= dest
11
+ super(**kwargs)
12
+ self.group_index = group_index
13
+ end
14
+
15
+ def parse(params)
16
+ super
17
+ self.group_index = to_number(params[0]) + 1
18
+ end
19
+
20
+ def group_index=(value)
21
+ raise ArgumentError, "group_index is out of range" unless (1..16).include?(value)
22
+ @group_index = value
23
+ end
24
+
25
+ def params
26
+ transform_param(group_index - 1)
27
+ end
13
28
  end
14
29
 
15
30
  class GetMotorDirection < SimpleRequest
16
31
  MSG = 0x22
17
32
  end
18
33
 
19
- class GetMotorRollingSpeed < SimpleRequest
20
- MSG = 0x23
21
- end
22
-
23
34
  class GetMotorIP < Message
24
35
  MSG = 0x25
25
36
  PARAMS_LENGTH = 1
26
37
 
27
38
  attr_reader :ip
28
39
 
29
- def initialize(dest = nil, ip = nil, **kwargs)
40
+ def initialize(dest = nil, ip = 1, **kwargs)
30
41
  kwargs[:dest] ||= dest
31
42
  super(**kwargs)
32
43
  self.ip = ip
@@ -34,48 +45,37 @@ module SDN
34
45
 
35
46
  def parse(params)
36
47
  super
37
- self.ip = to_number(params[0], nillable: true)
48
+ self.ip = to_number(params[0])
38
49
  end
39
50
 
40
51
  def ip=(value)
41
- raise ArgumentError, "invalid IP #{ip} (should be 1-16)" unless ip.nil? || (1..16).include?(ip)
52
+ raise ArgumentError, "invalid IP #{value} (should be 1-16)" unless (1..16).include?(value)
42
53
  @ip = value
43
54
  end
44
55
 
45
56
  def params
46
- transform_param(@ip || 0xff)
57
+ transform_param(ip)
47
58
  end
48
59
  end
49
60
 
50
- class GetGroupAddr < Message
51
- MSG = 0x41
52
- PARAMS_LENGTH = 1
53
-
54
- attr_reader :group_index
55
-
56
- def initialize(dest = nil, group_index = 0, **kwargs)
57
- kwargs[:dest] ||= dest
58
- super(**kwargs)
59
- self.group_index = group_index
60
- end
61
+ class GetMotorLimits < SimpleRequest
62
+ MSG = 0x21
63
+ end
61
64
 
62
- def parse(params)
63
- super
64
- self.group_index = to_number(params[0])
65
- end
65
+ class GetMotorPosition < SimpleRequest
66
+ MSG = 0x0c
67
+ end
66
68
 
67
- def group_index=(value)
68
- raise ArgumentError, "group_index is out of range" unless (0...16).include?(value)
69
- @group_index = value
70
- end
69
+ class GetMotorRollingSpeed < SimpleRequest
70
+ MSG = 0x23
71
+ end
71
72
 
72
- def params
73
- transform_param(group_index)
74
- end
73
+ class GetMotorStatus < SimpleRequest
74
+ MSG = 0x0e
75
75
  end
76
76
 
77
- class GetNodeLabel < SimpleRequest
78
- MSG = 0x45
77
+ class GetNetworkLock < SimpleRequest
78
+ MSG = 0x26
79
79
  end
80
80
 
81
81
  class GetNodeAddr < Message
@@ -88,6 +88,14 @@ module SDN
88
88
  end
89
89
  end
90
90
 
91
+ class GetNodeAppVersion < SimpleRequest
92
+ MSG = 0x74
93
+ end
94
+
95
+ class GetNodeLabel < SimpleRequest
96
+ MSG = 0x45
97
+ end
98
+
91
99
  class GetNodeSerialNumber < SimpleRequest
92
100
  MSG = 0x4c
93
101
  end
@@ -13,6 +13,34 @@ module SDN
13
13
  addr_bytes[0..1] == [1, 1]
14
14
  end
15
15
 
16
+ def node_type_from_number(number)
17
+ case number
18
+ when 1; :st50ilt2
19
+ when 2; :st30
20
+ when 6; :glydea
21
+ when 7; :st50ac
22
+ when 8; :st50dc
23
+ when 0x70; :lt50
24
+ else; number
25
+ end
26
+ end
27
+
28
+ def node_type_to_number(type)
29
+ case type
30
+ when :st50ilt2; 1
31
+ when :st30; 2
32
+ when :glydea; 6
33
+ when :st50ac; 7
34
+ when :st50dc; 8
35
+ when :lt50; 0x70
36
+ else; type
37
+ end
38
+ end
39
+
40
+ def node_type_to_string(type)
41
+ type.is_a?(Integer) ? "%02xh" % type : type.inspect
42
+ end
43
+
16
44
  def transform_param(param)
17
45
  Array(param).reverse.map { |byte| 0xff - byte }
18
46
  end
@@ -23,7 +51,9 @@ module SDN
23
51
  result
24
52
  end
25
53
 
26
- def from_number(number, bytes)
54
+ def from_number(number, bytes = 1)
55
+ number ||= 1 ** (bytes * 8) - 1
56
+ number = number.to_i
27
57
  bytes.times.inject([]) do |res, _|
28
58
  res << (0xff - number & 0xff)
29
59
  number >>= 8
@@ -33,12 +63,12 @@ module SDN
33
63
 
34
64
  def to_string(param)
35
65
  chars = param.map { |b| 0xff - b }
36
- chars[0..-1].pack("C*").sub(/\0+$/, '')
66
+ chars.pack("C*").sub(/\0+$/, '').strip
37
67
  end
38
68
 
39
69
  def from_string(string, bytes)
40
70
  chars = string.bytes
41
- chars = chars[0...16].fill(0, chars.length, bytes - chars.length)
71
+ chars = chars[0...bytes].fill(' '.ord, chars.length, bytes - chars.length)
42
72
  chars.map { |b| 0xff - b }
43
73
  end
44
74
 
@@ -0,0 +1,48 @@
1
+ module SDN
2
+ class Message
3
+ module ILT2
4
+ class GetIRConfig < SimpleRequest
5
+ MSG = 0x49
6
+ end
7
+
8
+ class GetLockStatus < SimpleRequest
9
+ MSG = 0x4b
10
+ end
11
+
12
+ class GetMotorIP < Message
13
+ MSG = 0x43
14
+ PARAMS_LENGTH = 1
15
+
16
+ attr_reader :ip
17
+
18
+ def initialize(dest = nil, ip = 1, **kwargs)
19
+ kwargs[:dest] ||= dest
20
+ super(**kwargs)
21
+ self.ip = ip
22
+ end
23
+
24
+ def ip=(value)
25
+ raise ArgumentError, "invalid IP #{value} (should be 1-16)" unless (1..16).include?(value)
26
+ @ip = value
27
+ end
28
+
29
+ def parse(params)
30
+ super
31
+ self.ip = to_number(params[0]) + 1
32
+ end
33
+
34
+ def params
35
+ transform_param(@ip - 1)
36
+ end
37
+ end
38
+
39
+ class GetMotorPosition < SimpleRequest
40
+ MSG = 0x44
41
+ end
42
+
43
+ class GetMotorSettings < SimpleRequest
44
+ MSG = 0x42
45
+ end
46
+ end
47
+ end
48
+ end