ladder_drive 0.6.3 → 0.6.7

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.
Files changed (36) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +7 -2
  3. data/Gemfile.lock +107 -62
  4. data/README.md +1 -0
  5. data/README_jp.md +2 -2
  6. data/ladder_drive.gemspec +12 -8
  7. data/lib/ladder_drive/cli.rb +16 -4
  8. data/lib/ladder_drive/config.rb +4 -1
  9. data/lib/ladder_drive/config_target.rb +1 -0
  10. data/lib/ladder_drive/plc_device.rb +11 -0
  11. data/lib/ladder_drive/protocol/keyence/kv_protocol.rb +1 -11
  12. data/lib/ladder_drive/protocol/mitsubishi/fx_protocol.rb +0 -11
  13. data/lib/ladder_drive/protocol/mitsubishi/mc_protocol.rb +1 -11
  14. data/lib/ladder_drive/protocol/omron/c_mode_protocol.rb +224 -0
  15. data/lib/ladder_drive/protocol/omron/fins_tcp_protocol.rb +380 -0
  16. data/lib/ladder_drive/protocol/omron/omron.rb +28 -0
  17. data/lib/ladder_drive/protocol/omron/omron_device.rb +139 -0
  18. data/lib/ladder_drive/protocol/protocol.rb +16 -5
  19. data/lib/ladder_drive/tasks/build.rb +1 -1
  20. data/lib/ladder_drive/version.rb +1 -1
  21. data/lib/ladder_drive.rb +3 -0
  22. data/lib/plc/emulator/emu_plc_server.rb +3 -1
  23. data/lib/plc/emulator/plc_plugins.rb +20 -13
  24. data/lib/plc/emulator/plugin_trigger_state.rb +152 -0
  25. data/plugins/ambient_plugin.rb +155 -0
  26. data/plugins/config/ambient.yaml.example +34 -0
  27. data/plugins/config/google_drive.yaml.example +39 -0
  28. data/plugins/config/ifttt.yaml.example +24 -0
  29. data/plugins/config/plc_mapper.yaml.example +36 -0
  30. data/plugins/config/slack.yaml.example +36 -0
  31. data/plugins/config/trello.yaml.example +41 -0
  32. data/plugins/ifttt_plugin.rb +4 -27
  33. data/plugins/plc_mapper_plugin.rb +27 -15
  34. data/plugins/slack_plugin.rb +14 -3
  35. data/plugins/trello_plugin.rb +30 -42
  36. metadata +72 -15
@@ -0,0 +1,28 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2019 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
+ $:.unshift File.dirname(__FILE__)
25
+
26
+ require 'omron_device'
27
+ require 'c_mode_protocol'
28
+ require 'fins_tcp_protocol'
@@ -0,0 +1,139 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2019 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
+
25
+ # Supported models : CP1E
26
+
27
+ module LadderDrive
28
+ module Protocol
29
+ module Omron
30
+
31
+ class OmronDevice < PlcDevice
32
+
33
+ attr_reader :suffix, :channel, :bit
34
+
35
+ SUFFIXES = %w(M H D T C A)
36
+
37
+ def initialize a, b = nil, c=nil
38
+ case a
39
+ when Array
40
+ =begin
41
+ case a.size
42
+ when 4
43
+ @suffix = suffix_for_code(a[3])
44
+ @channel = ((a[2] << 8 | a[1]) << 8) | a[0]
45
+ end
46
+ =end
47
+ else
48
+ if b
49
+ @suffix = a.upcase if a
50
+ @channel = b.to_i
51
+ @bit = c.to_i if c
52
+ else
53
+ if /^(M|H|D|T|C|A)?([0-9]+)(\.([0-9]{1,2}))?$/i =~ a
54
+ @suffix = $1.upcase if $1
55
+ @channel = $2.to_i
56
+ @bit = $4.to_i if $4
57
+ end
58
+ end
59
+ end
60
+ case @suffix
61
+ when "T", "C"
62
+ raise "#{self.name} is not allowed as a bit device." if @bit
63
+ end
64
+ end
65
+
66
+ def channel_device
67
+ return self unless bit_device?
68
+ self.class.new suffix, channel
69
+ end
70
+
71
+ def valid?
72
+ !!channel
73
+ end
74
+
75
+ def name
76
+ if bit
77
+ "#{suffix}#{channel}.#{bit.to_s.rjust(2, '0')}"
78
+ else
79
+ "#{suffix}#{channel}"
80
+ end
81
+ end
82
+
83
+ def next_device
84
+ self + 1
85
+ end
86
+
87
+ def bit_device?
88
+ !!bit
89
+ end
90
+
91
+ def suffix_for_code code
92
+ index = SUFFIX_CODES.index code
93
+ index ? SUFFIXES[index] : nil
94
+ end
95
+
96
+ def suffix_code
97
+ index = SUFFIXES.index suffix
98
+ index ? SUFFIX_CODES[index] : 0
99
+ end
100
+
101
+ def + value
102
+ if bit
103
+ v = channel * 16 + bit + value
104
+ c = v / 16
105
+ b = v % 16
106
+ self.class.new suffix, c, b
107
+ else
108
+ self.class.new suffix, channel + value
109
+ end
110
+ end
111
+
112
+ def - value
113
+ case value
114
+ when OmronDevice
115
+ d = value
116
+ raise "Can't subtract between different device type." if self.bit_device? ^ d.bit_device?
117
+ if bit
118
+ (channel * 16 + bit) - (d.channel * 16 + d.bit)
119
+ else
120
+ channel - d.channel
121
+ end
122
+ else
123
+ value = value.to_i
124
+ if bit
125
+ v = [channel * 16 + bit - value, 0].max
126
+ c = v / 16
127
+ b = v % 16
128
+ self.class.new suffix, c, b
129
+ else
130
+ self.class.new suffix, [channel - value, 0].max
131
+ end
132
+ end
133
+ end
134
+
135
+ end
136
+
137
+ end
138
+ end
139
+ end
@@ -58,20 +58,22 @@ module Protocol
58
58
  end
59
59
  end
60
60
 
61
+ TIMEOUT = 1.0
62
+
61
63
  # abstract methods
62
64
 
63
65
  def open; end
64
66
  def close; end
65
67
 
66
- def get_bit_from_device device; end
68
+ def get_bit_from_device device; get_bits_from_device(1, device_by_name(device)).first; end
67
69
  def get_bits_from_device count, device; end
68
- def set_bits_to_device bits, device; end
69
70
  def set_bit_to_device bit, device; set_bits_to_device [bit], device; end
71
+ def set_bits_to_device bits, device; end
70
72
 
71
- def get_word_from_device device; end
72
- def get_words_from_device(count, device); end
73
- def set_words_to_device words, device; end
73
+ def get_word_from_device device; get_words_from_device(1, device_by_name(device)).first; end
74
+ def get_words_from_device count, device; end
74
75
  def set_word_to_device word, device; set_words_to_device [word], device; end
76
+ def set_words_to_device words, device; end
75
77
 
76
78
  def device_by_name name; nil; end
77
79
 
@@ -188,6 +190,14 @@ module Protocol
188
190
  end
189
191
  end
190
192
 
193
+ def destination_ipv4
194
+ Socket.gethostbyname(self.host)[3].unpack("C4").join('.')
195
+ end
196
+
197
+ def self_ipv4
198
+ Socket::getaddrinfo(Socket.gethostname,"echo",Socket::AF_INET)[0][3]
199
+ end
200
+
191
201
  end
192
202
 
193
203
  end
@@ -198,3 +208,4 @@ require 'keyence/keyence'
198
208
  # Use load instead require, because there are two emulator files.
199
209
  load File.join(dir, 'emulator/emulator.rb')
200
210
  require 'mitsubishi/mitsubishi'
211
+ require 'omron/omron'
@@ -112,7 +112,7 @@ namespace :service do
112
112
  # copy lddrive.serive file to /etc/systemd/system
113
113
  fname = "lddrive.service"
114
114
  s = File.read(File.join(template_dir, fname))
115
- s.gsub!("<%= exec_start %>", "/home/pi/project/lddrive")
115
+ s.gsub!("<%= exec_start %>", "#{Dir.pwd}/lddrive")
116
116
  File.write File.join("/etc", "systemd", "system", fname), s
117
117
 
118
118
  # reload services
@@ -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.6.3"
25
+ VERSION = "0.6.7"
26
26
  end
data/lib/ladder_drive.rb CHANGED
@@ -24,5 +24,8 @@
24
24
  dir = File.expand_path(File.dirname(__FILE__))
25
25
  $:.unshift dir unless $:.include? dir
26
26
 
27
+ require 'dotenv'
28
+ Dotenv.load
29
+
27
30
  require "plc/plc"
28
31
  require "ladder_drive/ladder_drive"
@@ -52,9 +52,11 @@ module Emulator
52
52
  Thread.new do
53
53
  server = TCPServer.open @port
54
54
  puts "launching emulator ... "
55
+ launched = false
55
56
  loop do
56
57
  Thread.start(server.accept) do |socket|
57
- puts "done launching"
58
+ puts "done launching" unless launched
59
+ launched ||= true
58
60
  while line = socket.gets
59
61
  begin
60
62
  r = @plc.execute_console_commands line
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright (c) 2018 ITO SOFT DESIGN Inc.
2
+ # Copyright (c) 201 ITO SOFT DESIGN Inc.
3
3
  #
4
4
  # Permission is hereby granted, free of charge, to any person obtaining
5
5
  # a copy of this software and associated documentation files (the
@@ -25,6 +25,8 @@ $:.unshift dir unless $:.include? dir
25
25
 
26
26
  require 'active_support'
27
27
  require 'active_support/core_ext'
28
+ require 'erb'
29
+ require 'plugin_trigger_state'
28
30
 
29
31
  module PlcPlugins
30
32
 
@@ -110,6 +112,8 @@ class Plugin
110
112
  def initialize plc
111
113
  @config = load_config
112
114
  @plc = plc
115
+ @device_states = {}
116
+ @trigger_states = {}
113
117
  end
114
118
 
115
119
  def name
@@ -124,31 +128,34 @@ class Plugin
124
128
  return false unless self.plc == plc
125
129
  end
126
130
 
131
+ def triggered? trigger_config
132
+ state = trigger_state_for trigger_config
133
+ state.reset
134
+ state.update
135
+ state.triggered?
136
+ end
137
+
138
+ def trigger_state_for trigger_config
139
+ @trigger_states[trigger_config.object_id] ||= PluginTriggerState.new(plc, trigger_config)
140
+ end
141
+
142
+
127
143
  private
128
144
 
129
145
  def load_config
130
146
  h = {}
131
147
  path = File.join("config", "plugins", "#{name}.yml")
132
148
  if File.exist?(path)
133
- h = YAML.load(File.read(path))
149
+ erb = ERB.new File.read(path)
150
+ h = YAML.load(erb.result(binding))
134
151
  h = JSON.parse(h.to_json, symbolize_names: true)
135
152
  end
136
153
  h
137
154
  end
138
155
 
139
- end
140
156
 
141
157
  end
142
- end
143
158
 
144
159
 
145
- # @deprecated use LadderDrive::Emulator::Plugin class instead of this.
146
- def load_plugin_config name
147
- h = {}
148
- path = File.join("config", "plugins", "#{name}.yml")
149
- if File.exist?(path)
150
- h = YAML.load(File.read(path))
151
- h = JSON.parse(h.to_json, symbolize_names: true)
152
- end
153
- h
160
+ end
154
161
  end
@@ -0,0 +1,152 @@
1
+ #
2
+ # Copyright (c) 2018 ITO SOFT DESIGN Inc.
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ dir = Dir.pwd
24
+ $:.unshift dir unless $:.include? dir
25
+
26
+ require 'active_support'
27
+ require 'active_support/core_ext'
28
+ require 'erb'
29
+
30
+
31
+ module LadderDrive
32
+ module Emulator
33
+
34
+
35
+ class PluginTriggerState
36
+ attr_reader :plc
37
+ attr_reader :config
38
+ attr_reader :device
39
+ attr_reader :value_type
40
+ attr_reader :value
41
+
42
+ def initialize plc, config
43
+ @plc = plc
44
+ @config = config
45
+ @value_type = config[:value_type]
46
+ @devices = {}
47
+ @next_trigger_times = {}
48
+ end
49
+
50
+ def key
51
+ object_id
52
+ end
53
+
54
+ def value
55
+ @value
56
+ end
57
+
58
+ def value= value
59
+ if @changed.nil?
60
+ @changed = @value != value
61
+ @raised = @changed && !!value
62
+ @fallen = @changed && !value
63
+ @value = value
64
+ end
65
+ end
66
+
67
+ def changed?
68
+ !!@changed
69
+ end
70
+
71
+ def raised?
72
+ !!@raised
73
+ end
74
+
75
+ def fallen?
76
+ !!@fallen
77
+ end
78
+
79
+ def triggered?
80
+ !!@triggered
81
+ end
82
+
83
+ def device
84
+ device_with_trigger(config[:triggers]&.first || config[:trigger])
85
+ end
86
+
87
+ def update
88
+ return unless @triggered.nil?
89
+
90
+ triggers = config[:triggers]
91
+ triggers ||= [config[:trigger]]
92
+
93
+ @triggered = false
94
+
95
+ triggers.each do |trigger|
96
+ case trigger[:type]
97
+ when "changed", "raise", "fall", "raise_and_fall"
98
+ device = device_with_trigger(trigger)
99
+ value = device.send(@value_type || :value)
100
+
101
+ # update flags
102
+ @changed = @value != value
103
+ case value
104
+ when true, false, nil
105
+ @raised = @changed && !!value
106
+ @fallen = @changed && !value
107
+ else
108
+ @fallen = @changed && value == 0
109
+ @raised = @changed && !@fallen
110
+ end
111
+ @value = value
112
+
113
+ # judgement triggered
114
+ case trigger[:type]
115
+ when "raise"
116
+ @triggered = true if @raised
117
+ when "fall"
118
+ @triggered = true if @fallen
119
+ else
120
+ @triggered = true if @changed
121
+ end
122
+
123
+ when "interval"
124
+ now = Time.now
125
+ t = @next_trigger_times[trigger.object_id] || now
126
+ while now >= t
127
+ @triggered ||= true
128
+ t += trigger[:interval]
129
+ end
130
+ @next_trigger_times[trigger.object_id] = t
131
+ else
132
+ @triggered = false
133
+ end
134
+ end
135
+ @triggered
136
+ end
137
+
138
+ def reset
139
+ @changed = nil
140
+ @raised = nil
141
+ @fallen = nil
142
+ @triggered = nil
143
+ end
144
+
145
+ def device_with_trigger trigger
146
+ @devices[trigger.object_id] ||= @plc.device_by_name(trigger[:device])
147
+ end
148
+
149
+ end
150
+
151
+ end
152
+ end