# # Copyright (c) 2016 ITO SOFT DESIGN Inc. # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. require 'ladder_drive/plc_device' include LadderDrive module Plc module Emulator class EmuPlc attr_accessor :program_data attr_reader :program_pointer attr_reader :device_dict attr_reader :errors SUFFIXES = %w(x y m c t l sc cc tc d cs ts h sd) SUFFIXES.each do |k| attr_reader :"#{k}_devices" end STOP_PLC_FLAG = 2 # bit 1 CLEAR_PROGRAM_FLAG = 4 # bit 2 require bit 1 on CYCLE_RUN_FLAG = 2 INDEX_BIT_STACK_COUNT = 4 INDEX_BIT_STACK = 5 SIZE_OF_BIT_STACK = 3 def initialize SUFFIXES.each do |k| eval "@#{k}_devices = []" end @lock = Mutex.new reset end def device_by_name name @lock.synchronize { d = device_dict[name] unless d d = EmuDevice.new name device_dict[name] = d end d } end def device_by_type_and_number type, number d = EmuDevice.new type, number device_by_name d.name end def reset @word = 0 @program_data = [] @device_dict ||= {} @lock.synchronize { @device_dict.values.each do |d| d.reset end } end def run_cycle status_to_plc = device_by_name "SD0" status_form_plc = device_by_name "SD1" sync_input case status_to_plc.value & (STOP_PLC_FLAG | CLEAR_PROGRAM_FLAG) when STOP_PLC_FLAG status_form_plc.value = 0 sleep 0.1 when STOP_PLC_FLAG | CLEAR_PROGRAM_FLAG reset status_form_plc.value = CLEAR_PROGRAM_FLAG sleep 0.1 when 0 status_form_plc.value = CYCLE_RUN_FLAG @program_pointer = 0 clear_stacks while fetch_and_execution; end sleep 0.0001 else sleep 0.1 end sync_output end def bool (stack_device.word & 1) != 0 ? true : false end def bool= value if value stack_device.word |= 1 else stack_device.word &= 0xfffe; end end def run Thread.new do loop do run_cycle end end end def execute_console_commands line a = line.chomp.split(/\s+/) case a.first when /^ST/i d = device_by_name a[1] d.set_value true, :in "OK\r" when /^RS/i d = device_by_name a[1] d.set_value false, :in "OK\r" when /^RDS/i d = device_by_name a[1] c = a[2].to_i r = [] if d.bit_device? c.times do r << (d.bool(:out) ? 1 : 0) d = device_by_name (d+1).name end else case d.suffix when "PRG" c.times do r << program_data[d.number * 2, 2].pack("C*").unpack("n").first d = device_by_name (d+1).name end else c.times do r << d.word(:out) d = device_by_name (d+1).name end end end r.map{|e| e.to_s}.join(" ") + "\r" when /^WRS/i d = device_by_name a[1] c = a[2].to_i case d.suffix when "PRG" a[3, c].each do |v| program_data[d.number * 2, 2] = [v.to_i].pack("n").unpack("C*") d = device_by_name (d+1).name end else if d.bit_device? a[3, c].each do |v| d.set_value v == "0" ? false : true, :in d = device_by_name (d+1).name end else a[3, c].each do |v| d.word = v.to_i d.set_value v.to_i, :in d = device_by_name (d+1).name end end end "OK\r" when /E/ eval(a[1..-1].join(" ")).inspect else raise "Unknown command #{a.first}" end end private # ---------------- # stack operations def stack_device index=0 device_by_name (device_by_name("SD0") + INDEX_BIT_STACK + index).name end def stack_count_device device_by_name (device_by_name("SD0") + INDEX_BIT_STACK_COUNT).name end def push_stack flag = false stack_device.word <<= 1 if flag stack_device.word |= 1 else stack_device.word &= 0xfffe end stack_device.word &= 0xffff end def pop_stack stack_device.word >>= 1 stack_device.word |= 0x8000 end def clear_stacks stack_device.word = 0xffff stack_count_device.word = 0 end def push_stacks if stack_count_device.word > SIZE_OF_BIT_STACK puts "ERROR: stacks overflow pc: H#{program_pointer.to_s(16).rjust(4, "0")}" return end values = [] (SIZE_OF_BIT_STACK - 1).downto 0 do |i| values << stack_device(i).word end values.shift values << 0xffff SIZE_OF_BIT_STACK.times do |i| stack_device(i).word = values.pop end stack_count_device.word += 1 end def pop_stacks if stack_count_device.word <= 0 puts "ERROR: stacks underflow pc: H#{program_pointer.to_s(16).rjust(4, "0")}" return end values = [] (SIZE_OF_BIT_STACK - 1).downto 0 do |i| values << stack_device(i).word end v = values.pop values.each_with_index do |v, i| stack_device(i).word = v end stack_count_device.word -= 1 v end # ---------------- def sync_input @lock.synchronize { device_dict.values.each do |d| d.sync_input end } end def sync_output @lock.synchronize { device_dict.values.each do |d| d.sync_output end } end def and_join_stack d = stack_device b = d.word == 0xffff d.word = 0xffff push_stack b end def mnenonic_table @mnemonic_table ||= begin s = <