ladder_drive 0.5.2 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +0 -1
- data/Gemfile +6 -2
- data/Gemfile.lock +93 -17
- data/README.md +47 -14
- data/README_jp.md +51 -2
- data/Rakefile +31 -0
- data/doc/jp/raspberrypi.md +183 -0
- data/ladder_drive.gemspec +2 -1
- data/lib/ladder_drive/asm.rb +62 -7
- data/lib/ladder_drive/cli.rb +11 -0
- data/lib/ladder_drive/plc_device.rb +33 -4
- data/lib/ladder_drive/protocol/mitsubishi/fx_device.rb +14 -0
- data/lib/ladder_drive/protocol/mitsubishi/fx_protocol.rb +330 -0
- data/lib/ladder_drive/protocol/mitsubishi/mc_protocol.rb +6 -6
- data/lib/ladder_drive/protocol/mitsubishi/mitsubishi.rb +4 -0
- data/lib/ladder_drive/protocol/protocol.rb +1 -0
- data/lib/ladder_drive/version.rb +1 -1
- data/lib/plc/emulator/emu_device.rb +7 -0
- data/lib/plc/emulator/emu_plc.rb +6 -2
- data/lib/plc/emulator/emu_plc_server.rb +9 -8
- data/lib/plc/emulator/plc_plugins.rb +155 -0
- data/plugins/blank_plugin.rb +11 -0
- data/plugins/google_drive_plugin.rb +191 -0
- data/plugins/ifttt_plugin.rb +134 -0
- data/plugins/plc_mapper_plugin.rb +186 -0
- data/plugins/slack_plugin.rb +155 -0
- data/plugins/trello_plugin.rb +156 -0
- metadata +41 -5
@@ -0,0 +1,155 @@
|
|
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
|
+
<<-DOC
|
24
|
+
Here is a sample configuration.
|
25
|
+
Puts your configuration to config/plugins/slack.yml
|
26
|
+
|
27
|
+
device_comments:
|
28
|
+
- M0: エラー0
|
29
|
+
- M1: エラー1
|
30
|
+
- M2: エラー2
|
31
|
+
- M10: エラー10
|
32
|
+
|
33
|
+
events:
|
34
|
+
- webhook_url: your_web_hook_url
|
35
|
+
trigger:
|
36
|
+
type: raise_and_fall
|
37
|
+
value_type: bool
|
38
|
+
format:
|
39
|
+
raise: __device_comment__ が発生しました。
|
40
|
+
fall: __device_comment__ が解除になりました。
|
41
|
+
devices: M0-M2,M10
|
42
|
+
DOC
|
43
|
+
|
44
|
+
require 'net/https'
|
45
|
+
|
46
|
+
def plugin_slack_init plc
|
47
|
+
@plugin_slack_config = load_plugin_config 'slack'
|
48
|
+
|
49
|
+
@plugin_slack_values = {}
|
50
|
+
@plugin_slack_times = {}
|
51
|
+
@plugin_slack_worker_queue = Queue.new
|
52
|
+
|
53
|
+
# collect comments
|
54
|
+
@plugin_slack_comments = {}
|
55
|
+
@plugin_slack_config[:device_comments].each do |k, v|
|
56
|
+
d = plc.device_by_name(k)
|
57
|
+
@plugin_slack_comments[d.name] = v if d
|
58
|
+
end if @plugin_slack_config[:device_comments]
|
59
|
+
|
60
|
+
Thread.start {
|
61
|
+
plugin_slack_worker_loop
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def plugin_slack_exec plc
|
66
|
+
return if @plugin_slack_config[:disable]
|
67
|
+
|
68
|
+
@plugin_slack_config[:events].each do |event|
|
69
|
+
next unless event[:devices]
|
70
|
+
next unless event[:webhook_url]
|
71
|
+
begin
|
72
|
+
|
73
|
+
# gether values
|
74
|
+
devices = event[:devices].split(",").map{|e| e.split("-")}.map do |devs|
|
75
|
+
devs = devs.map{|d| plc.device_by_name d.strip}
|
76
|
+
d1 = devs.first
|
77
|
+
d2 = devs.last
|
78
|
+
d = d1
|
79
|
+
[d2.number - d1.number + 1, 1].max.times.inject([]){|a, i| a << d1; d1 += 1; a}
|
80
|
+
end.flatten
|
81
|
+
|
82
|
+
interval_triggered = false
|
83
|
+
now = Time.now
|
84
|
+
devices.each do |device|
|
85
|
+
triggered = false
|
86
|
+
v = nil
|
87
|
+
case event[:trigger][:type]
|
88
|
+
when "interval"
|
89
|
+
t = @plugin_slack_times[event.object_id] || now
|
90
|
+
triggered = t <= now
|
91
|
+
if triggered
|
92
|
+
interval_triggered = true
|
93
|
+
t += event[:trigger][:interval] || 300
|
94
|
+
@plugin_slack_times[event.object_id] = t
|
95
|
+
end
|
96
|
+
v = device.send event[:value_type], event[:trigger][:text_length] || 8
|
97
|
+
else
|
98
|
+
v = device.send event[:value_type], event[:text_length] || 8
|
99
|
+
unless @plugin_slack_values[device.name] == v
|
100
|
+
@plugin_slack_values[device.name] = v
|
101
|
+
case event[:trigger][:type]
|
102
|
+
when "raise"
|
103
|
+
triggered = !!v
|
104
|
+
when "fall"
|
105
|
+
triggered = !v
|
106
|
+
else
|
107
|
+
triggered = true
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
next unless triggered || interval_triggered
|
113
|
+
|
114
|
+
@plugin_slack_worker_queue.push event:event,
|
115
|
+
device_name:device.name,
|
116
|
+
value:v,
|
117
|
+
time: now
|
118
|
+
end
|
119
|
+
rescue => e
|
120
|
+
p e
|
121
|
+
end
|
122
|
+
end if @plugin_slack_config[:events]
|
123
|
+
end
|
124
|
+
|
125
|
+
def plugin_slack_worker_loop
|
126
|
+
while arg = @plugin_slack_worker_queue.pop
|
127
|
+
begin
|
128
|
+
event = arg[:event]
|
129
|
+
uri = URI.parse(event[:webhook_url])
|
130
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
131
|
+
http.use_ssl = true
|
132
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
133
|
+
|
134
|
+
req = Net::HTTP::Post.new(uri.path)
|
135
|
+
req["Content-Type"] = "application/json"
|
136
|
+
|
137
|
+
format = event[:format] || "__comment__ occured at __time__"
|
138
|
+
format = arg[:value] ? format[:raise] : format[:fall] unless format.is_a? String
|
139
|
+
|
140
|
+
device_name = arg[:device_name]
|
141
|
+
comment = @plugin_slack_comments[device_name] || device_name
|
142
|
+
value = arg[:value].to_s
|
143
|
+
time = arg[:time].iso8601
|
144
|
+
|
145
|
+
payload = {text:format.gsub(/__device_comment__/, comment).gsub(/__value__/, value).gsub(/__time__/, time).gsub(/__device_name__/, device_name)
|
146
|
+
}
|
147
|
+
req.body = payload.to_json
|
148
|
+
|
149
|
+
http.request(req)
|
150
|
+
rescue => e
|
151
|
+
# TODO: Resend if it fails.
|
152
|
+
p e
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
@@ -0,0 +1,156 @@
|
|
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
|
+
<<-DOC
|
24
|
+
Here is a sample configuration.
|
25
|
+
Puts your configuration to config/plugins/trello.yml
|
26
|
+
|
27
|
+
consummer_key: your_consumer_key
|
28
|
+
consumer_secret: your_consumer_secret
|
29
|
+
oauth_token: your_oauth_token
|
30
|
+
|
31
|
+
events:
|
32
|
+
- trigger:
|
33
|
+
device: D0
|
34
|
+
type: changed
|
35
|
+
value_type: text
|
36
|
+
text_length: 8
|
37
|
+
board_name: 工程モニター
|
38
|
+
list_name: 工程1
|
39
|
+
title: __value__
|
40
|
+
- trigger:
|
41
|
+
device: D10
|
42
|
+
type: changed
|
43
|
+
value_type: text
|
44
|
+
text_length: 8
|
45
|
+
board_name: 工程モニター
|
46
|
+
list_name: 工程2
|
47
|
+
title: __value__
|
48
|
+
DOC
|
49
|
+
|
50
|
+
require 'net/https'
|
51
|
+
require 'trello'
|
52
|
+
|
53
|
+
# @see https://qiita.com/tbpgr/items/60fc13aca8afd153e37b
|
54
|
+
|
55
|
+
=begin
|
56
|
+
Dotenv.load
|
57
|
+
|
58
|
+
b = Trello::Board.all.find{|b| b.name == "工程モニター"}
|
59
|
+
pp b.lists.map{|l| {id:l.id, name:l.name, cards:l.cards.map{|c| {id:c.id, name:c.name}}}}
|
60
|
+
=end
|
61
|
+
|
62
|
+
|
63
|
+
def plugin_trello_init plc
|
64
|
+
@plugin_trello_config = load_plugin_config 'trello'
|
65
|
+
|
66
|
+
@plugin_trello_values = {}
|
67
|
+
@plugin_trello_times = {}
|
68
|
+
@plugin_trello_worker_queue = Queue.new
|
69
|
+
|
70
|
+
@plugin_trello_configured = Trello.configure do |config|
|
71
|
+
config.consumer_key = @plugin_trello_config[:consumer_key]
|
72
|
+
config.consumer_secret = @plugin_trello_config[:consumer_secret]
|
73
|
+
config.oauth_token = @plugin_trello_config[:oauth_token]
|
74
|
+
end
|
75
|
+
|
76
|
+
Thread.start {
|
77
|
+
plugin_trello_worker_loop
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
def plugin_trello_exec plc
|
82
|
+
return if @plugin_trello_config[:disable]
|
83
|
+
# return unless @plugin_trello_configured
|
84
|
+
|
85
|
+
@plugin_trello_config[:events].each do |event|
|
86
|
+
begin
|
87
|
+
|
88
|
+
triggered = false
|
89
|
+
now = Time.now
|
90
|
+
device = nil
|
91
|
+
|
92
|
+
case event[:trigger][:type]
|
93
|
+
when "interval"
|
94
|
+
t = @plugin_trello_times[event.object_id] || now
|
95
|
+
triggered = t <= now
|
96
|
+
if triggered
|
97
|
+
interval_triggered = true
|
98
|
+
t += event[:trigger][:interval] || 300
|
99
|
+
@plugin_trello_times[event.object_id] = t
|
100
|
+
end
|
101
|
+
else
|
102
|
+
device = plc.device_by_name event[:trigger][:device]
|
103
|
+
v = device.send event[:trigger][:value_type], event[:trigger][:text_length] || 8
|
104
|
+
unless @plugin_trello_values[device.name] == v
|
105
|
+
@plugin_trello_values[device.name] = v
|
106
|
+
case event[:trigger][:type]
|
107
|
+
when "raise"
|
108
|
+
triggered = !!v
|
109
|
+
when "fall"
|
110
|
+
triggered = !v
|
111
|
+
else
|
112
|
+
triggered = true
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
next unless triggered
|
118
|
+
|
119
|
+
@plugin_trello_worker_queue.push event:event, device_name:device.name, value:v, time: now
|
120
|
+
|
121
|
+
rescue => e
|
122
|
+
p e
|
123
|
+
end
|
124
|
+
end if @plugin_trello_config[:events]
|
125
|
+
end
|
126
|
+
|
127
|
+
def plugin_trello_worker_loop
|
128
|
+
while arg = @plugin_trello_worker_queue.pop
|
129
|
+
begin
|
130
|
+
event = arg[:event]
|
131
|
+
|
132
|
+
board = Trello::Board.all.find{|b| b.name == event[:board_name]}
|
133
|
+
next unless board
|
134
|
+
|
135
|
+
card_name = event[:card_name].dup || ""
|
136
|
+
card_name.gsub!(/__value__/, arg[:value] || "")
|
137
|
+
next if (card_name || "").empty?
|
138
|
+
|
139
|
+
list_name = event[:list_name]
|
140
|
+
next unless list_name
|
141
|
+
list = board.lists.find{|l| l.name == list_name}
|
142
|
+
next unless list
|
143
|
+
|
144
|
+
card = board.lists.map{|l| l.cards.map{|c| c}}.flatten.find{|c| c.name == card_name}
|
145
|
+
if card
|
146
|
+
card.move_to_list list
|
147
|
+
else
|
148
|
+
card = Trello::Card.create name:card_name, list_id:list.id
|
149
|
+
end
|
150
|
+
|
151
|
+
rescue => e
|
152
|
+
# TODO: Resend if it fails.
|
153
|
+
p e
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ladder_drive
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Katsuyoshi Ito
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-11-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -44,20 +44,46 @@ dependencies:
|
|
44
44
|
- - ">="
|
45
45
|
- !ruby/object:Gem::Version
|
46
46
|
version: 4.2.7
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: ffi
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 1.9.24
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 1.9.24
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 1.9.24
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 1.9.24
|
47
67
|
- !ruby/object:Gem::Dependency
|
48
68
|
name: pi_piper
|
49
69
|
requirement: !ruby/object:Gem::Requirement
|
50
70
|
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '2.0'
|
51
74
|
- - ">="
|
52
75
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
76
|
+
version: 2.0.0
|
54
77
|
type: :runtime
|
55
78
|
prerelease: false
|
56
79
|
version_requirements: !ruby/object:Gem::Requirement
|
57
80
|
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '2.0'
|
58
84
|
- - ">="
|
59
85
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
86
|
+
version: 2.0.0
|
61
87
|
- !ruby/object:Gem::Dependency
|
62
88
|
name: bundler
|
63
89
|
requirement: !ruby/object:Gem::Requirement
|
@@ -105,6 +131,7 @@ files:
|
|
105
131
|
- Rakefile
|
106
132
|
- bin/console
|
107
133
|
- bin/setup
|
134
|
+
- doc/jp/raspberrypi.md
|
108
135
|
- exe/ladder_drive
|
109
136
|
- ladder_drive.gemspec
|
110
137
|
- lib/ladder_drive.rb
|
@@ -122,6 +149,8 @@ files:
|
|
122
149
|
- lib/ladder_drive/protocol/keyence/keyence.rb
|
123
150
|
- lib/ladder_drive/protocol/keyence/kv_device.rb
|
124
151
|
- lib/ladder_drive/protocol/keyence/kv_protocol.rb
|
152
|
+
- lib/ladder_drive/protocol/mitsubishi/fx_device.rb
|
153
|
+
- lib/ladder_drive/protocol/mitsubishi/fx_protocol.rb
|
125
154
|
- lib/ladder_drive/protocol/mitsubishi/mc_protocol.rb
|
126
155
|
- lib/ladder_drive/protocol/mitsubishi/mitsubishi.rb
|
127
156
|
- lib/ladder_drive/protocol/mitsubishi/qdevice.rb
|
@@ -134,6 +163,7 @@ files:
|
|
134
163
|
- lib/plc/emulator/emu_plc.rb
|
135
164
|
- lib/plc/emulator/emu_plc_server.rb
|
136
165
|
- lib/plc/emulator/emulator.rb
|
166
|
+
- lib/plc/emulator/plc_plugins.rb
|
137
167
|
- lib/plc/keyence/kv/kv-5000/DocumentWindowInfo.xml
|
138
168
|
- lib/plc/keyence/kv/kv-5000/KvsMon.ini
|
139
169
|
- lib/plc/keyence/kv/kv-5000/LadderDrive.mod
|
@@ -160,6 +190,12 @@ files:
|
|
160
190
|
- lib/plc/raspberrypi/raspberrypi.rb
|
161
191
|
- lib/plc/raspberrypi/raspberrypi_plc.rb
|
162
192
|
- lib/plc/raspberrypi/raspberrypi_plc_server.rb
|
193
|
+
- plugins/blank_plugin.rb
|
194
|
+
- plugins/google_drive_plugin.rb
|
195
|
+
- plugins/ifttt_plugin.rb
|
196
|
+
- plugins/plc_mapper_plugin.rb
|
197
|
+
- plugins/slack_plugin.rb
|
198
|
+
- plugins/trello_plugin.rb
|
163
199
|
- sample/ladder_drive/sample1.esc
|
164
200
|
- sample/ladder_drive/sample2.esc
|
165
201
|
- sample/ladder_drive/sample2.png
|
@@ -189,7 +225,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
189
225
|
version: '0'
|
190
226
|
requirements: []
|
191
227
|
rubyforge_project:
|
192
|
-
rubygems_version: 2.
|
228
|
+
rubygems_version: 2.7.7
|
193
229
|
signing_key:
|
194
230
|
specification_version: 4
|
195
231
|
summary: The ladder_drive is a simple abstract ladder for PLC (Programmable Logic
|