ladder_drive 0.5.2 → 0.6.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.
@@ -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