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.
- 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
|