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.
- checksums.yaml +5 -5
- data/Gemfile +7 -2
- data/Gemfile.lock +107 -62
- data/README.md +1 -0
- data/README_jp.md +2 -2
- data/ladder_drive.gemspec +12 -8
- data/lib/ladder_drive/cli.rb +16 -4
- data/lib/ladder_drive/config.rb +4 -1
- data/lib/ladder_drive/config_target.rb +1 -0
- data/lib/ladder_drive/plc_device.rb +11 -0
- data/lib/ladder_drive/protocol/keyence/kv_protocol.rb +1 -11
- data/lib/ladder_drive/protocol/mitsubishi/fx_protocol.rb +0 -11
- data/lib/ladder_drive/protocol/mitsubishi/mc_protocol.rb +1 -11
- data/lib/ladder_drive/protocol/omron/c_mode_protocol.rb +224 -0
- data/lib/ladder_drive/protocol/omron/fins_tcp_protocol.rb +380 -0
- data/lib/ladder_drive/protocol/omron/omron.rb +28 -0
- data/lib/ladder_drive/protocol/omron/omron_device.rb +139 -0
- data/lib/ladder_drive/protocol/protocol.rb +16 -5
- data/lib/ladder_drive/tasks/build.rb +1 -1
- data/lib/ladder_drive/version.rb +1 -1
- data/lib/ladder_drive.rb +3 -0
- data/lib/plc/emulator/emu_plc_server.rb +3 -1
- data/lib/plc/emulator/plc_plugins.rb +20 -13
- data/lib/plc/emulator/plugin_trigger_state.rb +152 -0
- data/plugins/ambient_plugin.rb +155 -0
- data/plugins/config/ambient.yaml.example +34 -0
- data/plugins/config/google_drive.yaml.example +39 -0
- data/plugins/config/ifttt.yaml.example +24 -0
- data/plugins/config/plc_mapper.yaml.example +36 -0
- data/plugins/config/slack.yaml.example +36 -0
- data/plugins/config/trello.yaml.example +41 -0
- data/plugins/ifttt_plugin.rb +4 -27
- data/plugins/plc_mapper_plugin.rb +27 -15
- data/plugins/slack_plugin.rb +14 -3
- data/plugins/trello_plugin.rb +30 -42
- 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
|
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 %>", "/
|
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
|
data/lib/ladder_drive/version.rb
CHANGED
data/lib/ladder_drive.rb
CHANGED
@@ -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)
|
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
|
-
|
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
|
-
|
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
|