ladder_drive 0.6.3 → 0.6.4

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.
@@ -0,0 +1,28 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2019 ITO SOFT DESIGN Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ $:.unshift File.dirname(__FILE__)
25
+
26
+ require 'omron_device'
27
+ require 'c_mode_protocol'
28
+ require 'fins_tcp_protocol'
@@ -0,0 +1,139 @@
1
+ # The MIT License (MIT)
2
+ #
3
+ # Copyright (c) 2019 ITO SOFT DESIGN Inc.
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining
6
+ # a copy of this software and associated documentation files (the
7
+ # "Software"), to deal in the Software without restriction, including
8
+ # without limitation the rights to use, copy, modify, merge, publish,
9
+ # distribute, sublicense, and/or sell copies of the Software, and to
10
+ # permit persons to whom the Software is furnished to do so, subject to
11
+ # the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be
14
+ # included in all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+
25
+ # Supported models : CP1E
26
+
27
+ module LadderDrive
28
+ module Protocol
29
+ module Omron
30
+
31
+ class OmronDevice < PlcDevice
32
+
33
+ attr_reader :suffix, :channel, :bit
34
+
35
+ SUFFIXES = %w(M H D T C A)
36
+
37
+ def initialize a, b = nil, c=nil
38
+ case a
39
+ when Array
40
+ =begin
41
+ case a.size
42
+ when 4
43
+ @suffix = suffix_for_code(a[3])
44
+ @channel = ((a[2] << 8 | a[1]) << 8) | a[0]
45
+ end
46
+ =end
47
+ else
48
+ if b
49
+ @suffix = a.upcase if a
50
+ @channel = b.to_i
51
+ @bit = c.to_i if c
52
+ else
53
+ if /^(M|H|D|T|C|A)?([0-9]+)(\.([0-9]{1,2}))?$/i =~ a
54
+ @suffix = $1.upcase if $1
55
+ @channel = $2.to_i
56
+ @bit = $4.to_i if $4
57
+ end
58
+ end
59
+ end
60
+ case @suffix
61
+ when "T", "C"
62
+ raise "#{self.name} is not allowed as a bit device." if @bit
63
+ end
64
+ end
65
+
66
+ def channel_device
67
+ return self unless bit_device?
68
+ self.class.new suffix, channel
69
+ end
70
+
71
+ def valid?
72
+ !!channel
73
+ end
74
+
75
+ def name
76
+ if bit
77
+ "#{suffix}#{channel}.#{bit.to_s.rjust(2, '0')}"
78
+ else
79
+ "#{suffix}#{channel}"
80
+ end
81
+ end
82
+
83
+ def next_device
84
+ self + 1
85
+ end
86
+
87
+ def bit_device?
88
+ !!bit
89
+ end
90
+
91
+ def suffix_for_code code
92
+ index = SUFFIX_CODES.index code
93
+ index ? SUFFIXES[index] : nil
94
+ end
95
+
96
+ def suffix_code
97
+ index = SUFFIXES.index suffix
98
+ index ? SUFFIX_CODES[index] : 0
99
+ end
100
+
101
+ def + value
102
+ if bit
103
+ v = channel * 16 + bit + value
104
+ c = v / 16
105
+ b = v % 16
106
+ self.class.new suffix, c, b
107
+ else
108
+ self.class.new suffix, channel + value
109
+ end
110
+ end
111
+
112
+ def - value
113
+ case value
114
+ when OmronDevice
115
+ d = value
116
+ raise "Can't subtract between different device type." if self.bit_device? ^ d.bit_device?
117
+ if bit
118
+ (channel * 16 + bit) - (d.channel * 16 + d.bit)
119
+ else
120
+ channel - d.channel
121
+ end
122
+ else
123
+ value = value.to_i
124
+ if bit
125
+ v = [channel * 16 + bit - value, 0].max
126
+ c = v / 16
127
+ b = v % 16
128
+ self.class.new suffix, c, b
129
+ else
130
+ self.class.new suffix, [channel - value, 0].max
131
+ end
132
+ end
133
+ end
134
+
135
+ end
136
+
137
+ end
138
+ end
139
+ end
@@ -63,15 +63,15 @@ module Protocol
63
63
  def open; end
64
64
  def close; end
65
65
 
66
- def get_bit_from_device device; end
66
+ def get_bit_from_device device; get_bits_from_device(1, device_by_name(device)).first; end
67
67
  def get_bits_from_device count, device; end
68
- def set_bits_to_device bits, device; end
69
68
  def set_bit_to_device bit, device; set_bits_to_device [bit], device; end
69
+ def set_bits_to_device bits, device; end
70
70
 
71
- def get_word_from_device device; end
72
- def get_words_from_device(count, device); end
73
- def set_words_to_device words, device; end
71
+ def get_word_from_device device; get_words_from_device(1, device_by_name(device)).first; end
72
+ def get_words_from_device count, device; end
74
73
  def set_word_to_device word, device; set_words_to_device [word], device; end
74
+ def set_words_to_device words, device; end
75
75
 
76
76
  def device_by_name name; nil; end
77
77
 
@@ -188,6 +188,14 @@ module Protocol
188
188
  end
189
189
  end
190
190
 
191
+ def destination_ipv4
192
+ Socket.gethostbyname(self.host)[3].unpack("C4").join('.')
193
+ end
194
+
195
+ def self_ipv4
196
+ Socket::getaddrinfo(Socket.gethostname,"echo",Socket::AF_INET)[0][3]
197
+ end
198
+
191
199
  end
192
200
 
193
201
  end
@@ -198,3 +206,4 @@ require 'keyence/keyence'
198
206
  # Use load instead require, because there are two emulator files.
199
207
  load File.join(dir, 'emulator/emulator.rb')
200
208
  require 'mitsubishi/mitsubishi'
209
+ require 'omron/omron'
@@ -112,7 +112,7 @@ namespace :service do
112
112
  # copy lddrive.serive file to /etc/systemd/system
113
113
  fname = "lddrive.service"
114
114
  s = File.read(File.join(template_dir, fname))
115
- s.gsub!("<%= exec_start %>", "/home/pi/project/lddrive")
115
+ s.gsub!("<%= exec_start %>", "#{Dir.pwd}/lddrive")
116
116
  File.write File.join("/etc", "systemd", "system", fname), s
117
117
 
118
118
  # reload services
@@ -22,5 +22,5 @@
22
22
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
23
 
24
24
  module LadderDrive
25
- VERSION = "0.6.3"
25
+ VERSION = "0.6.4"
26
26
  end
@@ -0,0 +1,187 @@
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
+ @times = {}
86
+ @worker_queue = Queue.new
87
+
88
+ setup
89
+ end
90
+
91
+ def run_cycle plc
92
+ return if disabled?
93
+ return unless config[:channels]
94
+
95
+ config[:channels].each do |channel|
96
+ channel = channel
97
+ next unless channel[:channel_id]
98
+ next unless channel[:write_key]
99
+ 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
131
+
132
+ # gether values
133
+ values = channel[:devices].inject({}) do |h, pair|
134
+ d = plc.device_by_name pair.last[:device]
135
+ v = d.send pair.last[:value_type], pair.last[:text_length] || 8
136
+ h[pair.first] = v
137
+ h
138
+ end
139
+
140
+ @worker_queue.push channel:channel, values:values
141
+ rescue => e
142
+ p e
143
+ end
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def setup
150
+ Thread.start {
151
+ thread_proc
152
+ }
153
+ end
154
+
155
+ def thread_proc
156
+ @uploaded_at = nil
157
+
158
+ while arg = @worker_queue.pop
159
+ begin
160
+ now = Time.now
161
+ # ignore a request if it's requested in a 5 sec from previous request.
162
+ next unless @uploaded_at.nil? || (now - @uploaded_at >= 5)
163
+
164
+ channel = arg[:channel]
165
+ client = AmbientIot::Client.new channel[:channel_id], write_key:channel[:write_key]
166
+ client << arg[:values]
167
+ client.sync
168
+ @uploaded_at = now
169
+ rescue => e
170
+ p e
171
+ end
172
+ end
173
+ end
174
+
175
+ end
176
+
177
+ end
178
+ end
179
+
180
+
181
+ def plugin_ambient_init plc
182
+ @ambient_plugin = LadderDrive::Emulator::AmbientPlugin.new plc
183
+ end
184
+
185
+ def plugin_ambient_exec plc
186
+ @ambient_plugin.run_cycle plc
187
+ end
@@ -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
- values_for_writing[d] = plc.device_by_name(d).value
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.number - d1.number + 1, 1].max]
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 form ladder drive (values_for_writing) to plc
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
- # It may not get the value for the first time, set zero instead of it.
173
- values_for_writing[src_d.name] ||= 0
174
- values << values_for_writing[src_d.name]
175
- src_d = src_d.next_device
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
- @plugin_plc_mapper.run_cycle plc
198
+ begin
199
+ @plugin_plc_mapper.run_cycle plc
200
+ rescue
201
+ puts $!
202
+ puts $@
203
+ plc.close
204
+ end
193
205
  end