somfy_sdn 1.0.12 → 2.0.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.
@@ -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