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,155 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (c) 2019 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
|
+
<<-DOC
|
24
|
+
Here is a sample configuration.
|
25
|
+
Puts your configuration to config/plugins/ambient.yml
|
26
|
+
|
27
|
+
channels:
|
28
|
+
- channel_id: 12345
|
29
|
+
write_key: your_write_key
|
30
|
+
trigger:
|
31
|
+
device: M100
|
32
|
+
type: raise_and_fall
|
33
|
+
value_type: bool
|
34
|
+
devices:
|
35
|
+
d1:
|
36
|
+
device: D30
|
37
|
+
value_type: value
|
38
|
+
d2:
|
39
|
+
device: D17
|
40
|
+
value_type: value
|
41
|
+
d3:
|
42
|
+
device: D11
|
43
|
+
value_type: value
|
44
|
+
d4:
|
45
|
+
device: D22
|
46
|
+
value_type: value
|
47
|
+
d5:
|
48
|
+
device: D25
|
49
|
+
value_type: value
|
50
|
+
- channel_id: 12345
|
51
|
+
write_key: your_write_key
|
52
|
+
type: interval
|
53
|
+
interval: 60
|
54
|
+
devices:
|
55
|
+
d1:
|
56
|
+
device: D30
|
57
|
+
value_type: value
|
58
|
+
d2:
|
59
|
+
device: D17
|
60
|
+
value_type: value
|
61
|
+
d3:
|
62
|
+
device: D11
|
63
|
+
value_type: value
|
64
|
+
d4:
|
65
|
+
device: D22
|
66
|
+
value_type: value
|
67
|
+
d5:
|
68
|
+
device: D25
|
69
|
+
value_type: value
|
70
|
+
DOC
|
71
|
+
|
72
|
+
require 'ambient_iot'
|
73
|
+
|
74
|
+
|
75
|
+
module LadderDrive
|
76
|
+
module Emulator
|
77
|
+
|
78
|
+
class AmbientPlugin < Plugin
|
79
|
+
|
80
|
+
def initialize plc
|
81
|
+
super #plc
|
82
|
+
return if disabled?
|
83
|
+
|
84
|
+
@values = {}
|
85
|
+
@worker_queue = Queue.new
|
86
|
+
|
87
|
+
setup
|
88
|
+
end
|
89
|
+
|
90
|
+
def run_cycle plc
|
91
|
+
return if disabled?
|
92
|
+
return unless config[:channels]
|
93
|
+
|
94
|
+
config[:channels].each do |channel|
|
95
|
+
next unless channel[:channel_id]
|
96
|
+
next unless channel[:write_key]
|
97
|
+
begin
|
98
|
+
next unless self.triggered?(channel)
|
99
|
+
|
100
|
+
# gether values
|
101
|
+
values = channel[:devices].inject({}) do |h, pair|
|
102
|
+
d = plc.device_by_name pair.last[:device]
|
103
|
+
v = d.send pair.last[:value_type], pair.last[:text_length] || 8
|
104
|
+
h[pair.first] = v
|
105
|
+
h
|
106
|
+
end
|
107
|
+
|
108
|
+
@worker_queue.push channel:channel, values:values
|
109
|
+
rescue => e
|
110
|
+
p e
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
private
|
116
|
+
|
117
|
+
def setup
|
118
|
+
Thread.start {
|
119
|
+
thread_proc
|
120
|
+
}
|
121
|
+
end
|
122
|
+
|
123
|
+
def thread_proc
|
124
|
+
@uploaded_at = nil
|
125
|
+
|
126
|
+
while arg = @worker_queue.pop
|
127
|
+
begin
|
128
|
+
now = Time.now
|
129
|
+
# ignore a request if it's requested in a 5 sec from previous request.
|
130
|
+
next unless @uploaded_at.nil? || (now - @uploaded_at >= 5)
|
131
|
+
|
132
|
+
channel = arg[:channel]
|
133
|
+
client = AmbientIot::Client.new channel[:channel_id], write_key:channel[:write_key]
|
134
|
+
client << arg[:values]
|
135
|
+
client.sync
|
136
|
+
@uploaded_at = now
|
137
|
+
rescue => e
|
138
|
+
p e
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def plugin_ambient_init plc
|
150
|
+
@ambient_plugin = LadderDrive::Emulator::AmbientPlugin.new plc
|
151
|
+
end
|
152
|
+
|
153
|
+
def plugin_ambient_exec plc
|
154
|
+
@ambient_plugin.run_cycle plc
|
155
|
+
end
|
@@ -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
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# This is a sample setting for Trello plugin
|
2
|
+
# Copy this file to trello.yaml or rename.
|
3
|
+
|
4
|
+
# If you want to disable this plugin, uncomment it.
|
5
|
+
#disable: true
|
6
|
+
|
7
|
+
# Connection configuration.
|
8
|
+
consumer_key: <%= ENV['TRELLO_CONSUMER_KEY'] %>
|
9
|
+
consumer_secret: <%= ENV['TRELLO_CONSUMMER_SECRET'] %>
|
10
|
+
oauth_token: <%= ENV['TRELLO_OAUTH_TOKEN'] %>
|
11
|
+
|
12
|
+
events:
|
13
|
+
|
14
|
+
# Set trigger conditions
|
15
|
+
# You can use type raise, fall, raise_and_fall, changed, and interval.
|
16
|
+
- trigger:
|
17
|
+
device: M100
|
18
|
+
type: raise
|
19
|
+
|
20
|
+
# Board name
|
21
|
+
board_name: Floor 1
|
22
|
+
# List name.
|
23
|
+
# If you set it, move the card into this list.
|
24
|
+
list_name: RUNNING
|
25
|
+
# Card name.
|
26
|
+
card_name: Machine A
|
27
|
+
|
28
|
+
# color label
|
29
|
+
color:
|
30
|
+
# Add color label
|
31
|
+
add: green
|
32
|
+
|
33
|
+
- trigger:
|
34
|
+
device: M100
|
35
|
+
type: fall
|
36
|
+
board_name: Floor 1
|
37
|
+
list_name: STOPPING
|
38
|
+
card_name: Machine A
|
39
|
+
color:
|
40
|
+
# Remove color label
|
41
|
+
remove: green
|
data/plugins/ifttt_plugin.rb
CHANGED
@@ -76,36 +76,13 @@ class IftttPlugin < Plugin
|
|
76
76
|
return if disabled?
|
77
77
|
config[:events].each do |event|
|
78
78
|
begin
|
79
|
-
|
80
|
-
case event[:trigger][:type]
|
81
|
-
when "interval"
|
82
|
-
now = Time.now
|
83
|
-
t = @times[event.object_id] || now
|
84
|
-
triggered = t <= now
|
85
|
-
if triggered
|
86
|
-
t += event[:trigger][:interval] || 300
|
87
|
-
@times[event.object_id] = t
|
88
|
-
end
|
89
|
-
else
|
90
|
-
d = plc.device_by_name event[:trigger][:device]
|
91
|
-
v = d.send event[:trigger][:value_type], event[:trigger][:text_length] || 8
|
92
|
-
unless @values[event.object_id] == v
|
93
|
-
@values[event.object_id] = v
|
94
|
-
case event[:trigger][:type]
|
95
|
-
when "raise"
|
96
|
-
triggered = !!v
|
97
|
-
when "fall"
|
98
|
-
triggered = !v
|
99
|
-
else
|
100
|
-
triggered = true
|
101
|
-
end
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
next unless triggered
|
79
|
+
next unless self.triggered?(event)
|
106
80
|
|
81
|
+
v = trigger_state_for(event).value
|
107
82
|
@worker_queue.push event:event[:name], payload:event[:params].dup || {}, value:v
|
108
83
|
rescue => e
|
84
|
+
puts $!
|
85
|
+
puts $@
|
109
86
|
p e
|
110
87
|
end
|
111
88
|
end if config[:events]
|
@@ -63,18 +63,19 @@ class PlcMapperPlugin < Plugin
|
|
63
63
|
|
64
64
|
def run_cycle plc
|
65
65
|
return if disabled?
|
66
|
-
return false unless super
|
66
|
+
#return false unless super
|
67
67
|
@lock.synchronize {
|
68
68
|
# set values from plcs to ladder drive.
|
69
|
-
values_for_reading.each do |d, v|
|
69
|
+
self.values_for_reading.each do |d, v|
|
70
70
|
plc.device_by_name(d).value = v
|
71
71
|
end
|
72
|
-
values_for_reading.clear
|
72
|
+
self.values_for_reading.clear
|
73
73
|
|
74
74
|
# set values from ladder drive to values_for_writing.
|
75
75
|
# then set it to plc at #sync_with_plc
|
76
|
-
values_for_writing.each do |d, v|
|
77
|
-
|
76
|
+
self.values_for_writing.each do |d, v|
|
77
|
+
dev = plc.device_by_name(d)
|
78
|
+
self.values_for_writing[d] = dev.bit_device? ? dev.bool : dev.value
|
78
79
|
end
|
79
80
|
}
|
80
81
|
end
|
@@ -110,7 +111,7 @@ class PlcMapperPlugin < Plugin
|
|
110
111
|
d1 = devs.first
|
111
112
|
d2 = devs.last
|
112
113
|
a << k
|
113
|
-
a << [d1, [d2
|
114
|
+
a << [d1, [d2 - d1 + 1, 1].max]
|
114
115
|
end
|
115
116
|
Hash[*a]
|
116
117
|
end
|
@@ -137,11 +138,14 @@ class PlcMapperPlugin < Plugin
|
|
137
138
|
sync_with_plc protocol, read_mappings, write_mappings
|
138
139
|
next_time += interval
|
139
140
|
end
|
140
|
-
sleep next_time - Time.now
|
141
|
+
sleep [next_time - Time.now, 0].max
|
141
142
|
alerted = false
|
142
|
-
rescue
|
143
|
-
puts "#{config[:description]} is not reachable." unless alerted
|
143
|
+
rescue => e
|
144
|
+
puts "#{config[:description]} is not reachable. #{e}" unless alerted
|
145
|
+
puts $!
|
146
|
+
puts $@
|
144
147
|
alerted = true
|
148
|
+
protocol.close
|
145
149
|
end
|
146
150
|
end
|
147
151
|
end
|
@@ -160,7 +164,7 @@ class PlcMapperPlugin < Plugin
|
|
160
164
|
}
|
161
165
|
end
|
162
166
|
|
163
|
-
# set values
|
167
|
+
# set values from ladder drive (values_for_writing) to plc
|
164
168
|
# values_for_writing was set at run_cycle
|
165
169
|
# but for the first time, it's not known what device it should take.
|
166
170
|
# after running below, devices for need is listed to values_for_writing.
|
@@ -169,10 +173,12 @@ class PlcMapperPlugin < Plugin
|
|
169
173
|
src_d = plc.device_by_name mapping[:ld].first.name
|
170
174
|
values = []
|
171
175
|
lock.synchronize {
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
+
c.times do
|
177
|
+
# It may not get the value for the first time, set zero instead of it.
|
178
|
+
self.values_for_writing[src_d.name] ||= (src_d.bit_device? ? false : 0)
|
179
|
+
values << self.values_for_writing[src_d.name]
|
180
|
+
src_d = src_d.next_device
|
181
|
+
end
|
176
182
|
}
|
177
183
|
protocol[dst_d.name, c] = values
|
178
184
|
end
|
@@ -189,5 +195,11 @@ def plugin_plc_mapper_init plc
|
|
189
195
|
end
|
190
196
|
|
191
197
|
def plugin_plc_mapper_exec plc
|
192
|
-
|
198
|
+
begin
|
199
|
+
@plugin_plc_mapper.run_cycle plc
|
200
|
+
rescue
|
201
|
+
puts $!
|
202
|
+
puts $@
|
203
|
+
plc.close
|
204
|
+
end
|
193
205
|
end
|