lib/plc/emulator/emu_plc.rb in ladder_drive-0.4.1 vs lib/plc/emulator/emu_plc.rb in ladder_drive-0.5.0

- old
+ new

@@ -19,20 +19,22 @@ # 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' +require 'yaml' include LadderDrive module Plc module Emulator class EmuPlc attr_accessor :program_data attr_reader :program_pointer + attr_reader :config attr_reader :device_dict attr_reader :errors SUFFIXES = %w(x y m c t l sc cc tc d cs ts h sd) @@ -47,24 +49,33 @@ INDEX_BIT_STACK_COUNT = 4 INDEX_BIT_STACK = 5 SIZE_OF_BIT_STACK = 3 - def initialize + SAVE_INTERVAL = 1.0 #60.0 + + def initialize config={} SUFFIXES.each do |k| eval "@#{k}_devices = []" end @lock = Mutex.new + @config = config reset end def device_by_name name @lock.synchronize { d = device_dict[name] unless d - d = EmuDevice.new name - device_dict[name] = d + # try normalized name again + new_d = EmuDevice.new(name) + d = device_dict[new_d.name] + unless d + d = new_d + d.plc = self + device_dict[d.name] = d + end end d } end @@ -92,10 +103,11 @@ when STOP_PLC_FLAG status_form_plc.value = 0 sleep 0.1 when STOP_PLC_FLAG | CLEAR_PROGRAM_FLAG reset + load status_form_plc.value = CLEAR_PROGRAM_FLAG sleep 0.1 when 0 status_form_plc.value = CYCLE_RUN_FLAG @program_pointer = 0 @@ -103,10 +115,12 @@ while fetch_and_execution; end sleep 0.0001 else sleep 0.1 end + # Save must be executed berofe sync_output, because changed flag was clear after sync_output + save sync_output end def bool (stack_device.word & 1) != 0 ? true : false @@ -128,24 +142,31 @@ end end def execute_console_commands line a = line.chomp.split(/\s+/) - case a.first + + # separate data type except eval command. + unless a[0] == "E" + word = /([A-Z0-9]+)(\.H)?/ =~ a[1] + a[1] = $1 + end + + case a[0] when /^ST/i d = device_by_name a[1] d.set_value true, :in - "OK\r" + "OK\r\n" when /^RS/i d = device_by_name a[1] d.set_value false, :in - "OK\r" + "OK\r\n" when /^RDS/i d = device_by_name a[1] c = a[2].to_i r = [] - if d.bit_device? + if !word && d.bit_device? c.times do r << (d.bool(:out) ? 1 : 0) d = device_by_name (d+1).name end else @@ -160,42 +181,104 @@ 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 + r.map{|e| e.to_s(16)}.join(" ") + "\r\n" + + # LadderDrive communication is bases KV protocol. + # LadderDrive console is use WRS command only. (Not use WR command) + # WR command is for irBoard. (http://irboard.itosoft.com) + # A word value is communicated with hex format only in this product. (hard coding) + when /^WRS?/i d = device_by_name a[1] + a.insert 2, "1" if /^WR$/i =~ a.first 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*") + program_data[d.number * 2, 2] = [v.to_i(16)].pack("n").unpack("C*") d = device_by_name (d+1).name end else - if d.bit_device? + if !word && d.bit_device? a[3, c].each do |v| d.set_value v == "0" ? false : true, :in - d = device_by_name (d+1).name + d = d + 1#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 + v = v.to_i(16) + d.set_word v, :in + #d.set_value v, :in + d = d + 1#device_by_name (d+1).name end end end - "OK\r" + "OK\r\n" when /E/ eval(a[1..-1].join(" ")).inspect else raise "Unknown command #{a.first}" end end + # --- queries + def keep_device? device + @keep_ranges ||= begin + if config[:keep] + config[:keep].map do |a| + [device_by_name(a.first), device_by_name(a.last)] + end + else + [] + end + end + @keep_ranges.each do |a| + f = a.first + e = a.last + next unless f.suffix == device.suffix + return true if (f.number..e.number).include? device.number + end + false + end + private + + # ---------------- + # Load / Save keep devices + + def dump_memory_path + @dump_memory_paty ||= File.join("cache", "dump_memory.yml").tap do |path| + FileUtils.mkdir_p( File.dirname(path)) + end + end + + def load + path = dump_memory_path + return unless File.exist? path + + YAML.load(File.read(path)).each do |a| + d = device_by_name(a.first) + d.value = a.last + end + end + + def save + devices = @device_dict.values.select{|d| self.keep_device?(d)} + needs_save = @pending_save || !(devices.select{|d| d.changed?}).empty? + return unless needs_save + now = Time.now + if @saved_at && ((now - @saved_at) < SAVE_INTERVAL) + @pending_save ||= true + return + end + + File.write dump_memory_path, YAML.dump(devices.map{|d| [d.name, d.value]}) + @saved_at = now + @pending_save = false + end + # ---------------- # stack operations def stack_device index=0 device_by_name (device_by_name("SD0") + INDEX_BIT_STACK + index).name