ladder_drive 0.6.4 → 0.6.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/Gemfile +1 -1
- data/Gemfile.lock +95 -62
- data/README.md +1 -0
- data/README_jp.md +2 -2
- data/ladder_drive.gemspec +12 -9
- data/lib/ladder_drive/cli.rb +16 -4
- data/lib/ladder_drive/config.rb +3 -1
- data/lib/ladder_drive/protocol/keyence/kv_device.rb +1 -1
- data/lib/ladder_drive/protocol/keyence/kv_protocol.rb +1 -1
- data/lib/ladder_drive/protocol/mitsubishi/fx_protocol.rb +0 -1
- data/lib/ladder_drive/protocol/mitsubishi/mc_protocol.rb +1 -1
- data/lib/ladder_drive/protocol/omron/fins_tcp_protocol.rb +3 -1
- data/lib/ladder_drive/protocol/protocol.rb +2 -0
- 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 +1 -33
- 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/slack_plugin.rb +14 -3
- data/plugins/trello_plugin.rb +25 -42
- metadata +66 -14
@@ -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
|
data/plugins/ambient_plugin.rb
CHANGED
@@ -82,7 +82,6 @@ class AmbientPlugin < Plugin
|
|
82
82
|
return if disabled?
|
83
83
|
|
84
84
|
@values = {}
|
85
|
-
@times = {}
|
86
85
|
@worker_queue = Queue.new
|
87
86
|
|
88
87
|
setup
|
@@ -93,41 +92,10 @@ class AmbientPlugin < Plugin
|
|
93
92
|
return unless config[:channels]
|
94
93
|
|
95
94
|
config[:channels].each do |channel|
|
96
|
-
channel = channel
|
97
95
|
next unless channel[:channel_id]
|
98
96
|
next unless channel[:write_key]
|
99
97
|
begin
|
100
|
-
|
101
|
-
interval_triggered = false
|
102
|
-
now = Time.now
|
103
|
-
triggered = false
|
104
|
-
v = nil
|
105
|
-
case channel[:trigger][:type]
|
106
|
-
when "interval"
|
107
|
-
t = @times[channel.object_id] || now
|
108
|
-
triggered = t <= now
|
109
|
-
if triggered
|
110
|
-
interval_triggered = true
|
111
|
-
t += channel[:trigger][:interval] || 300
|
112
|
-
@times[channel.object_id] = t
|
113
|
-
end
|
114
|
-
else
|
115
|
-
device = plc.device_by_name channel[:trigger][:device]
|
116
|
-
v = device.send channel[:trigger][:value_type]
|
117
|
-
unless @values[device.name] == v
|
118
|
-
@values[device.name] = v
|
119
|
-
case channel[:trigger][:type]
|
120
|
-
when "raise"
|
121
|
-
triggered = !!v
|
122
|
-
when "fall"
|
123
|
-
triggered = !v
|
124
|
-
else
|
125
|
-
triggered = true
|
126
|
-
end
|
127
|
-
end
|
128
|
-
end
|
129
|
-
|
130
|
-
next unless triggered || interval_triggered
|
98
|
+
next unless self.triggered?(channel)
|
131
99
|
|
132
100
|
# gether values
|
133
101
|
values = channel[:devices].inject({}) do |h, pair|
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# This is a sample setting for ambient plugin
|
2
|
+
# Copy this file to ambient.yaml or rename.
|
3
|
+
|
4
|
+
# If you want to disable this plugin, uncomment it.
|
5
|
+
#disable: true
|
6
|
+
|
7
|
+
# Configure ambient channels
|
8
|
+
# Set the Ambient channel, which is to send data you want to.
|
9
|
+
channels:
|
10
|
+
|
11
|
+
# set ambient configuration.
|
12
|
+
# We recommend setting write_key from an environment variable.
|
13
|
+
- channel_id: 12824
|
14
|
+
write_key: <%= ENV['AMBIENT_WRITE_KEY'] %>
|
15
|
+
|
16
|
+
# Set trigger conditions
|
17
|
+
# You can use type raise, fall, raise_and_fall, changed, and interval.
|
18
|
+
triggers:
|
19
|
+
- type: raise_and_fall
|
20
|
+
device: M100
|
21
|
+
- type: interval
|
22
|
+
interval: 60
|
23
|
+
|
24
|
+
# Set data to send, if triggered.
|
25
|
+
devices:
|
26
|
+
d1:
|
27
|
+
device: D10
|
28
|
+
value_type: value
|
29
|
+
d2:
|
30
|
+
device: D11
|
31
|
+
value_type: value
|
32
|
+
d3:
|
33
|
+
device: D12
|
34
|
+
value_type: value
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# This is a sample setting for google drive plugin
|
2
|
+
# Copy this file to google_drive.yaml or rename.
|
3
|
+
|
4
|
+
# If you want to disable this plugin, uncomment it.
|
5
|
+
#disable: true
|
6
|
+
|
7
|
+
# Api configuration.
|
8
|
+
client_id: <%= ENV['GOOGLE_CLIENT_ID'] %>
|
9
|
+
client_secret: <%= ENV['GOOGLE_CLIENT_SECRET'] %>
|
10
|
+
access_token: <%= ENV['GOOGLE_ACCESS_TOKEN'] %>
|
11
|
+
refresh_token: <%= ENV['GOOGLE_REFRESH_TOKEN'] %>
|
12
|
+
|
13
|
+
# Logging setting
|
14
|
+
loggings:
|
15
|
+
- name: temperature
|
16
|
+
|
17
|
+
# Set trigger conditions
|
18
|
+
# You can use type raise, fall, raise_and_fall, changed, and interval.
|
19
|
+
trigger:
|
20
|
+
device: M0
|
21
|
+
type: raise_and_fall
|
22
|
+
value_type: bool
|
23
|
+
|
24
|
+
# Column header
|
25
|
+
columns: D0,D1,D2,D3,D4,D5,D6,D7,D8,D9,D20,M0
|
26
|
+
|
27
|
+
# Devices for logging
|
28
|
+
devices:
|
29
|
+
- device: D0-D9
|
30
|
+
type: value
|
31
|
+
- device: D20
|
32
|
+
type: value
|
33
|
+
- device: M0
|
34
|
+
type: bool
|
35
|
+
|
36
|
+
# Spreadsheet to access
|
37
|
+
spread_sheet:
|
38
|
+
spread_sheet_key: <%= ENV['GOOGLE_SPREADSHEET_KEY'] %>
|
39
|
+
sheet_no: 0
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# This is a sample setting for ifttt plugin
|
2
|
+
# Copy this file to ifttt.yaml or rename.
|
3
|
+
|
4
|
+
# If you want to disable this plugin, uncomment it.
|
5
|
+
#disable: true
|
6
|
+
|
7
|
+
# Set webhook key of Webhooks service for IFTTT.
|
8
|
+
# To see your webhook key, to open https://ifttt.com/maker_webhook
|
9
|
+
# and click the Documentation link.
|
10
|
+
# We recommend setting web_hook_key from an environment variable.
|
11
|
+
web_hook_key: <%= ENV['IFTTT_WEBHOOK_KEY'] %>
|
12
|
+
|
13
|
+
# Set trigger event of webhook.
|
14
|
+
# Set event name to name attribute.
|
15
|
+
# Pass values to the params to display information.
|
16
|
+
events:
|
17
|
+
- name: rubyworldconference2019
|
18
|
+
trigger:
|
19
|
+
type: raise
|
20
|
+
device: M100
|
21
|
+
params:
|
22
|
+
value1: Abnormal temperature
|
23
|
+
value2: Something additional information.
|
24
|
+
value3: and more.
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# This is a sample setting for Plc mapper plugin
|
2
|
+
# Copy this file to plc_mapper.yaml or rename.
|
3
|
+
|
4
|
+
# If you want to disable this plugin, uncomment it.
|
5
|
+
#disable: true
|
6
|
+
|
7
|
+
# This is a sample setting for Plc mapper plugin
|
8
|
+
plcs:
|
9
|
+
- description: Machine A-123
|
10
|
+
|
11
|
+
# Set connection conditions between PLC
|
12
|
+
protocol: mc_protocol
|
13
|
+
host: 192.168.1.10
|
14
|
+
port: 5010
|
15
|
+
interval: 10
|
16
|
+
|
17
|
+
# Set address mapping.
|
18
|
+
mapping:
|
19
|
+
|
20
|
+
# Read from PLC to set Ladder drive.
|
21
|
+
# plc: PLC address area
|
22
|
+
# ld: Ladder drive area
|
23
|
+
read:
|
24
|
+
- plc: M1000-M1099
|
25
|
+
ld: M0
|
26
|
+
- plc: D1000-D1099
|
27
|
+
ld: D0
|
28
|
+
# Read from Ladder drive to set PLC.
|
29
|
+
# plc: PLC address area
|
30
|
+
# ld: Ladder drive area
|
31
|
+
write:
|
32
|
+
# LadderDrive address: PLC address
|
33
|
+
- plc: M100-M199
|
34
|
+
ld: M1100
|
35
|
+
- plc: D100-D199
|
36
|
+
ld: D1100
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# This is a sample setting for Slack plugin
|
2
|
+
# Copy this file to slack.yaml or rename.
|
3
|
+
|
4
|
+
# If you want to disable this plugin, uncomment it.
|
5
|
+
#disable: true
|
6
|
+
|
7
|
+
# Define comment for a device
|
8
|
+
device_comments:
|
9
|
+
M210: Full error
|
10
|
+
D10: Running
|
11
|
+
|
12
|
+
# Event setting
|
13
|
+
events:
|
14
|
+
|
15
|
+
# Set webhook url for IFTTT.
|
16
|
+
# We recommend setting web_hook_key from an environment variable.
|
17
|
+
- webhook_url: <%= ENV['SLACK_WEBHOOK_NOTIFICATION_URL'] %>
|
18
|
+
|
19
|
+
# Set trigger conditions
|
20
|
+
# You can use type raise, fall, raise_and_fall, changed, and interval.
|
21
|
+
trigger:
|
22
|
+
type: raise_and_fall
|
23
|
+
|
24
|
+
# String format to send Slack.
|
25
|
+
format:
|
26
|
+
raise: __device_comment__ has occurred at __time__ .
|
27
|
+
fall: __device_comment__ was reset at __time__ .
|
28
|
+
devices: M210
|
29
|
+
|
30
|
+
- webhook_url: <%= ENV['SLACK_WEBHOOK_URL'] %>
|
31
|
+
trigger:
|
32
|
+
type: raise_and_fall
|
33
|
+
format:
|
34
|
+
raise: Start __device_comment__ at __time__ .
|
35
|
+
fall: Stop __device_comment__ at __time__ .
|
36
|
+
devices: M100
|