somfy_sdn 1.0.11 → 2.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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