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