ladder_drive 0.6.0 → 0.6.1
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 +4 -4
- data/Gemfile.lock +5 -5
- data/README_jp.md +0 -6
- data/ladder_drive.gemspec +1 -0
- data/lib/ladder_drive/cli.rb +2 -2
- data/lib/ladder_drive/console.rb +0 -1
- data/lib/ladder_drive/plc_device.rb +2 -2
- data/lib/ladder_drive/protocol/keyence/kv_protocol.rb +3 -1
- data/lib/ladder_drive/protocol/mitsubishi/fx_protocol.rb +1 -0
- data/lib/ladder_drive/protocol/mitsubishi/mc_protocol.rb +1 -0
- data/lib/ladder_drive/protocol/protocol.rb +5 -1
- data/lib/ladder_drive/uploader.rb +0 -1
- data/lib/ladder_drive/version.rb +1 -1
- data/lib/plc/emulator/emu_device.rb +1 -1
- data/lib/plc/emulator/emu_plc.rb +3 -2
- data/lib/plc/emulator/plc_plugins.rb +2 -3
- data/plugins/google_drive_plugin.rb +129 -102
- data/plugins/ifttt_plugin.rb +97 -64
- data/plugins/plc_mapper_plugin.rb +13 -6
- data/plugins/slack_plugin.rb +125 -99
- data/plugins/trello_plugin.rb +96 -79
- metadata +25 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 89e93e6ad6630128bf1a0996dd9de4c399e246acbb1c92615eb92fc83f0ec206
|
4
|
+
data.tar.gz: 5cb779d24babb0d23284276765c79e6edb27bbf1901d946eceb1a88a7b1cca99
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ea1e43769ad3deb7845a208c501906b2db81f04c9223ce7152f35d663ff5529cb44f2f8ebd0920f37443cb963715280bf8a69ac7efab3319504d218adc721b23
|
7
|
+
data.tar.gz: 59dae9cd3d5080ee2d7ce616951d05e1cf387c2e7920b8f3a2a2ce0411492ed662ae5da91517c69f99c9969fd3f82256049839e89f135bc5de99a89e59647091
|
data/Gemfile.lock
CHANGED
@@ -4,7 +4,7 @@ PATH
|
|
4
4
|
ladder_drive (0.6.0)
|
5
5
|
activesupport (~> 4.2, >= 4.2.7)
|
6
6
|
ffi (~> 1.9.24, >= 1.9.24)
|
7
|
-
pi_piper (>= 2.0.0)
|
7
|
+
pi_piper (~> 2.0, >= 2.0.0)
|
8
8
|
thor (~> 0)
|
9
9
|
|
10
10
|
GEM
|
@@ -110,16 +110,16 @@ PLATFORMS
|
|
110
110
|
ruby
|
111
111
|
|
112
112
|
DEPENDENCIES
|
113
|
-
activesupport
|
113
|
+
activesupport (~> 4.2, >= 4.2.7)
|
114
114
|
bundler (~> 1.11)
|
115
|
-
ffi
|
115
|
+
ffi (~> 1.9.24)
|
116
116
|
google_drive
|
117
117
|
ladder_drive!
|
118
|
-
pi_piper
|
118
|
+
pi_piper (>= 2.0.0)
|
119
119
|
rake (~> 10.0)
|
120
120
|
ruby-trello
|
121
121
|
serialport
|
122
122
|
test-unit
|
123
123
|
|
124
124
|
BUNDLED WITH
|
125
|
-
1.
|
125
|
+
1.17.2
|
data/README_jp.md
CHANGED
@@ -184,12 +184,6 @@ OUT M1
|
|
184
184
|
<!-- [](https://youtu.be/qGbicGLB7Gs) -->
|
185
185
|
|
186
186
|
|
187
|
-
```warning: constant ::Fixnum is deprecated``` の表示が出る場合は次の様にしてみてください。
|
188
|
-
|
189
|
-
```sh
|
190
|
-
$ RUBYOPT="-W0" rake
|
191
|
-
```
|
192
|
-
|
193
187
|
## Raspberry Pi
|
194
188
|
|
195
189
|
Raspberry Pi上で動作させることもできます。
|
data/ladder_drive.gemspec
CHANGED
@@ -18,6 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.add_runtime_dependency 'activesupport', '~> 4.2', '>= 4.2.7'
|
19
19
|
spec.add_runtime_dependency 'ffi', '~> 1.9.24', '>= 1.9.24'
|
20
20
|
spec.add_runtime_dependency 'pi_piper', '~> 2.0', '>= 2.0.0'
|
21
|
+
spec.add_runtime_dependency 'serialport'
|
21
22
|
|
22
23
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
23
24
|
spec.bindir = "exe"
|
data/lib/ladder_drive/cli.rb
CHANGED
@@ -33,7 +33,7 @@ module LadderDrive
|
|
33
33
|
def create(name)
|
34
34
|
if File.exist? name
|
35
35
|
puts "ERROR: #{name} already exists."
|
36
|
-
exit
|
36
|
+
exit(-1)
|
37
37
|
end
|
38
38
|
|
39
39
|
# copy from template file
|
@@ -58,7 +58,7 @@ module LadderDrive
|
|
58
58
|
path = File.join(plugins_path, "#{name}_plugin.rb")
|
59
59
|
if File.exist? path
|
60
60
|
mkdir_p "plugins"
|
61
|
-
cp path, "plugins/#{name}
|
61
|
+
cp path, "plugins/#{name}_plugin.rb"
|
62
62
|
end
|
63
63
|
end
|
64
64
|
|
data/lib/ladder_drive/console.rb
CHANGED
@@ -54,7 +54,7 @@ module LadderDrive
|
|
54
54
|
@suffix = nil
|
55
55
|
@value = 0
|
56
56
|
case a
|
57
|
-
when
|
57
|
+
when Integer
|
58
58
|
@suffix = ESC_SUFFIXES[a]
|
59
59
|
@number = b
|
60
60
|
when String, Symbol
|
@@ -127,7 +127,7 @@ module LadderDrive
|
|
127
127
|
|
128
128
|
def bool
|
129
129
|
case @value
|
130
|
-
when
|
130
|
+
when Integer
|
131
131
|
@value != 0
|
132
132
|
else
|
133
133
|
!!@value
|
@@ -29,6 +29,7 @@ module Keyence
|
|
29
29
|
|
30
30
|
def initialize options={}
|
31
31
|
super
|
32
|
+
@socket = nil
|
32
33
|
@host = options[:host] || "192.168.0.10"
|
33
34
|
@port = options[:port] || 8501
|
34
35
|
prepare_device_map
|
@@ -148,6 +149,7 @@ module Keyence
|
|
148
149
|
|
149
150
|
def available_bits_range suffix=nil
|
150
151
|
case suffix
|
152
|
+
# FIXME: duplicated
|
151
153
|
when "TM"
|
152
154
|
1..512
|
153
155
|
when "TM"
|
@@ -167,13 +169,13 @@ module Keyence
|
|
167
169
|
|
168
170
|
def available_words_range suffix=nil
|
169
171
|
case suffix
|
172
|
+
# FIXME: duplicated
|
170
173
|
when "TM"
|
171
174
|
1..256
|
172
175
|
when "TM"
|
173
176
|
1..12
|
174
177
|
when "T", "TC", "TS", "C", "CC", "CS"
|
175
178
|
1..120
|
176
|
-
1..120
|
177
179
|
when "CTH"
|
178
180
|
1..2
|
179
181
|
when "CTC"
|
@@ -29,13 +29,17 @@ module Protocol
|
|
29
29
|
|
30
30
|
class Protocol
|
31
31
|
|
32
|
-
attr_accessor :host, :port
|
32
|
+
attr_accessor :host, :port
|
33
33
|
|
34
34
|
def initialize options={}
|
35
35
|
@logger = Logger.new(STDOUT)
|
36
36
|
self.log_level = options[:log_level] || :info
|
37
37
|
end
|
38
38
|
|
39
|
+
def log_level
|
40
|
+
@log_level
|
41
|
+
end
|
42
|
+
|
39
43
|
def log_level= level
|
40
44
|
@log_level = level.is_a?(String) ? level.to_sym : level
|
41
45
|
case @log_level
|
data/lib/ladder_drive/version.rb
CHANGED
data/lib/plc/emulator/emu_plc.rb
CHANGED
@@ -63,6 +63,7 @@ module Emulator
|
|
63
63
|
end
|
64
64
|
@lock = Mutex.new
|
65
65
|
@config = config
|
66
|
+
@pending_save = nil
|
66
67
|
reset
|
67
68
|
load_plugins
|
68
69
|
end
|
@@ -341,12 +342,12 @@ module Emulator
|
|
341
342
|
(SIZE_OF_BIT_STACK - 1).downto 0 do |i|
|
342
343
|
values << stack_device(i).word
|
343
344
|
end
|
344
|
-
|
345
|
+
value = values.pop
|
345
346
|
values.each_with_index do |v, i|
|
346
347
|
stack_device(i).word = v
|
347
348
|
end
|
348
349
|
stack_count_device.word -= 1
|
349
|
-
|
350
|
+
value
|
350
351
|
end
|
351
352
|
# ----------------
|
352
353
|
|
@@ -85,11 +85,10 @@ class Plugin
|
|
85
85
|
class << self
|
86
86
|
|
87
87
|
def devices_with_plc_from_str plc, dev_str
|
88
|
-
|
88
|
+
dev_str.split(",").map{|e| e.split("-")}.map do |devs|
|
89
89
|
devs = devs.map{|d| plc.device_by_name d.strip}
|
90
90
|
d1 = devs.first
|
91
91
|
d2 = devs.last
|
92
|
-
d = d1
|
93
92
|
[d2.number - d1.number + 1, 1].max.times.inject([]){|a, i| a << d1; d1 += 1; a}
|
94
93
|
end.flatten
|
95
94
|
end
|
@@ -118,7 +117,7 @@ class Plugin
|
|
118
117
|
end
|
119
118
|
|
120
119
|
def disabled?
|
121
|
-
config[:disable]
|
120
|
+
config.empty? || config[:disable]
|
122
121
|
end
|
123
122
|
|
124
123
|
def run_cycle plc
|
@@ -65,127 +65,154 @@ DOC
|
|
65
65
|
require 'net/https'
|
66
66
|
require 'google_drive'
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
return if @plugin_google_drive_config[:disable]
|
71
|
-
|
72
|
-
@plugin_google_drive_values = {}
|
73
|
-
@plugin_google_drive_times = {}
|
74
|
-
@plugin_google_drive_worker_queue = Queue.new
|
75
|
-
|
76
|
-
begin
|
77
|
-
# generate config file for google drive session
|
78
|
-
tmp_dir = File.expand_path "tmp"
|
79
|
-
session_path = File.join(tmp_dir, "google_drive_session.json")
|
80
|
-
unless File.exist? session_path
|
81
|
-
mkdir_p tmp_dir
|
82
|
-
conf =
|
83
|
-
[:client_id, :client_secret, :refresh_token].inject({}) do |h, key|
|
84
|
-
v = @plugin_google_drive_config[key]
|
85
|
-
h[key] = v if v
|
86
|
-
h
|
87
|
-
end
|
88
|
-
File.write session_path, JSON.generate(conf)
|
89
|
-
end
|
68
|
+
module LadderDrive
|
69
|
+
module Emulator
|
90
70
|
|
91
|
-
|
92
|
-
@plugin_google_drive_session = GoogleDrive::Session.from_config(session_path)
|
71
|
+
class GoogleDrivePlugin < Plugin
|
93
72
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
}
|
98
|
-
rescue => e
|
99
|
-
p e
|
100
|
-
@plugin_google_drive_session = nil
|
101
|
-
end
|
102
|
-
end
|
73
|
+
def initialize plc
|
74
|
+
super #plc
|
75
|
+
return if disabled?
|
103
76
|
|
104
|
-
|
105
|
-
|
106
|
-
|
77
|
+
@values = {}
|
78
|
+
@times = {}
|
79
|
+
@worker_queue = Queue.new
|
107
80
|
|
108
|
-
@plugin_google_drive_config[:loggings].each do |logging|
|
109
81
|
begin
|
110
|
-
#
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
end
|
121
|
-
else
|
122
|
-
d = plc.device_by_name logging[:trigger][:device]
|
123
|
-
v = d.send logging[:trigger][:value_type], logging[:trigger][:text_length] || 8
|
124
|
-
unless @plugin_google_drive_values[logging.object_id] == v
|
125
|
-
@plugin_google_drive_values[logging.object_id] = v
|
126
|
-
case logging[:trigger][:type]
|
127
|
-
when "raise"
|
128
|
-
triggered = !!v
|
129
|
-
when "fall"
|
130
|
-
triggered = !v
|
131
|
-
else
|
132
|
-
triggered = true
|
82
|
+
# generate config file for google drive session
|
83
|
+
tmp_dir = File.expand_path "tmp"
|
84
|
+
session_path = File.join(tmp_dir, "google_drive_session.json")
|
85
|
+
unless File.exist? session_path
|
86
|
+
mkdir_p tmp_dir
|
87
|
+
conf =
|
88
|
+
[:client_id, :client_secret, :access_token, :refresh_token].inject({}) do |h, key|
|
89
|
+
v = config[key]
|
90
|
+
h[key] = v if v
|
91
|
+
h
|
133
92
|
end
|
134
|
-
|
93
|
+
File.write session_path, JSON.generate(conf)
|
135
94
|
end
|
136
95
|
|
137
|
-
|
96
|
+
# create google drive session
|
97
|
+
@session = GoogleDrive::Session.from_config(session_path)
|
138
98
|
|
139
|
-
#
|
140
|
-
|
141
|
-
d1, d2 = config[:device].split("-").map{|d| plc.device_by_name d}
|
142
|
-
devices = [d1]
|
143
|
-
if d2
|
144
|
-
d = d1 + 1
|
145
|
-
devices += [d2.number - d1.number, 0].max.times.inject([]){|a, i| a << d; d += 1; a}
|
146
|
-
end
|
147
|
-
devices.map{|d| d.send config[:type], config[:length] || 8}
|
148
|
-
end.flatten
|
149
|
-
@plugin_google_drive_worker_queue.push logging:logging, values:values, time:Time.now
|
99
|
+
# start worker thread
|
100
|
+
setup if @session
|
150
101
|
rescue => e
|
151
102
|
p e
|
103
|
+
@session = nil
|
104
|
+
exit(1)
|
152
105
|
end
|
153
|
-
end if @plugin_google_drive_config[:loggings]
|
154
|
-
end
|
155
106
|
|
156
|
-
|
157
|
-
while arg = @plugin_google_drive_worker_queue.pop
|
158
|
-
begin
|
159
|
-
logging = arg[:logging]
|
160
|
-
spread_sheet = @plugin_google_drive_session.spreadsheet_by_key(logging[:spread_sheet][:spread_sheet_key])
|
107
|
+
end
|
161
108
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
109
|
+
def run_cycle plc
|
110
|
+
return if disabled?
|
111
|
+
return unless @session
|
112
|
+
|
113
|
+
config[:loggings].each do |logging|
|
114
|
+
begin
|
115
|
+
# check triggered or not
|
116
|
+
triggered = false
|
117
|
+
case logging[:trigger][:type]
|
118
|
+
when "interval"
|
119
|
+
now = Time.now
|
120
|
+
t = @times[logging.object_id] || now
|
121
|
+
triggered = t <= now
|
122
|
+
if triggered
|
123
|
+
t += logging[:trigger][:interval] || 300
|
124
|
+
@times[logging.object_id] = t
|
125
|
+
end
|
166
126
|
else
|
167
|
-
|
127
|
+
d = plc.device_by_name logging[:trigger][:device]
|
128
|
+
v = d.send logging[:trigger][:value_type], logging[:trigger][:text_length] || 8
|
129
|
+
unless @values[logging.object_id] == v
|
130
|
+
@values[logging.object_id] = v
|
131
|
+
case logging[:trigger][:type]
|
132
|
+
when "raise"
|
133
|
+
triggered = !!v
|
134
|
+
when "fall"
|
135
|
+
triggered = !v
|
136
|
+
else
|
137
|
+
triggered = true
|
138
|
+
end
|
139
|
+
end
|
168
140
|
end
|
141
|
+
|
142
|
+
next unless triggered
|
143
|
+
|
144
|
+
# gether values
|
145
|
+
values = logging[:devices].map do |config|
|
146
|
+
d1, d2 = config[:device].split("-").map{|d| plc.device_by_name d}
|
147
|
+
devices = [d1]
|
148
|
+
if d2
|
149
|
+
d3 = d1 + 1
|
150
|
+
devices += [d2.number - d1.number, 0].max.times.inject([]){|a, i| a << d3; d3 += 1; a}
|
151
|
+
end
|
152
|
+
devices.map{|d| d.send config[:type], config[:length] || 8}
|
153
|
+
end.flatten
|
154
|
+
@worker_queue.push logging:logging, values:values, time:Time.now
|
155
|
+
rescue => e
|
156
|
+
#p e, caller
|
169
157
|
end
|
158
|
+
end if config[:loggings]
|
159
|
+
end
|
160
|
+
|
161
|
+
private
|
162
|
+
|
163
|
+
def setup
|
164
|
+
Thread.start {
|
165
|
+
thread_proc
|
166
|
+
}
|
167
|
+
end
|
168
|
+
|
169
|
+
def thread_proc
|
170
|
+
while arg = @worker_queue.pop
|
171
|
+
begin
|
172
|
+
logging = arg[:logging]
|
173
|
+
spread_sheet = @session.spreadsheet_by_key(logging[:spread_sheet][:spread_sheet_key])
|
174
|
+
|
175
|
+
# get worksheet
|
176
|
+
worksheet = begin
|
177
|
+
if logging[:spread_sheet][:sheet_name]
|
178
|
+
spread_sheet.worksheet_by_title logging[:spread_sheet][:sheet_name]
|
179
|
+
else
|
180
|
+
spread_sheet.worksheets[logging[:spread_sheet][:sheet_no] || 0]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# write columns if needs
|
185
|
+
if worksheet.num_rows == 0
|
186
|
+
worksheet[1, 1] = "Time"
|
187
|
+
logging[:columns].split(",").each_with_index do |t, i|
|
188
|
+
worksheet[1, i + 2] = t
|
189
|
+
end
|
190
|
+
end
|
170
191
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
192
|
+
# write values
|
193
|
+
r = worksheet.num_rows + 1
|
194
|
+
worksheet[r, 1] = arg[:time]
|
195
|
+
arg[:values].each_with_index do |v, i|
|
196
|
+
worksheet[r, i + 2] = v
|
197
|
+
end if arg[:values]
|
198
|
+
worksheet.save
|
199
|
+
rescue => e
|
200
|
+
# TODO: Resend if it fails.
|
201
|
+
#p e, caller
|
176
202
|
end
|
177
203
|
end
|
178
|
-
|
179
|
-
# write values
|
180
|
-
r = worksheet.num_rows + 1
|
181
|
-
worksheet[r, 1] = arg[:time]
|
182
|
-
arg[:values].each_with_index do |v, i|
|
183
|
-
worksheet[r, i + 2] = v
|
184
|
-
end if arg[:values]
|
185
|
-
worksheet.save
|
186
|
-
rescue => e
|
187
|
-
# TODO: Resend if it fails.
|
188
|
-
p e
|
189
204
|
end
|
190
|
-
|
205
|
+
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
|
212
|
+
def plugin_google_drive_init plc
|
213
|
+
@google_drive_plugin = LadderDrive::Emulator::GoogleDrivePlugin.new plc
|
214
|
+
end
|
215
|
+
|
216
|
+
def plugin_google_drive_exec plc
|
217
|
+
@google_drive_plugin.run_cycle plc
|
191
218
|
end
|
data/plugins/ifttt_plugin.rb
CHANGED
@@ -57,78 +57,111 @@ DOC
|
|
57
57
|
|
58
58
|
require 'net/https'
|
59
59
|
|
60
|
-
|
61
|
-
|
62
|
-
return if @plugin_ifttt_config[:disable]
|
63
|
-
|
64
|
-
@plugin_ifttt_values = {}
|
65
|
-
@plugin_ifttt_times = {}
|
66
|
-
@plugin_ifttt_worker_queue = Queue.new
|
67
|
-
Thread.start {
|
68
|
-
plugin_ifttt_worker_loop
|
69
|
-
}
|
70
|
-
end
|
60
|
+
module LadderDrive
|
61
|
+
module Emulator
|
71
62
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
@
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
63
|
+
class IftttPlugin < Plugin
|
64
|
+
|
65
|
+
def initialize plc
|
66
|
+
super #plc
|
67
|
+
return if disabled?
|
68
|
+
|
69
|
+
@values = {}
|
70
|
+
@times = {}
|
71
|
+
@worker_queue = Queue.new
|
72
|
+
setup
|
73
|
+
end
|
74
|
+
|
75
|
+
def run_cycle plc
|
76
|
+
return if disabled?
|
77
|
+
config[:events].each do |event|
|
78
|
+
begin
|
79
|
+
triggered = false
|
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
|
100
102
|
end
|
101
103
|
end
|
104
|
+
|
105
|
+
next unless triggered
|
106
|
+
|
107
|
+
@worker_queue.push event:event[:name], payload:event[:params].dup || {}, value:v
|
108
|
+
rescue => e
|
109
|
+
p e
|
102
110
|
end
|
111
|
+
end if config[:events]
|
112
|
+
end
|
113
|
+
|
114
|
+
def disabled?
|
115
|
+
return false unless super
|
116
|
+
unless config[:web_hook_key]
|
117
|
+
puts "ERROR: IftttPlugin requires web_hook_key."
|
118
|
+
false
|
119
|
+
else
|
120
|
+
super
|
121
|
+
end
|
122
|
+
end
|
103
123
|
|
104
|
-
next unless triggered
|
105
124
|
|
106
|
-
|
107
|
-
|
108
|
-
|
125
|
+
private
|
126
|
+
|
127
|
+
def setup
|
128
|
+
Thread.start {
|
129
|
+
thread_proc
|
130
|
+
}
|
109
131
|
end
|
110
|
-
end if @plugin_ifttt_config[:events]
|
111
|
-
end
|
112
132
|
|
113
|
-
def
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
133
|
+
def thread_proc
|
134
|
+
while arg = @worker_queue.pop
|
135
|
+
begin
|
136
|
+
uri = URI.parse("https://maker.ifttt.com/trigger/#{arg[:event]}/with/key/#{config[:web_hook_key]}")
|
137
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
138
|
+
http.use_ssl = true
|
139
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
140
|
+
|
141
|
+
req = Net::HTTP::Post.new(uri.path)
|
142
|
+
payload = arg[:payload]
|
143
|
+
payload.keys.each do |key|
|
144
|
+
payload[key] = arg[:value] if payload[key] == "__value__"
|
145
|
+
end
|
146
|
+
req.set_form_data(payload)
|
127
147
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
148
|
+
http.request(req)
|
149
|
+
rescue => e
|
150
|
+
# TODO: Resend if it fails.
|
151
|
+
p e
|
152
|
+
end
|
153
|
+
end
|
132
154
|
end
|
133
|
-
|
155
|
+
|
156
|
+
end
|
157
|
+
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def plugin_ifttt_init plc
|
162
|
+
@ifttt_plugin = LadderDrive::Emulator::IftttPlugin.new plc
|
163
|
+
end
|
164
|
+
|
165
|
+
def plugin_ifttt_exec plc
|
166
|
+
@ifttt_plugin.run_cycle plc
|
134
167
|
end
|
@@ -54,13 +54,15 @@ class PlcMapperPlugin < Plugin
|
|
54
54
|
|
55
55
|
def initialize plc
|
56
56
|
super #plc
|
57
|
+
return if disabled?
|
57
58
|
@lock = Mutex.new
|
58
59
|
@values_for_reading = {}
|
59
60
|
@values_for_writing = {}
|
60
|
-
setup
|
61
|
+
setup unless config.empty? || config[:disable]
|
61
62
|
end
|
62
63
|
|
63
64
|
def run_cycle plc
|
65
|
+
return if disabled?
|
64
66
|
return false unless super
|
65
67
|
@lock.synchronize {
|
66
68
|
# set values from plcs to ladder drive.
|
@@ -81,8 +83,8 @@ class PlcMapperPlugin < Plugin
|
|
81
83
|
|
82
84
|
def setup
|
83
85
|
config[:plcs].each do |plc_config|
|
84
|
-
Thread.start(plc_config) {|
|
85
|
-
mapping_thread_proc
|
86
|
+
Thread.start(plc_config) {|config|
|
87
|
+
mapping_thread_proc config
|
86
88
|
}
|
87
89
|
end
|
88
90
|
end
|
@@ -102,7 +104,9 @@ class PlcMapperPlugin < Plugin
|
|
102
104
|
mappings.map do |h|
|
103
105
|
a = []
|
104
106
|
h.each do |k, v|
|
105
|
-
devs = v.split("-").map{|d|
|
107
|
+
devs = v.split("-").map{|d|
|
108
|
+
protocol.device_by_name d.strip
|
109
|
+
}
|
106
110
|
d1 = devs.first
|
107
111
|
d2 = devs.last
|
108
112
|
a << k
|
@@ -125,6 +129,7 @@ class PlcMapperPlugin < Plugin
|
|
125
129
|
Time.at t
|
126
130
|
end
|
127
131
|
|
132
|
+
alerted = false
|
128
133
|
loop do
|
129
134
|
begin
|
130
135
|
now = Time.now
|
@@ -133,8 +138,10 @@ class PlcMapperPlugin < Plugin
|
|
133
138
|
next_time += interval
|
134
139
|
end
|
135
140
|
sleep next_time - Time.now
|
136
|
-
|
137
|
-
|
141
|
+
alerted = false
|
142
|
+
rescue
|
143
|
+
puts "#{config[:description]} is not reachable." unless alerted
|
144
|
+
alerted = true
|
138
145
|
end
|
139
146
|
end
|
140
147
|
end
|
data/plugins/slack_plugin.rb
CHANGED
@@ -43,113 +43,139 @@ DOC
|
|
43
43
|
|
44
44
|
require 'net/https'
|
45
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
46
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
47
|
+
module LadderDrive
|
48
|
+
module Emulator
|
49
|
+
|
50
|
+
class SlackPlugin < Plugin
|
51
|
+
|
52
|
+
def initialize plc
|
53
|
+
super #plc
|
54
|
+
return if disabled?
|
55
|
+
|
56
|
+
@values = {}
|
57
|
+
@times = {}
|
58
|
+
@worker_queue = Queue.new
|
59
|
+
|
60
|
+
# collect comments
|
61
|
+
@comments = {}
|
62
|
+
config[:device_comments].each do |k, v|
|
63
|
+
d = plc.device_by_name(k)
|
64
|
+
@comments[d.name] = v if d
|
65
|
+
end if config[:device_comments]
|
66
|
+
setup
|
67
|
+
end
|
68
|
+
|
69
|
+
def run_cycle plc
|
70
|
+
return if disabled?
|
71
|
+
return unless config[:events]
|
72
|
+
|
73
|
+
config[:events].each do |event|
|
74
|
+
next unless event[:devices]
|
75
|
+
next unless event[:webhook_url]
|
76
|
+
begin
|
77
|
+
|
78
|
+
# gether values
|
79
|
+
devices = event[:devices].split(",").map{|e| e.split("-")}.map do |devs|
|
80
|
+
devs = devs.map{|d| plc.device_by_name d.strip}
|
81
|
+
d1 = devs.first
|
82
|
+
d2 = devs.last
|
83
|
+
[d2.number - d1.number + 1, 1].max.times.inject([]){|a, i| a << d1; d1 += 1; a}
|
84
|
+
end.flatten
|
85
|
+
|
86
|
+
interval_triggered = false
|
87
|
+
now = Time.now
|
88
|
+
devices.each do |device|
|
89
|
+
triggered = false
|
90
|
+
v = nil
|
91
|
+
case event[:trigger][:type]
|
92
|
+
when "interval"
|
93
|
+
t = @times[event.object_id] || now
|
94
|
+
triggered = t <= now
|
95
|
+
if triggered
|
96
|
+
interval_triggered = true
|
97
|
+
t += event[:trigger][:interval] || 300
|
98
|
+
@times[event.object_id] = t
|
99
|
+
end
|
100
|
+
v = device.send event[:value_type], event[:trigger][:text_length] || 8
|
101
|
+
else
|
102
|
+
v = device.send event[:value_type], event[:text_length] || 8
|
103
|
+
unless @values[device.name] == v
|
104
|
+
@values[device.name] = v
|
105
|
+
case event[:trigger][:type]
|
106
|
+
when "raise"
|
107
|
+
triggered = !!v
|
108
|
+
when "fall"
|
109
|
+
triggered = !v
|
110
|
+
else
|
111
|
+
triggered = true
|
112
|
+
end
|
108
113
|
end
|
109
114
|
end
|
115
|
+
|
116
|
+
next unless triggered || interval_triggered
|
117
|
+
|
118
|
+
@worker_queue.push event:event,
|
119
|
+
device_name:device.name,
|
120
|
+
value:v,
|
121
|
+
time: now
|
110
122
|
end
|
123
|
+
rescue => e
|
124
|
+
p e
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
111
130
|
|
112
|
-
|
131
|
+
def setup
|
132
|
+
Thread.start {
|
133
|
+
thread_proc
|
134
|
+
}
|
135
|
+
end
|
113
136
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
137
|
+
def thread_proc
|
138
|
+
while arg = @worker_queue.pop
|
139
|
+
begin
|
140
|
+
event = arg[:event]
|
141
|
+
uri = URI.parse(event[:webhook_url])
|
142
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
143
|
+
http.use_ssl = true
|
144
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
145
|
+
|
146
|
+
req = Net::HTTP::Post.new(uri.path)
|
147
|
+
req["Content-Type"] = "application/json"
|
148
|
+
|
149
|
+
format = event[:format] || "__comment__ occured at __time__"
|
150
|
+
format = arg[:value] ? format[:raise] : format[:fall] unless format.is_a? String
|
151
|
+
|
152
|
+
device_name = arg[:device_name]
|
153
|
+
comment = @comments[device_name] || device_name
|
154
|
+
value = arg[:value].to_s
|
155
|
+
time = arg[:time].iso8601
|
156
|
+
|
157
|
+
payload = {text:format.gsub(/__device_comment__/, comment).gsub(/__value__/, value).gsub(/__time__/, time).gsub(/__device_name__/, device_name)
|
158
|
+
}
|
159
|
+
req.body = payload.to_json
|
160
|
+
|
161
|
+
http.request(req)
|
162
|
+
rescue => e
|
163
|
+
# TODO: Resend if it fails.
|
164
|
+
p e
|
165
|
+
end
|
118
166
|
end
|
119
|
-
rescue => e
|
120
|
-
p e
|
121
167
|
end
|
122
|
-
|
168
|
+
|
123
169
|
end
|
124
170
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
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
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
|
175
|
+
def plugin_slack_init plc
|
176
|
+
@slack_plugin = LadderDrive::Emulator::SlackPlugin.new plc
|
177
|
+
end
|
178
|
+
|
179
|
+
def plugin_slack_exec plc
|
180
|
+
@slack_plugin.run_cycle plc
|
155
181
|
end
|
data/plugins/trello_plugin.rb
CHANGED
@@ -52,105 +52,122 @@ require 'trello'
|
|
52
52
|
|
53
53
|
# @see https://qiita.com/tbpgr/items/60fc13aca8afd153e37b
|
54
54
|
|
55
|
-
=begin
|
56
|
-
Dotenv.load
|
57
55
|
|
58
|
-
|
59
|
-
|
60
|
-
=end
|
56
|
+
module LadderDrive
|
57
|
+
module Emulator
|
61
58
|
|
59
|
+
class TrelloPlugin < Plugin
|
62
60
|
|
63
|
-
def
|
64
|
-
|
61
|
+
def initialize plc
|
62
|
+
super #plc
|
63
|
+
return if disabled?
|
65
64
|
|
66
|
-
|
67
|
-
|
68
|
-
|
65
|
+
@values = {}
|
66
|
+
@times = {}
|
67
|
+
@worker_queue = Queue.new
|
69
68
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
69
|
+
@configured = Trello.configure do |config|
|
70
|
+
config.consumer_key = self.config[:consumer_key]
|
71
|
+
config.consumer_secret = self.config[:consumer_secret]
|
72
|
+
config.oauth_token = self.config[:oauth_token]
|
73
|
+
end
|
74
|
+
setup
|
74
75
|
end
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end
|
77
|
+
def run_cycle plc
|
78
|
+
return if disabled?
|
79
|
+
return if config[:events].nil?
|
80
80
|
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
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
|
81
|
+
@config[:events].each do |event|
|
82
|
+
begin
|
83
|
+
|
84
|
+
triggered = false
|
85
|
+
now = Time.now
|
86
|
+
device = nil
|
87
|
+
|
88
|
+
case event[:trigger][:type]
|
89
|
+
when "interval"
|
90
|
+
t = @times[event.object_id] || now
|
91
|
+
triggered = t <= now
|
92
|
+
if triggered
|
112
93
|
triggered = true
|
94
|
+
t += event[:trigger][:interval] || 300
|
95
|
+
@times[event.object_id] = t
|
96
|
+
end
|
97
|
+
else
|
98
|
+
device = plc.device_by_name event[:trigger][:device]
|
99
|
+
v = device.send event[:trigger][:value_type], event[:trigger][:text_length] || 8
|
100
|
+
unless @values[device.name] == v
|
101
|
+
@values[device.name] = v
|
102
|
+
case event[:trigger][:type]
|
103
|
+
when "raise"
|
104
|
+
triggered = !!v
|
105
|
+
when "fall"
|
106
|
+
triggered = !v
|
107
|
+
else
|
108
|
+
triggered = true
|
109
|
+
end
|
113
110
|
end
|
114
111
|
end
|
115
|
-
end
|
116
112
|
|
117
|
-
|
113
|
+
next unless triggered
|
118
114
|
|
119
|
-
|
115
|
+
@worker_queue.push event:event, device_name:device.name, value:v, time: now
|
120
116
|
|
121
|
-
|
122
|
-
|
117
|
+
rescue => e
|
118
|
+
p e
|
119
|
+
end
|
123
120
|
end
|
124
|
-
end
|
125
|
-
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
126
124
|
|
127
|
-
def
|
128
|
-
|
129
|
-
|
130
|
-
|
125
|
+
def setup
|
126
|
+
Thread.start {
|
127
|
+
thread_proc
|
128
|
+
}
|
129
|
+
end
|
131
130
|
|
132
|
-
|
133
|
-
|
131
|
+
def thread_proc
|
132
|
+
while arg = @worker_queue.pop
|
133
|
+
begin
|
134
|
+
event = arg[:event]
|
134
135
|
|
135
|
-
|
136
|
-
|
137
|
-
next if (card_name || "").empty?
|
136
|
+
board = Trello::Board.all.find{|b| b.name == event[:board_name]}
|
137
|
+
next unless board
|
138
138
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
next unless list
|
139
|
+
card_name = event[:card_name].dup || ""
|
140
|
+
card_name.gsub!(/__value__/, arg[:value] || "")
|
141
|
+
next if (card_name || "").empty?
|
143
142
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
card = Trello::Card.create name:card_name, list_id:list.id
|
149
|
-
end
|
143
|
+
list_name = event[:list_name]
|
144
|
+
next unless list_name
|
145
|
+
list = board.lists.find{|l| l.name == list_name}
|
146
|
+
next unless list
|
150
147
|
|
151
|
-
|
152
|
-
|
153
|
-
|
148
|
+
card = board.lists.map{|l| l.cards.map{|c| c}}.flatten.find{|c| c.name == card_name}
|
149
|
+
if card
|
150
|
+
card.move_to_list list
|
151
|
+
else
|
152
|
+
card = Trello::Card.create name:card_name, list_id:list.id
|
153
|
+
end
|
154
|
+
|
155
|
+
rescue => e
|
156
|
+
# TODO: Resend if it fails.
|
157
|
+
p e
|
158
|
+
end
|
159
|
+
end
|
154
160
|
end
|
155
|
-
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
def plugin_trello_init plc
|
168
|
+
@trello_plugin = LadderDrive::Emulator::TrelloPlugin.new plc
|
169
|
+
end
|
170
|
+
|
171
|
+
def plugin_trello_exec plc
|
172
|
+
@trello_plugin.run_cycle plc
|
156
173
|
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.6.
|
4
|
+
version: 0.6.1
|
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: 2019-04-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: thor
|
@@ -48,42 +48,56 @@ dependencies:
|
|
48
48
|
name: ffi
|
49
49
|
requirement: !ruby/object:Gem::Requirement
|
50
50
|
requirements:
|
51
|
-
- - "
|
51
|
+
- - ">="
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: 1.9.24
|
54
|
-
- - "
|
54
|
+
- - "~>"
|
55
55
|
- !ruby/object:Gem::Version
|
56
56
|
version: 1.9.24
|
57
57
|
type: :runtime
|
58
58
|
prerelease: false
|
59
59
|
version_requirements: !ruby/object:Gem::Requirement
|
60
60
|
requirements:
|
61
|
-
- - "
|
61
|
+
- - ">="
|
62
62
|
- !ruby/object:Gem::Version
|
63
63
|
version: 1.9.24
|
64
|
-
- - "
|
64
|
+
- - "~>"
|
65
65
|
- !ruby/object:Gem::Version
|
66
66
|
version: 1.9.24
|
67
67
|
- !ruby/object:Gem::Dependency
|
68
68
|
name: pi_piper
|
69
69
|
requirement: !ruby/object:Gem::Requirement
|
70
70
|
requirements:
|
71
|
-
- - "~>"
|
72
|
-
- !ruby/object:Gem::Version
|
73
|
-
version: '2.0'
|
74
71
|
- - ">="
|
75
72
|
- !ruby/object:Gem::Version
|
76
73
|
version: 2.0.0
|
74
|
+
- - "~>"
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '2.0'
|
77
77
|
type: :runtime
|
78
78
|
prerelease: false
|
79
79
|
version_requirements: !ruby/object:Gem::Requirement
|
80
80
|
requirements:
|
81
|
+
- - ">="
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: 2.0.0
|
81
84
|
- - "~>"
|
82
85
|
- !ruby/object:Gem::Version
|
83
86
|
version: '2.0'
|
87
|
+
- !ruby/object:Gem::Dependency
|
88
|
+
name: serialport
|
89
|
+
requirement: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
84
91
|
- - ">="
|
85
92
|
- !ruby/object:Gem::Version
|
86
|
-
version:
|
93
|
+
version: '0'
|
94
|
+
type: :runtime
|
95
|
+
prerelease: false
|
96
|
+
version_requirements: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
87
101
|
- !ruby/object:Gem::Dependency
|
88
102
|
name: bundler
|
89
103
|
requirement: !ruby/object:Gem::Requirement
|
@@ -224,8 +238,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
224
238
|
- !ruby/object:Gem::Version
|
225
239
|
version: '0'
|
226
240
|
requirements: []
|
227
|
-
|
228
|
-
rubygems_version: 2.7.7
|
241
|
+
rubygems_version: 3.0.1
|
229
242
|
signing_key:
|
230
243
|
specification_version: 4
|
231
244
|
summary: The ladder_drive is a simple abstract ladder for PLC (Programmable Logic
|