ladder_drive 0.6.3 → 0.6.7

Sign up to get free protection for your applications and to get access to all the features.
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