ladder_drive 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -34,10 +34,17 @@ module LadderDrive
34
34
  def initialize source, endian = nil
35
35
  @endian = endian || BIG_ENDIAN
36
36
  @lines = []
37
+ line_no = 1
37
38
  address = 0
38
39
  source.each_line do |line|
39
- @lines << AsmLine.new(line, address, @endian)
40
- address = @lines.last.next_address
40
+ begin
41
+ @lines << AsmLine.new(line, address, @endian)
42
+ address = @lines.last.next_address
43
+ line_no += 1
44
+ rescue SyntaxError => e
45
+ puts "#{e.class}: line:#{line_no}; #{line.chomp}; #{e.to_s} "
46
+ throw
47
+ end
41
48
  end
42
49
  end
43
50
 
@@ -113,7 +120,7 @@ module LadderDrive
113
120
  @codes << encode_mnemonic(mnemonic)
114
121
  case operand_type(mnemonic)
115
122
  when OPERAND_TYPE_TYPE_AND_NUMBER
116
- @codes += parse_type_and_number(operand1)
123
+ @codes += parse_type_and_number(mnemonic, operand1, operand2)
117
124
  end
118
125
  end
119
126
  end
@@ -166,7 +173,7 @@ EOB
166
173
  mnemonic_dict[mnemonic]
167
174
  end
168
175
 
169
- def parse_type_and_number operand
176
+ def parse_type_and_number mnemonic, operand, value
170
177
  /([[:alpha:]]*)(\d+[0-9A-Fa-f]*)\.?(\d*)?/ =~ operand
171
178
  suffix = $1
172
179
  number = $2
@@ -189,15 +196,63 @@ EOB
189
196
  end
190
197
 
191
198
  if number < 256
192
- [type_code, number]
199
+ codes = [type_code, number]
193
200
  else
194
201
  case endian
195
202
  when Asm::LITTLE_ENDIAN
196
- [type_code | 0x10, number & 0xff, (number & 0xff00) >> 8]
203
+ codes = [type_code | 0x10, number & 0xff, (number & 0xff00) >> 8]
197
204
  when Asm::BIG_ENDIAN
198
- [type_code | 0x10, (number & 0xff00) >> 8, number & 0xff]
205
+ codes = [type_code | 0x10, (number & 0xff00) >> 8, number & 0xff]
206
+ end
207
+ end
208
+
209
+ # If mnemonic is out/outi and device type is timer or counter, append time or count.
210
+ case mnemonic
211
+ when /OUT/
212
+ case suffix
213
+ when 'T'
214
+ codes += time_value(value)
215
+ when 'C'
216
+ codes += counter_value(value)
199
217
  end
200
218
  end
219
+
220
+ codes
221
+ end
222
+
223
+ def time_value value
224
+ raise SyntaxError.new "It must be required time value." unless /^\d*\.?\d*$/ =~ value
225
+
226
+ t = value.to_f
227
+ g = 0
228
+ codes = []
229
+ while t < 16384 && g <= 3
230
+ t *= 10.0
231
+ g += 1
232
+ end
233
+ t /= 10.0
234
+ g -= 1
235
+ v = (g << 14) | t.to_i
236
+
237
+ case endian
238
+ when Asm::LITTLE_ENDIAN
239
+ codes = [v & 0xff, (v & 0xff00) >> 8]
240
+ when Asm::BIG_ENDIAN
241
+ codes = [(v & 0xff00) >> 8, v & 0xff]
242
+ end
243
+ codes #rdlsf@de
244
+ end
245
+
246
+ def counter_value value
247
+ raise SyntaxError.new "It must be required count value." unless /^\d+$/ =~ value
248
+
249
+ v = value.to_i
250
+ case endian
251
+ when Asm::LITTLE_ENDIAN
252
+ codes = [v & 0xff, (v & 0xff00) >> 8]
253
+ when Asm::BIG_ENDIAN
254
+ codes = [(v & 0xff00) >> 8, v & 0xff]
255
+ end
201
256
  end
202
257
 
203
258
  end
@@ -51,5 +51,16 @@ module LadderDrive
51
51
  puts "#{name} was successfully created."
52
52
  end
53
53
 
54
+ desc "plugin", "Install the specified plugin."
55
+ def plugin(name)
56
+ root_dir = File.expand_path(File.join(File.dirname(__FILE__), "..", ".."))
57
+ plugins_path = File.join(root_dir, "plugins")
58
+ path = File.join(plugins_path, "#{name}_plugin.rb")
59
+ if File.exist? path
60
+ mkdir_p "plugins"
61
+ cp path, "plugins/#{name}_plugins.rb"
62
+ end
63
+ end
64
+
54
65
  end
55
66
  end
@@ -99,9 +99,14 @@ module LadderDrive
99
99
  end
100
100
  end
101
101
 
102
+ # NOTE: override at subclass
103
+ # It should get from plc
104
+ def device_by_suffix_number suffix, number
105
+ self.class.new suffix, number
106
+ end
107
+
102
108
  def next_device
103
- d = self.class.new @suffix, @number + 1
104
- d
109
+ device_by_suffix_number @suffix, @number + 1
105
110
  end
106
111
 
107
112
  def bit_device?
@@ -109,11 +114,11 @@ module LadderDrive
109
114
  end
110
115
 
111
116
  def + value
112
- self.class.new self.suffix, self.number + value
117
+ device_by_suffix_number self.suffix, self.number + value
113
118
  end
114
119
 
115
120
  def - value
116
- self.class.new self.suffix, [self.number - value, 0].max
121
+ device_by_suffix_number self.suffix, [self.number - value, 0].max
117
122
  end
118
123
 
119
124
  def input?
@@ -132,6 +137,30 @@ module LadderDrive
132
137
  alias :word :value
133
138
  alias :word= :value=
134
139
 
140
+ def text len=8
141
+ n = (len + 1) / 2
142
+ d = self
143
+ a = []
144
+ n.times{ a << d.value; d = d.next_device}
145
+ s = a.pack("n*").split("\x00").first
146
+ s ? s[0,len] : ""
147
+ end
148
+
149
+ def set_text value, len=8
150
+ value = value[0, len]
151
+ value << "\00" unless value.length % 2 == 0
152
+ a = value.unpack("n*")
153
+ d = self
154
+ a.each do |v|
155
+ d.value = v
156
+ d = d.next_device
157
+ end
158
+ end
159
+
160
+ def text= value
161
+ set_text value
162
+ end
163
+
135
164
  def device_code
136
165
  ESC_SUFFIXES.index @suffix
137
166
  end
@@ -0,0 +1,14 @@
1
+ module LadderDrive
2
+ module Protocol
3
+ module Mitsubishi
4
+
5
+ class FxDevice < QDevice
6
+
7
+ def name
8
+ @suffix + @number.to_s.rjust(4, "0")
9
+ end
10
+ end
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,330 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2016 ITO SOFT DESIGN Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ module LadderDrive
25
+ module Protocol
26
+ module Mitsubishi
27
+
28
+ class FxProtocol < Protocol
29
+
30
+ attr_accessor :pc_no
31
+ attr_accessor :baudrate
32
+ attr_accessor :station_no
33
+ attr_accessor :wait_time
34
+
35
+ STX = "\u0002"
36
+ ETX = "\u0003"
37
+ EOT = "\u0004"
38
+ ENQ = "\u0005"
39
+ ACK = "\u0006"
40
+ LF = "\u000a"
41
+ CR = "\u000d"
42
+ NAK = "\u0015"
43
+
44
+ DELIMITER = "\r\n"
45
+ TIMEOUT = 1.0
46
+
47
+ def initialize options={}
48
+ super
49
+ @port = options[:port] || `ls /dev/tty.usb*`.split("\n").map{|l| l.chomp}.first
50
+ @pc_no = 0xff
51
+ @baudrate = 19200
52
+ @station_no = 0
53
+ @wait_time = 0
54
+ #prepare_device_map
55
+ end
56
+
57
+ def open
58
+ open!
59
+ rescue
60
+ nil
61
+ end
62
+
63
+ def open!
64
+ return false unless @port
65
+ begin
66
+ # port, baudrate, bits, stop bits, parity(0:none, 1:even, 2:odd)
67
+ @comm ||= SerialPort.new(@port, @baudrate, 7, 1, 2).tap do |s|
68
+ s.read_timeout = (TIMEOUT * 1000.0).to_i
69
+ end
70
+ rescue => e
71
+ p e
72
+ nil
73
+ end
74
+ end
75
+
76
+ def close
77
+ @comm.close if @comm
78
+ @comm = nil
79
+ end
80
+
81
+ def get_bit_from_device device
82
+ device = device_by_name device
83
+ get_bits_from_device(1, device).first
84
+ end
85
+
86
+ def get_bits_from_device count, device
87
+ raise ArgumentError.new("A count #{count} must be between #{available_bits_range.first} and #{available_bits_range.last} for #{__method__}") unless available_bits_range.include? count
88
+
89
+ device = device_by_name device
90
+ packet = body_for_get_bits_from_device(count, device) + DELIMITER
91
+ @logger.debug("> #{dump_packet packet}")
92
+ open
93
+ @comm.write(packet)
94
+ @comm.flush
95
+ res = receive
96
+ bits = []
97
+
98
+ if res
99
+ if check_sum(res[0..-5]) == res[-4,2]
100
+ bits =
101
+ res[5..-6].each_char.map do |c|
102
+ c == "1" ? true : false
103
+ end
104
+ else
105
+ end
106
+ end
107
+ @logger.debug("> #{dump_packet ack_packet}")
108
+ @comm.write(ack_packet)
109
+ @logger.debug("get #{device.name} => #{bits}")
110
+
111
+ bits
112
+ end
113
+
114
+ def set_bits_to_device bits, device
115
+ raise ArgumentError.new("A count #{count} must be between #{available_bits_range.first} and #{available_bits_range.last} for #{__method__}") unless available_bits_range.include? bits.size
116
+
117
+ device = device_by_name device
118
+ packet = body_for_set_bits_to_device(bits, device)
119
+ @logger.debug("> #{dump_packet packet}")
120
+ open
121
+ @comm.write(packet)
122
+ @comm.flush
123
+ res = receive
124
+ @logger.debug("set #{bits} to:#{device.name}")
125
+
126
+ # error checking
127
+ unless res == ack_packet
128
+ raise "ERROR: return #{res} for set_bits_to_device(#{bits}, #{device.name})"
129
+ end
130
+ end
131
+
132
+ def get_word_from_device device
133
+ device = device_by_name device
134
+ get_words_from_device(1, device).first
135
+ end
136
+
137
+ def get_words_from_device(count, device)
138
+ raise ArgumentError.new("A count #{count} must be between #{available_words_range.first} and #{available_words_range.last} for #{__method__}") unless available_words_range.include? count
139
+
140
+ device = device_by_name device
141
+ packet = body_for_get_words_from_device(count, device) + DELIMITER
142
+ @logger.debug("> #{dump_packet packet}")
143
+ open
144
+ @comm.write(packet)
145
+ @comm.flush
146
+ res = receive
147
+ words = []
148
+
149
+ if res
150
+ if check_sum(res[0..-5]) == res[-4,2]
151
+ words =
152
+ res[5..-6].scan(/.{4}/).map do |v|
153
+ v.to_i(16)
154
+ end
155
+ else
156
+ end
157
+ end
158
+ @logger.debug("> #{dump_packet ack_packet}")
159
+ @comm.write(ack_packet)
160
+ @logger.debug("get #{device.name} => #{words}")
161
+
162
+ words
163
+ end
164
+
165
+ def set_words_to_device words, device
166
+ raise ArgumentError.new("A count of words #{words.size} must be between #{available_words_range.first} and #{available_words_range.last} for #{__method__}") unless available_bits_range.include? words.size
167
+
168
+ device = device_by_name device
169
+ packet = body_for_set_words_to_device(words, device)
170
+ @logger.debug("> #{dump_packet packet}")
171
+ open
172
+ @comm.write(packet)
173
+ @comm.flush
174
+ res = receive
175
+ @logger.debug("set #{words} to: #{device.name}")
176
+
177
+ # error checking
178
+ unless res == ack_packet
179
+ raise "ERROR: return #{res} for set_bits_to_device(#{words}, #{device.name})"
180
+ end
181
+ end
182
+
183
+ def device_by_name name
184
+ case name
185
+ when String
186
+ FxDevice.new name
187
+ when EscDevice
188
+ local_device_of name
189
+ else
190
+ # it may be already QDevice
191
+ name
192
+ end
193
+ end
194
+
195
+
196
+ def receive
197
+ res = ""
198
+ begin
199
+ Timeout.timeout(TIMEOUT) do
200
+ res = @comm.gets
201
+ end
202
+ res
203
+ rescue Timeout::Error
204
+ puts "*** ERROR: TIME OUT : #{res} ***"
205
+ end
206
+ @logger.debug("< #{dump_packet res}")
207
+ res
208
+ end
209
+
210
+ def available_bits_range device=nil
211
+ 1..256
212
+ end
213
+
214
+ def available_words_range device=nil
215
+ 1..64
216
+ end
217
+
218
+ def body_for_get_bit_from_deivce device
219
+ body_for_get_bits_from_device 1, device
220
+ end
221
+
222
+ def body_for_get_bits_from_device count, device
223
+ body = header_with_command "BR"
224
+ body += "#{device.name}#{count.to_s(16).rjust(2, '0')}"
225
+ body += check_sum(body)
226
+ body += DELIMITER
227
+ body.upcase
228
+ end
229
+
230
+ def body_for_get_words_from_device count, device
231
+ body = header_with_command "WR"
232
+ body += "#{device.name}#{count.to_s(16).rjust(2, '0')}"
233
+ body += check_sum(body)
234
+ body += DELIMITER
235
+ body.upcase
236
+ end
237
+
238
+ def body_for_set_bits_to_device bits, device
239
+ body = header_with_command "BW"
240
+ body += "#{device.name}#{bits.count.to_s(16).rjust(2, '0')}"
241
+ body += bits.map{|b| b ? "1" : "0"}.join("")
242
+ body += check_sum(body)
243
+ body += DELIMITER
244
+ body.upcase
245
+ end
246
+ alias :body_for_set_bit_to_device :body_for_set_bits_to_device
247
+
248
+ def body_for_set_words_to_device words, device
249
+ body = header_with_command "WW"
250
+ body += "#{device.name}#{words.count.to_s(16).rjust(2, '0')}"
251
+ body += words.map{|w| w.to_s(16).rjust(4, "0")}.join("")
252
+ body += check_sum(body)
253
+ body += DELIMITER
254
+ body.upcase
255
+ end
256
+
257
+
258
+ =begin
259
+ def data_for_device device
260
+ a = data_for_int device.number
261
+ a[3] = device.suffix_code
262
+ a
263
+ end
264
+
265
+ def data_for_short value
266
+ [value].pack("v").unpack("C*")
267
+ end
268
+
269
+ def data_for_int value
270
+ [value].pack("V").unpack("C*")
271
+ end
272
+ =end
273
+
274
+ def dump_packet packet
275
+ packet.inspect
276
+ end
277
+
278
+ =begin
279
+ def prepare_device_map
280
+ @conv_dev_dict ||= begin
281
+ h = {}
282
+ [
283
+ ["X", "X0", 1024],
284
+ ["Y", "Y0", 1024],
285
+ ["M", "M0", 1024],
286
+ ["C", "C0", 256],
287
+ ["T", "T0", 256],
288
+ ["L", "L0", 1024],
289
+ ["SC", "M1024", 1024],
290
+ ["D", "D0", 1024],
291
+ ["H", "D1024", 1024],
292
+ ["SD", "D2048", 1024],
293
+ ["PRG", "D3072", 1024] # ..D4095
294
+ ].each do |s,d,c|
295
+ h[s] = [QDevice.new(d), c]
296
+ end
297
+ h
298
+ end
299
+ end
300
+ =end
301
+
302
+ =begin
303
+ def local_device_of device
304
+ return device if device.is_a? QDevice
305
+ d, c = @conv_dev_dict[device.suffix]
306
+ return nil unless device.number < c
307
+ ld = QDevice.new(d.suffix, d.number + device.number)
308
+ device_by_name ld.name
309
+ end
310
+ =end
311
+
312
+ private
313
+
314
+ def check_sum packet
315
+ packet[1..-1].upcase.unpack("C*").inject(0){|s,c| s + c}.to_s(16).rjust(2, "0")[-2, 2].upcase
316
+ end
317
+
318
+ def header_with_command command
319
+ "#{ENQ}#{station_no.to_s.rjust(2,'0')}#{pc_no.to_s(16).rjust(2, '0')}#{command}#{wait_time.to_s}".upcase
320
+ end
321
+
322
+ def ack_packet
323
+ "#{ACK}#{station_no.to_s.rjust(2,'0')}#{pc_no.to_s(16).rjust(2, '0')}#{DELIMITER}".upcase
324
+ end
325
+
326
+ end
327
+
328
+ end
329
+ end
330
+ end
@@ -58,7 +58,7 @@ module Mitsubishi
58
58
  raise ArgumentError.new("A count #{count} must be between #{available_bits_range.first} and #{available_bits_range.last} for #{__method__}") unless available_bits_range.include? count
59
59
 
60
60
  device = device_by_name device
61
- packet = make_packet(body_for_get_bits_from_deivce(count, device))
61
+ packet = make_packet(body_for_get_bits_from_device(count, device))
62
62
  @logger.debug("> #{dump_packet packet}")
63
63
  open
64
64
  @socket.write(packet.pack("C*"))
@@ -116,7 +116,7 @@ module Mitsubishi
116
116
  raise ArgumentError.new("A count #{count} must be between #{available_words_range.first} and #{available_words_range.last} for #{__method__}") unless available_bits_range.include? count
117
117
 
118
118
  device = device_by_name device
119
- packet = make_packet(body_for_get_words_from_deivce(count, device))
119
+ packet = make_packet(body_for_get_words_from_device(count, device))
120
120
  @logger.debug("> #{dump_packet packet}")
121
121
  open
122
122
  @socket.write(packet.pack("C*"))
@@ -211,14 +211,14 @@ module Mitsubishi
211
211
  end
212
212
 
213
213
  def body_for_get_bit_from_deivce device
214
- body_for_get_bits_from_deivce 1, device
214
+ body_for_get_bits_from_device 1, device
215
215
  end
216
216
 
217
- def body_for_get_bits_from_deivce count, device
218
- body_for_get_words_from_deivce count, device, false
217
+ def body_for_get_bits_from_device count, device
218
+ body_for_get_words_from_device count, device, false
219
219
  end
220
220
 
221
- def body_for_get_words_from_deivce count, device, word = true
221
+ def body_for_get_words_from_device count, device, word = true
222
222
  body = [0x01, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x01, 0x00]
223
223
  body[2] = 1 unless word
224
224
  body[4..7] = data_for_device(device)
@@ -26,5 +26,9 @@ $:.unshift File.dirname(__FILE__)
26
26
  require 'socket'
27
27
  require 'logger'
28
28
  require 'timeout'
29
+
29
30
  require 'qdevice'
30
31
  require 'mc_protocol'
32
+
33
+ require 'fx_device'
34
+ require 'fx_protocol'
@@ -189,6 +189,7 @@ module Protocol
189
189
  end
190
190
  end
191
191
 
192
+ require 'serialport'
192
193
  require 'keyence/keyence'
193
194
  # Use load instead require, because there are two emulator files.
194
195
  load File.join(dir, 'emulator/emulator.rb')
@@ -22,5 +22,5 @@
22
22
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
 
24
24
  module LadderDrive
25
- VERSION = "0.5.2"
25
+ VERSION = "0.6.0"
26
26
  end
@@ -57,6 +57,13 @@ module Emulator
57
57
  @changed
58
58
  end
59
59
 
60
+ # NOTE: override at subclass
61
+ # It should get from plc
62
+ def device_by_suffix_number suffix, number
63
+ d = super
64
+ plc.device_by_name d.name
65
+ end
66
+
60
67
  def value= value
61
68
  set_value value
62
69
  end
@@ -22,14 +22,18 @@
22
22
 
23
23
  require 'ladder_drive/plc_device'
24
24
  require 'yaml'
25
+ require 'plc_plugins'
25
26
 
26
27
  include LadderDrive
27
28
 
28
29
  module Plc
29
30
  module Emulator
30
31
 
32
+
31
33
  class EmuPlc
32
34
 
35
+ include PlcPlugins
36
+
33
37
  attr_accessor :program_data
34
38
  attr_reader :program_pointer
35
39
  attr_reader :config
@@ -60,6 +64,7 @@ module Emulator
60
64
  @lock = Mutex.new
61
65
  @config = config
62
66
  reset
67
+ load_plugins
63
68
  end
64
69
 
65
70
  def device_by_name name
@@ -120,6 +125,7 @@ module Emulator
120
125
  # Save must be executed berofe sync_output, because changed flag was clear after sync_output
121
126
  save
122
127
  sync_output
128
+ exec_plugins
123
129
  end
124
130
 
125
131
  def bool
@@ -557,8 +563,6 @@ EOB
557
563
  end
558
564
  def rst; set true; end
559
565
 
560
-
561
-
562
566
  end
563
567
 
564
568
  end
@@ -53,14 +53,15 @@ module Emulator
53
53
  server = TCPServer.open @port
54
54
  puts "launching emulator ... "
55
55
  loop do
56
- socket = server.accept
57
- puts "done launching"
58
- while line = socket.gets
59
- begin
60
- r = @plc.execute_console_commands line
61
- socket.puts r
62
- rescue => e
63
- socket.puts "E0 #{e}\r\n"
56
+ Thread.start(server.accept) do |socket|
57
+ puts "done launching"
58
+ while line = socket.gets
59
+ begin
60
+ r = @plc.execute_console_commands line
61
+ socket.puts r
62
+ rescue => e
63
+ socket.puts "E0 #{e}\r\n"
64
+ end
64
65
  end
65
66
  end
66
67
  end