somfy_sdn 1.0.12 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,234 @@
1
+ require 'curses'
2
+
3
+ module SDN
4
+ module CLI
5
+ class Provisioner
6
+ attr_reader :win, :sdn, :addr, :ns
7
+
8
+ def initialize(port, addr = nil)
9
+ @sdn = Client.new(port)
10
+ @reversed = false
11
+ @pulse_count = 10
12
+
13
+ if addr
14
+ @addr = addr = Message.parse_address(addr)
15
+ else
16
+ puts "Discovering motor..."
17
+ message = sdn.ensure(Message::GetNodeAddr.new)
18
+ puts "Found #{message.node_type}"
19
+ @addr = addr = message.src
20
+ end
21
+
22
+ puts "Preparing to provision motor #{Message.print_address(addr)}"
23
+
24
+ message = sdn.ensure(Message::GetNodeLabel.new(addr))
25
+
26
+ node_type = message.node_type
27
+ @ns = ns = node_type == :st50ilt2 ? Message::ILT2 : Message
28
+
29
+ print "Motor is currently labeled '#{message.label}'; what would you like to change it to (blank to leave alone)? "
30
+ new_label = STDIN.gets
31
+
32
+ unless new_label == "\n"
33
+ new_label.strip!
34
+ sdn.ensure(ns::SetNodeLabel.new(addr, new_label))
35
+ end
36
+
37
+ # make sure some limits exist
38
+ unless ns == Message::ILT2
39
+ limits = sdn.ensure(Message::GetMotorLimits.new(addr))
40
+ if limits.up_limit.nil? || limits.down_limit.nil?
41
+ sdn.ensure(Message::SetMotorLimits.new(addr, :delete, :up))
42
+ sdn.ensure(Message::SetMotorLimits.new(addr, :delete, :down))
43
+ sdn.ensure(Message::SetMotorLimits.new(addr, :current_position, :up))
44
+ sdn.ensure(Message::SetMotorLimits.new(addr, :specified_position, :down, 500))
45
+ end
46
+ end
47
+
48
+ Curses.init_screen
49
+ begin
50
+ Curses.noecho
51
+ Curses.crmode
52
+ Curses.nonl
53
+ Curses.curs_set(0)
54
+ @win = Curses.stdscr
55
+
56
+ process
57
+ rescue => e
58
+ win.setpos(0, 0)
59
+ win.addstr(e.inspect)
60
+ win.addstr("\n")
61
+ win.addstr(e.backtrace.join("\n"))
62
+ win.refresh
63
+ sleep 10
64
+ ensure
65
+ Curses.close_screen
66
+ end
67
+ end
68
+
69
+ def process
70
+ win.keypad = true
71
+ print_help
72
+ refresh
73
+
74
+ loop do
75
+ char = win.getch
76
+ case char
77
+ when 27 # Esc
78
+ stop
79
+ refresh
80
+ when Curses::Key::UP
81
+ if ilt2?
82
+ sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :up_limit))
83
+ else
84
+ sdn.ensure(Message::MoveTo.new(addr, :up_limit))
85
+ end
86
+ wait_for_stop
87
+ when Curses::Key::DOWN
88
+ if ilt2?
89
+ sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :down_limit))
90
+ else
91
+ sdn.ensure(Message::MoveTo.new(addr, :down_limit))
92
+ end
93
+ wait_for_stop
94
+ when Curses::Key::LEFT
95
+ if @pos < @pulse_count
96
+ sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @limit + @pulse_count - @pos, @pulse_count))
97
+ refresh
98
+ end
99
+ sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :jog_up_pulses, @pulse_count))
100
+ wait_for_stop
101
+ when Curses::Key::RIGHT
102
+ if @limit - @pos < @pulse_count
103
+ sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @pos + @pulse_count, @pos))
104
+ refresh
105
+ end
106
+ sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :jog_down_pulses, @pulse_count))
107
+ wait_for_stop
108
+ when 'u'
109
+ if ilt2?
110
+ sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @limit - @pos, 0))
111
+ else
112
+ sdn.ensure(Message::SetMotorLimits.new(addr, :current_position, :up))
113
+ end
114
+ refresh
115
+ when 'l'
116
+ if ilt2?
117
+ sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @pos, @pos))
118
+ else
119
+ sdn.ensure(Message::SetMotorLimits.new(addr, :current_position, :down))
120
+ end
121
+ refresh
122
+ when 'r'
123
+ @reversed = !@reversed
124
+ if ilt2?
125
+ sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @limit, @limit - @pos))
126
+ else
127
+ sdn.ensure(Message::SetMotorDirection.new(addr, @reversed ? :reversed : :standard))
128
+ end
129
+ refresh
130
+ when 'R'
131
+ next unless ilt2?
132
+ @reversed = !@reversed
133
+ sdn.ensure(Message::ILT2::SetMotorSettings.new(addr, reversed_int, @limit, @pos))
134
+ refresh
135
+ when '<'
136
+ @pulse_count /= 2 if @pulse_count > 5
137
+ print_help
138
+ when '>'
139
+ @pulse_count *= 2
140
+ print_help
141
+ when 'q'
142
+ break
143
+ end
144
+ end
145
+ end
146
+
147
+ def print_help
148
+ win.setpos(0, 0)
149
+ win.addstr(<<-INSTRUCTIONS)
150
+ Move the motor. Keys:
151
+ Esc stop movement
152
+ \u2191 go to upper limit
153
+ \u2193 go to lower limit
154
+ \u2190 jog up #{@pulse_count} pulses
155
+ \u2192 jog down #{@pulse_count} pulses
156
+ > increase jog size
157
+ < decrease jog size
158
+ u set upper limit at current position
159
+ l set lower limit at current position
160
+ r reverse motor
161
+ INSTRUCTIONS
162
+
163
+ if ilt2?
164
+ win.addstr("R reverse motor (but leave position alone)\n")
165
+ end
166
+ win.addstr("q quit\n")
167
+ win.refresh
168
+ end
169
+
170
+ def wait_for_stop
171
+ win.setpos(13, 0)
172
+ win.addstr("Moving...\n")
173
+ loop do
174
+ win.nodelay = true
175
+ stop if win.getch == 27 # Esc
176
+ sdn.send(ns::GetMotorPosition.new(addr))
177
+ sdn.receive do |message|
178
+ next unless message.is_a?(ns::PostMotorPosition)
179
+ last_pos = @pos
180
+ @pos = message.position_pulses
181
+ win.setpos(14, 0)
182
+ win.addstr("Position: #{@pos}\n")
183
+
184
+ if last_pos == @pos
185
+ win.setpos(13, 0)
186
+ win.addstr("\n")
187
+ win.nodelay = false
188
+ refresh
189
+ return
190
+ end
191
+ end
192
+ sleep 0.1
193
+
194
+ end
195
+ end
196
+
197
+ def refresh
198
+ pos = sdn.ensure(ns::GetMotorPosition.new(addr))
199
+ @pos = pos.position_pulses
200
+ if ilt2?
201
+ settings = sdn.ensure(Message::ILT2::GetMotorSettings.new(addr))
202
+ @limit = settings.limit
203
+ else
204
+ limits = sdn.ensure(Message::GetMotorLimits.new(addr))
205
+ @limit = limits.down_limit
206
+ direction = sdn.ensure(Message::GetMotorDirection.new(addr))
207
+ @reversed = direction.direction == :reversed
208
+ end
209
+
210
+ win.setpos(14, 0)
211
+ win.addstr("Position: #{@pos}\n")
212
+ win.addstr("Limit: #{@limit}\n")
213
+ win.addstr("Reversed: #{@reversed}\n")
214
+ win.refresh
215
+ end
216
+
217
+ def stop
218
+ if ilt2?
219
+ sdn.ensure(Message::ILT2::SetMotorPosition.new(addr, :stop))
220
+ else
221
+ sdn.ensure(Message::Stop.new(addr))
222
+ end
223
+ end
224
+
225
+ def ilt2?
226
+ ns == Message::ILT2
227
+ end
228
+
229
+ def reversed_int
230
+ @reversed ? 1 : 0
231
+ end
232
+ end
233
+ end
234
+ end
@@ -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,84 @@
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
+ 'parity' => Net::Telnet::RFC2217::ODD)
16
+ elsif port == "/dev/ptmx"
17
+ require 'pty'
18
+ io, slave = PTY.open
19
+ puts "Slave PTY available at #{slave.path}"
20
+ io
21
+ else
22
+ require 'ccutrer-serialport'
23
+ CCutrer::SerialPort.new(port, baud: 4800, data_bits: 8, parity: :odd, stop_bits: 1)
24
+ end
25
+ @buffer = ""
26
+ end
27
+
28
+ def send(message)
29
+ @io.write(message.serialize)
30
+ end
31
+
32
+ def transact(message)
33
+ message.ack_requested = true
34
+ send(message)
35
+ receive(1)
36
+ end
37
+
38
+ def ensure(message)
39
+ loop do
40
+ messages = transact(message)
41
+ next if messages.empty?
42
+ next unless message.class.expected_response?(messages.first)
43
+ return messages.first
44
+ end
45
+ end
46
+
47
+ WAIT_TIME = 0.25
48
+
49
+ def receive(timeout = nil)
50
+ messages = []
51
+
52
+ loop do
53
+ message, bytes_read = Message.parse(@buffer.bytes)
54
+ # discard how much we read
55
+ @buffer = @buffer[bytes_read..-1] if bytes_read
56
+ unless message
57
+ break unless messages.empty?
58
+
59
+ begin
60
+ block = @io.read_nonblock(64 * 1024)
61
+ SDN.logger.debug "read #{block.unpack("H*").first.gsub(/\h{2}/, "\\0 ")}"
62
+ @buffer.concat(block)
63
+ next
64
+ rescue IO::WaitReadable, EOFError
65
+ wait = @buffer.empty? ? timeout : WAIT_TIME
66
+ if @io.wait_readable(wait).nil?
67
+ # timed out; just discard everything
68
+ SDN.logger.debug "discarding #{@buffer.unpack("H*").first.gsub(/\h{2}/, "\\0 ")} due to timeout"
69
+ @buffer = ""
70
+ end
71
+ end
72
+ next
73
+ end
74
+ if block_given?
75
+ yield message
76
+ else
77
+ messages << message
78
+ end
79
+ end
80
+
81
+ messages
82
+ end
83
+ end
84
+ end