ladder_drive 0.6.3 → 0.6.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +7 -2
- data/ladder_drive.gemspec +1 -0
- data/lib/ladder_drive/config.rb +1 -0
- 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 +0 -10
- data/lib/ladder_drive/protocol/mitsubishi/fx_protocol.rb +0 -10
- data/lib/ladder_drive/protocol/mitsubishi/mc_protocol.rb +0 -10
- data/lib/ladder_drive/protocol/omron/c_mode_protocol.rb +224 -0
- data/lib/ladder_drive/protocol/omron/fins_tcp_protocol.rb +378 -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 +14 -5
- data/lib/ladder_drive/tasks/build.rb +1 -1
- data/lib/ladder_drive/version.rb +1 -1
- data/plugins/ambient_plugin.rb +187 -0
- data/plugins/plc_mapper_plugin.rb +27 -15
- data/plugins/trello_plugin.rb +11 -6
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 78f994329616c81aa08af6f821851a9cfa302382
|
4
|
+
data.tar.gz: cdd5b03d754155febbc0e0b77f4e23a09910a447
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c92d94494040f7d972110d07d116df9fdea309c979fde59a58da644e4f9d41a775ec680c16a8e82be9267705f6dae9f6ca7967b4c595c03dcbf1cbd41158081f
|
7
|
+
data.tar.gz: d5343de39d9760831bda75adb6dfc7e6f30cb485f7c6f137a67dcd651583ecc517c415de040963cbe6cab29f5a968d1fdd7a9837fc9ebdc69ace1dfedbc8b07d
|
data/Gemfile
CHANGED
@@ -3,9 +3,13 @@ source 'https://rubygems.org'
|
|
3
3
|
# Specify your gem's dependencies in ladder_drive.gemspec
|
4
4
|
gemspec
|
5
5
|
|
6
|
-
|
6
|
+
group :test do
|
7
|
+
gem "test-unit"
|
8
|
+
gem "test-unit-rr"
|
9
|
+
#gem "test-unit-notify"
|
10
|
+
end
|
7
11
|
|
8
|
-
gem "activesupport", '
|
12
|
+
gem "activesupport", '>= 4.0'
|
9
13
|
|
10
14
|
gem 'pi_piper', ">= 2.0.0"
|
11
15
|
gem "ffi", "~> 1.9.24"
|
@@ -13,3 +17,4 @@ gem "ffi", "~> 1.9.24"
|
|
13
17
|
gem 'serialport'
|
14
18
|
gem 'google_drive'
|
15
19
|
gem "ruby-trello"
|
20
|
+
gem 'ambient_iot', ">= 0.1.1"
|
data/ladder_drive.gemspec
CHANGED
@@ -22,6 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
spec.add_runtime_dependency 'google_drive', '~> 3.0'
|
23
23
|
spec.add_runtime_dependency 'ruby-trello', '~>2.1'
|
24
24
|
|
25
|
+
spec.required_ruby_version = '>= 2.3.3'
|
25
26
|
|
26
27
|
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
27
28
|
spec.bindir = "exe"
|
data/lib/ladder_drive/config.rb
CHANGED
@@ -50,11 +50,6 @@ module Keyence
|
|
50
50
|
@socket = nil
|
51
51
|
end
|
52
52
|
|
53
|
-
def get_bit_from_device device
|
54
|
-
device = device_by_name device
|
55
|
-
get_bits_from_device(1, device).first
|
56
|
-
end
|
57
|
-
|
58
53
|
def get_bits_from_device count, device
|
59
54
|
c = (count + 15) / 16
|
60
55
|
words = get_words_from_device c, device
|
@@ -89,11 +84,6 @@ module Keyence
|
|
89
84
|
alias :set_bit_to_device :set_bits_to_device
|
90
85
|
|
91
86
|
|
92
|
-
def get_word_from_device device
|
93
|
-
device = device_by_name device
|
94
|
-
get_words_from_device(1, device).first
|
95
|
-
end
|
96
|
-
|
97
87
|
def get_words_from_device(count, device)
|
98
88
|
device = local_device device
|
99
89
|
packet = "RDS #{device.name}.H #{count}\r\n"
|
@@ -79,11 +79,6 @@ module Mitsubishi
|
|
79
79
|
@comm = nil
|
80
80
|
end
|
81
81
|
|
82
|
-
def get_bit_from_device device
|
83
|
-
device = device_by_name device
|
84
|
-
get_bits_from_device(1, device).first
|
85
|
-
end
|
86
|
-
|
87
82
|
def get_bits_from_device count, device
|
88
83
|
raise ArgumentError.new("A count #{count} must be between #{available_bits_range.first} and #{available_bits_range.last} for #{__method__}") unless available_bits_range.include? count
|
89
84
|
|
@@ -130,11 +125,6 @@ module Mitsubishi
|
|
130
125
|
end
|
131
126
|
end
|
132
127
|
|
133
|
-
def get_word_from_device device
|
134
|
-
device = device_by_name device
|
135
|
-
get_words_from_device(1, device).first
|
136
|
-
end
|
137
|
-
|
138
128
|
def get_words_from_device(count, device)
|
139
129
|
raise ArgumentError.new("A count #{count} must be between #{available_words_range.first} and #{available_words_range.last} for #{__method__}") unless available_words_range.include? count
|
140
130
|
|
@@ -50,11 +50,6 @@ module Mitsubishi
|
|
50
50
|
@socket = nil
|
51
51
|
end
|
52
52
|
|
53
|
-
def get_bit_from_device device
|
54
|
-
device = device_by_name device
|
55
|
-
get_bits_from_device(1, device).first
|
56
|
-
end
|
57
|
-
|
58
53
|
def get_bits_from_device count, device
|
59
54
|
raise ArgumentError.new("A count #{count} must be between #{available_bits_range.first} and #{available_bits_range.last} for #{__method__}") unless available_bits_range.include? count
|
60
55
|
|
@@ -108,11 +103,6 @@ module Mitsubishi
|
|
108
103
|
end
|
109
104
|
|
110
105
|
|
111
|
-
def get_word_from_device device
|
112
|
-
device = device_by_name device
|
113
|
-
get_words_from_device(1, device).first
|
114
|
-
end
|
115
|
-
|
116
106
|
def get_words_from_device(count, device)
|
117
107
|
raise ArgumentError.new("A count #{count} must be between #{available_words_range.first} and #{available_words_range.last} for #{__method__}") unless available_bits_range.include? count
|
118
108
|
|
@@ -0,0 +1,224 @@
|
|
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
|
+
module LadderDrive
|
25
|
+
module Protocol
|
26
|
+
module Omron
|
27
|
+
|
28
|
+
class CModeProtocol < Protocol
|
29
|
+
|
30
|
+
attr_accessor :baudrate
|
31
|
+
attr_accessor :unit_no
|
32
|
+
|
33
|
+
DELIMITER = "\r"
|
34
|
+
TERMINATOR = "*\r"
|
35
|
+
TIMEOUT = 1.0
|
36
|
+
|
37
|
+
def initialize options={}
|
38
|
+
super
|
39
|
+
@port = options[:port] || `ls /dev/tty.usb*`.split("\n").map{|l| l.chomp}.first
|
40
|
+
@baudrate = 38400
|
41
|
+
@unit_no = 0
|
42
|
+
@comm = nil
|
43
|
+
#prepare_device_map
|
44
|
+
end
|
45
|
+
|
46
|
+
def open
|
47
|
+
open!
|
48
|
+
rescue
|
49
|
+
nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def open!
|
53
|
+
return false unless @port
|
54
|
+
begin
|
55
|
+
# port, baudrate, bits, stop bits, parity(0:none, 1:even, 2:odd)
|
56
|
+
@comm ||= SerialPort.new(@port, @baudrate, 7, 2, 1).tap do |s|
|
57
|
+
s.read_timeout = (TIMEOUT * 1000.0).to_i
|
58
|
+
end
|
59
|
+
rescue => e
|
60
|
+
p e
|
61
|
+
nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def close
|
66
|
+
@comm.close if @comm
|
67
|
+
@comm = nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def unit_no= no
|
71
|
+
@unit_no = [[no, 0].max, 31].min
|
72
|
+
end
|
73
|
+
|
74
|
+
def get_bits_from_device count, device
|
75
|
+
device = device_by_name device
|
76
|
+
|
77
|
+
# convert to the channel device
|
78
|
+
from = device.channel_device
|
79
|
+
to = (device + count).channel_device
|
80
|
+
c = [to - from, 1].max
|
81
|
+
|
82
|
+
# get value as words
|
83
|
+
words = get_words_from_device(c, device)
|
84
|
+
|
85
|
+
# convert to bit devices
|
86
|
+
index = device.bit
|
87
|
+
bits = []
|
88
|
+
count.times do
|
89
|
+
i = index / 16
|
90
|
+
b = index % 16
|
91
|
+
f = 1 << b
|
92
|
+
bits << ((words[i] & f) == f)
|
93
|
+
index += 1
|
94
|
+
end
|
95
|
+
bits
|
96
|
+
end
|
97
|
+
|
98
|
+
def get_words_from_device(count, device)
|
99
|
+
device = device_by_name(device).channel_device
|
100
|
+
|
101
|
+
# make read packet
|
102
|
+
packet = read_packet_with device
|
103
|
+
packet << "#{device.channel.to_s.rjust(4, '0')}#{count.to_s.rjust(4, '0')}"
|
104
|
+
packet << fcs_for(packet).to_s(16).upcase.rjust(2, "0")
|
105
|
+
packet << TERMINATOR
|
106
|
+
@logger.debug("> #{dump_packet packet}")
|
107
|
+
|
108
|
+
# send command
|
109
|
+
open
|
110
|
+
send packet
|
111
|
+
|
112
|
+
# receive response
|
113
|
+
words = []
|
114
|
+
terminated = false
|
115
|
+
loop do
|
116
|
+
res = receive
|
117
|
+
data = ""
|
118
|
+
if res
|
119
|
+
ec = error_code(res)
|
120
|
+
raise "Error response: #{ec.to_i(16).rjust(2, '0')}" unless ec == 0
|
121
|
+
if res[-2,2] == TERMINATOR
|
122
|
+
fcs = fcs_for(res[0..-5])
|
123
|
+
raise "Not matched FCS expected #{fcs.to_s(16).rjust(2,'0')}" unless fcs == res[-4,2].to_i(16)
|
124
|
+
data = res[7..-5]
|
125
|
+
terminated = true
|
126
|
+
else res[-1,1] == DELIMITER
|
127
|
+
fcs = fcs_for(res[0..-4])
|
128
|
+
raise "Not matched FCS expected #{fcs.to_s(16).rjust(2,'0')}" unless fcs == res[-3,2].to_i(16)
|
129
|
+
data = res[7..-4]
|
130
|
+
end
|
131
|
+
len = data.length
|
132
|
+
index = 0
|
133
|
+
while index < len
|
134
|
+
words << data[index,4].to_i(16)
|
135
|
+
index += 4
|
136
|
+
end
|
137
|
+
return words if terminated
|
138
|
+
else
|
139
|
+
break
|
140
|
+
end
|
141
|
+
end
|
142
|
+
[]
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def device_by_name name
|
148
|
+
case name
|
149
|
+
when String
|
150
|
+
d = OmronDevice.new name
|
151
|
+
d.valid? ? d : nil
|
152
|
+
when EscDevice
|
153
|
+
local_device_of name
|
154
|
+
else
|
155
|
+
# it may be already OmronDevice
|
156
|
+
name
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def send packet
|
161
|
+
@comm.write(packet)
|
162
|
+
@comm.flush
|
163
|
+
end
|
164
|
+
|
165
|
+
def receive
|
166
|
+
res = ""
|
167
|
+
begin
|
168
|
+
Timeout.timeout(TIMEOUT) do
|
169
|
+
res = @comm.gets DELIMITER
|
170
|
+
=begin
|
171
|
+
loop do
|
172
|
+
res << @comm.getc# '\r' #gets
|
173
|
+
break if res[-1] == '\r'
|
174
|
+
end
|
175
|
+
=end
|
176
|
+
end
|
177
|
+
# res
|
178
|
+
rescue Timeout::Error
|
179
|
+
puts "*** ERROR: TIME OUT : #{res} ***"
|
180
|
+
end
|
181
|
+
@logger.debug("< #{dump_packet res}")
|
182
|
+
res
|
183
|
+
end
|
184
|
+
|
185
|
+
|
186
|
+
def read_packet_with device
|
187
|
+
packet = "@#{unit_no.to_s.rjust(2, '0')}R"
|
188
|
+
case device.suffix
|
189
|
+
when "HR"
|
190
|
+
packet << "H"
|
191
|
+
when "AL"
|
192
|
+
packet << "L"
|
193
|
+
when "DM", "D"
|
194
|
+
packet << "D"
|
195
|
+
when "AR"
|
196
|
+
packet << "J"
|
197
|
+
when "EM", "E"
|
198
|
+
packet << "E"
|
199
|
+
else
|
200
|
+
packet << "R"
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def fcs_for packet
|
205
|
+
fcs = packet.bytes.inject(0) do |a, b|
|
206
|
+
a = a ^ b
|
207
|
+
end
|
208
|
+
fcs = fcs & 0xff
|
209
|
+
fcs
|
210
|
+
end
|
211
|
+
|
212
|
+
def error_code packet
|
213
|
+
packet[1 + 2 + 2, 2].to_i(16)
|
214
|
+
end
|
215
|
+
|
216
|
+
def dump_packet packet
|
217
|
+
packet.inspect
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
@@ -0,0 +1,378 @@
|
|
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
|
+
module LadderDrive
|
25
|
+
module Protocol
|
26
|
+
module Omron
|
27
|
+
|
28
|
+
class FinsTcpProtocol < Protocol
|
29
|
+
|
30
|
+
attr_accessor :gateway_count
|
31
|
+
attr_accessor :destination_network
|
32
|
+
attr_accessor :destination_node
|
33
|
+
attr_accessor :destination_unit
|
34
|
+
attr_accessor :source_network
|
35
|
+
attr_accessor :source_node
|
36
|
+
attr_accessor :source_unit
|
37
|
+
|
38
|
+
attr_accessor :ethernet_module
|
39
|
+
|
40
|
+
attr_accessor :tcp_error_code
|
41
|
+
|
42
|
+
IOFINS_DESTINATION_NODE_FROM_IP = 0
|
43
|
+
IOFINS_SOURCE_AUTO_NODE = 0
|
44
|
+
|
45
|
+
# Available ethernet module.
|
46
|
+
ETHERNET_ETN21 = 0
|
47
|
+
ETHERNET_CP1E = 1
|
48
|
+
ETHERNET_CP1L = 2
|
49
|
+
ETHERNET_CP1H = 3
|
50
|
+
|
51
|
+
def initialize options={}
|
52
|
+
super
|
53
|
+
@socket = nil
|
54
|
+
@host = options[:host] || "192.168.250.1"
|
55
|
+
@port = options[:port] || 9600
|
56
|
+
@gateway_count = 3
|
57
|
+
@destination_network = 0
|
58
|
+
@destination_node = 0
|
59
|
+
@destination_unit = 0
|
60
|
+
@source_network = 0
|
61
|
+
@source_node = IOFINS_SOURCE_AUTO_NODE
|
62
|
+
@source_unit = 0
|
63
|
+
@ethernet_module = ETHERNET_ETN21
|
64
|
+
|
65
|
+
@tcp_error_code = 0
|
66
|
+
|
67
|
+
prepare_device_map
|
68
|
+
end
|
69
|
+
|
70
|
+
def open
|
71
|
+
open!
|
72
|
+
rescue =>e
|
73
|
+
p e
|
74
|
+
nil
|
75
|
+
end
|
76
|
+
|
77
|
+
def open!
|
78
|
+
if @socket.nil?
|
79
|
+
@socket = TCPSocket.open(@host, @port)
|
80
|
+
if @socket
|
81
|
+
source_node = IOFINS_SOURCE_AUTO_NODE
|
82
|
+
query_node
|
83
|
+
end
|
84
|
+
end
|
85
|
+
@socket
|
86
|
+
end
|
87
|
+
|
88
|
+
def close
|
89
|
+
@socket.close if @socket
|
90
|
+
@socket = nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def tcp_error?
|
94
|
+
tcp_error_code != 0
|
95
|
+
end
|
96
|
+
|
97
|
+
def create_query_node
|
98
|
+
header = [ "FINS".bytes.to_a, 0, 0, 0, 0xc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].flatten
|
99
|
+
header[19] = source_node == IOFINS_SOURCE_AUTO_NODE ? 0 : source_node
|
100
|
+
header
|
101
|
+
end
|
102
|
+
|
103
|
+
def create_fins_frame packet
|
104
|
+
packet = packet.flatten
|
105
|
+
header = [ "FINS".bytes.to_a, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0].flatten
|
106
|
+
header[4, 4] = int_to_a(packet.length + 8, 4)
|
107
|
+
header + packet
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_bits_from_device(count, device)
|
111
|
+
open
|
112
|
+
raise ArgumentError.new("A count #{count} must be between #{available_bits_range.first} and #{available_bits_range.last} for #{__method__}") unless available_bits_range.include? count
|
113
|
+
|
114
|
+
device = device_by_name device
|
115
|
+
raise ArgumentError.new("#{device.name} is not bit device!") unless device.bit_device?
|
116
|
+
|
117
|
+
command = [1, 1]
|
118
|
+
command << device_to_a(device)
|
119
|
+
command << int_to_a(count, 2)
|
120
|
+
|
121
|
+
send_packet create_fins_frame(fins_header + command)
|
122
|
+
res = receive
|
123
|
+
|
124
|
+
count.times.inject([]) do |a, i|
|
125
|
+
a << (res[16 + 10 + 4 + i] == 0 ? false : true)
|
126
|
+
a
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def get_words_from_device(count, device)
|
131
|
+
open
|
132
|
+
raise ArgumentError.new("A count #{count} must be between #{available_words_range.first} and #{available_words_range.last} for #{__method__}") unless available_words_range.include? count
|
133
|
+
|
134
|
+
device = device_by_name device
|
135
|
+
device = device.channel_device
|
136
|
+
|
137
|
+
command = [1, 1]
|
138
|
+
command << device_to_a(device)
|
139
|
+
command << int_to_a(count, 2)
|
140
|
+
|
141
|
+
send_packet create_fins_frame(fins_header + command)
|
142
|
+
res = receive
|
143
|
+
count.times.inject([]) do |a, i|
|
144
|
+
a << to_int(res[16 + 10 + 4 + i * 2, 2])
|
145
|
+
a
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def set_bits_to_device(bits, device)
|
150
|
+
open
|
151
|
+
count = bits.size
|
152
|
+
raise ArgumentError.new("A count #{count} must be between #{available_bits_range.first} and #{available_bits_range.last} for #{__method__}") unless available_bits_range.include? count
|
153
|
+
|
154
|
+
device = device_by_name device
|
155
|
+
raise ArgumentError.new("#{device.name} is not bit device!") unless device.bit_device?
|
156
|
+
|
157
|
+
command = [1, 2]
|
158
|
+
command << device_to_a(device)
|
159
|
+
command << int_to_a(count, 2)
|
160
|
+
bits.each do |b|
|
161
|
+
command << (b ? 1 : 0)
|
162
|
+
end
|
163
|
+
|
164
|
+
send_packet create_fins_frame(fins_header + command)
|
165
|
+
res = receive
|
166
|
+
end
|
167
|
+
|
168
|
+
def set_words_to_device(words, device)
|
169
|
+
open
|
170
|
+
count = words.size
|
171
|
+
raise ArgumentError.new("A count #{count} must be between #{available_words_range.first} and #{available_words_range.last} for #{__method__}") unless available_words_range.include? count
|
172
|
+
|
173
|
+
device = device_by_name device
|
174
|
+
device = device.channel_device
|
175
|
+
|
176
|
+
command = [1, 2]
|
177
|
+
command << device_to_a(device)
|
178
|
+
command << int_to_a(count, 2)
|
179
|
+
words.each do |w|
|
180
|
+
command << int_to_a(w, 2)
|
181
|
+
end
|
182
|
+
|
183
|
+
send_packet create_fins_frame(fins_header + command)
|
184
|
+
res = receive
|
185
|
+
end
|
186
|
+
|
187
|
+
def query_node
|
188
|
+
send_packet create_query_node
|
189
|
+
res = receive
|
190
|
+
self.source_node = res[19]
|
191
|
+
end
|
192
|
+
|
193
|
+
|
194
|
+
def send_packet packet
|
195
|
+
@socket.write(packet.flatten.pack("c*"))
|
196
|
+
@socket.flush
|
197
|
+
@logger.debug("> #{dump_packet packet}")
|
198
|
+
end
|
199
|
+
|
200
|
+
def receive
|
201
|
+
res = []
|
202
|
+
len = 0
|
203
|
+
begin
|
204
|
+
Timeout.timeout(5.0) do
|
205
|
+
loop do
|
206
|
+
c = @socket.getc
|
207
|
+
next if c.nil? || c == ""
|
208
|
+
|
209
|
+
res << c.bytes.first
|
210
|
+
next if res.length < 8
|
211
|
+
|
212
|
+
len = to_int(res[4, 4])
|
213
|
+
next if res.length < 8 + len
|
214
|
+
|
215
|
+
tcp_command = to_int(res[8, 4])
|
216
|
+
case tcp_command
|
217
|
+
when 3 # ERROR
|
218
|
+
raise "Invalidate tcp header: #{res}"
|
219
|
+
end
|
220
|
+
break
|
221
|
+
end
|
222
|
+
end
|
223
|
+
raise "Response error code: #{res[15]}" unless res[15] == 0
|
224
|
+
res
|
225
|
+
end
|
226
|
+
@logger.debug("< #{dump_packet res}")
|
227
|
+
res
|
228
|
+
end
|
229
|
+
|
230
|
+
# max length:
|
231
|
+
# CS1W-ETN21, CJ1W-ETN21 : 2012
|
232
|
+
# CP1W-CIF41 option board : 540 (1004 if cpu is CP1L/H)
|
233
|
+
|
234
|
+
def available_bits_range device=nil
|
235
|
+
case ethernet_module
|
236
|
+
when ETHERNET_ETN21
|
237
|
+
1..(2012 - 8)
|
238
|
+
when ETHERNET_CP1E
|
239
|
+
1..(540 - 8)
|
240
|
+
when ETHERNET_CP1L, ETHERNET_CP1H
|
241
|
+
1..(1004 - 8)
|
242
|
+
else
|
243
|
+
0..0
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
def available_words_range device=nil
|
248
|
+
case ethernet_module
|
249
|
+
when ETHERNET_ETN21
|
250
|
+
1..((2012 - 8) / 2)
|
251
|
+
when ETHERNET_CP1E
|
252
|
+
1..((540 - 8) / 2)
|
253
|
+
when ETHERNET_CP1L, ETHERNET_CP1H
|
254
|
+
1..((1004 - 8) / 2)
|
255
|
+
else
|
256
|
+
0..0
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def device_by_name name
|
261
|
+
case name
|
262
|
+
when String
|
263
|
+
d = OmronDevice.new name
|
264
|
+
d.valid? ? d : nil
|
265
|
+
when EscDevice
|
266
|
+
local_device_of name
|
267
|
+
else
|
268
|
+
# it may be already OmronDevice
|
269
|
+
name
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
private
|
274
|
+
|
275
|
+
def fins_header
|
276
|
+
buf = [
|
277
|
+
0x80, # ICF
|
278
|
+
0x00, # RSV
|
279
|
+
0x02, # GCT
|
280
|
+
0x00, # DNA
|
281
|
+
0x01, # DA1
|
282
|
+
0x00, # DA2
|
283
|
+
0x00, # SNA
|
284
|
+
0x01, # SA1
|
285
|
+
0x00, # SA2
|
286
|
+
0x00, # SID
|
287
|
+
]
|
288
|
+
buf[2] = gateway_count - 1
|
289
|
+
buf[3] = destination_network
|
290
|
+
if destination_node == IOFINS_DESTINATION_NODE_FROM_IP
|
291
|
+
buf[4] = destination_ipv4.split(".").last.to_i
|
292
|
+
else
|
293
|
+
buf[4] = destination_node
|
294
|
+
end
|
295
|
+
buf[7] = source_node
|
296
|
+
buf[8] = source_unit
|
297
|
+
|
298
|
+
buf
|
299
|
+
end
|
300
|
+
|
301
|
+
def fins_tcp_cmnd_header
|
302
|
+
header = [ "FINS".bytes.to_a, 0, 0, 0, 0xc, 0, 0, 0, 2, 0, 0, 0, 0].flatten
|
303
|
+
header[19] = source_node == IOFINS_SOURCE_AUTO_NODE ? 0 : source_node
|
304
|
+
header
|
305
|
+
end
|
306
|
+
|
307
|
+
def device_code_of device
|
308
|
+
@@bit_codes ||= { nil => 0x30, "" => 0x30, "W" => 0x31, "H" => 0x32, "A" => 0x33, "T" => 0x09, "C" => 0x09, "D" => 0x02, "E" => 0x0a, "TK" => 0x06 }
|
309
|
+
@@word_codes ||= { nil => 0xB0, "" => 0xB0, "W" => 0xB1, "H" => 0xB2, "A" => 0xB3, "TIM" => 0x89, "CNT" => 0x89, "D" => 0x82, "E" => 0x98, "DR" => 0xbc }
|
310
|
+
if device.bit_device?
|
311
|
+
@@bit_codes[device.suffix]
|
312
|
+
else
|
313
|
+
@@word_codes[device.suffix]
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
def device_to_a device
|
318
|
+
a = []
|
319
|
+
a << device_code_of(device)
|
320
|
+
a << int_to_a(device.channel, 2)
|
321
|
+
a << (device.bit_device? ? (device.bit || 0) : 0)
|
322
|
+
a.flatten
|
323
|
+
end
|
324
|
+
|
325
|
+
|
326
|
+
# FIXME: It's dummy currently.
|
327
|
+
def prepare_device_map
|
328
|
+
@conv_dev_dict ||= begin
|
329
|
+
h = {}
|
330
|
+
[
|
331
|
+
["X", "0.0", 1024],
|
332
|
+
["Y", "400.0", 1024],
|
333
|
+
["M", "M0.0", 1024],
|
334
|
+
["C", "C0", 256],
|
335
|
+
["T", "T0", 256],
|
336
|
+
["L", "H0.0", 1024],
|
337
|
+
["SC", "M400.0", 1024],
|
338
|
+
["D", "D0", 1024],
|
339
|
+
["H", "D1024", 1024],
|
340
|
+
["SD", "D2048", 1024],
|
341
|
+
["PRG", "D3072", 1024] # ..D4095
|
342
|
+
].each do |s,d,c|
|
343
|
+
h[s] = [OmronDevice.new(d), c]
|
344
|
+
end
|
345
|
+
h
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
def int_to_a value, size
|
350
|
+
a = []
|
351
|
+
(size - 1).downto 0 do |i|
|
352
|
+
a << ((value >> (i * 8)) & 0xff)
|
353
|
+
end
|
354
|
+
a
|
355
|
+
end
|
356
|
+
|
357
|
+
def to_int a
|
358
|
+
v = 0
|
359
|
+
a.each do |e|
|
360
|
+
v <<= 8
|
361
|
+
v += e
|
362
|
+
end
|
363
|
+
v
|
364
|
+
end
|
365
|
+
|
366
|
+
def dump_packet packet
|
367
|
+
a =
|
368
|
+
packet.map{|e|
|
369
|
+
e.to_s(16).rjust(2, '0')
|
370
|
+
}
|
371
|
+
"[#{a.join(', ')}]"
|
372
|
+
end
|
373
|
+
|
374
|
+
end
|
375
|
+
|
376
|
+
end
|
377
|
+
end
|
378
|
+
end
|