ladder_drive 0.6.4 → 0.6.8
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 +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
|