plc_access 0.1.0
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 +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +53 -0
- data/LICENSE.txt +21 -0
- data/README.md +69 -0
- data/Rakefile +11 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/lib/plc_access/protocol/keyence/keyence.rb +33 -0
- data/lib/plc_access/protocol/keyence/kv_device.rb +68 -0
- data/lib/plc_access/protocol/keyence/kv_protocol.rb +181 -0
- data/lib/plc_access/protocol/mitsubishi/fx_device.rb +13 -0
- data/lib/plc_access/protocol/mitsubishi/fx_protocol.rb +265 -0
- data/lib/plc_access/protocol/mitsubishi/mc_protocol.rb +281 -0
- data/lib/plc_access/protocol/mitsubishi/mitsubishi.rb +36 -0
- data/lib/plc_access/protocol/mitsubishi/qdevice.rb +113 -0
- data/lib/plc_access/protocol/omron/c_mode_protocol.rb +218 -0
- data/lib/plc_access/protocol/omron/fins_tcp_protocol.rb +353 -0
- data/lib/plc_access/protocol/omron/omron.rb +30 -0
- data/lib/plc_access/protocol/omron/omron_device.rb +134 -0
- data/lib/plc_access/protocol/plc_device.rb +225 -0
- data/lib/plc_access/protocol/protocol.rb +219 -0
- data/lib/plc_access/version.rb +5 -0
- data/lib/plc_access.rb +30 -0
- data/plc_access.gemspec +31 -0
- metadata +94 -0
@@ -0,0 +1,218 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2019 ITO SOFT DESIGN Inc.
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
# a copy of this software and associated documentation files (the
|
9
|
+
# "Software"), to deal in the Software without restriction, including
|
10
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
# the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be
|
16
|
+
# included in all copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
|
26
|
+
module PlcAccess
|
27
|
+
module Protocol
|
28
|
+
module Omron
|
29
|
+
class CModeProtocol < Protocol
|
30
|
+
attr_accessor :baudrate, :unit_no
|
31
|
+
|
32
|
+
DELIMITER = "\r"
|
33
|
+
TERMINATOR = "*\r"
|
34
|
+
TIMEOUT = 1.0
|
35
|
+
|
36
|
+
def initialize(options = {})
|
37
|
+
super
|
38
|
+
@port = options[:port] || `ls /dev/tty.usb*`.split("\n").map(&:chomp).first
|
39
|
+
@baudrate = 38_400
|
40
|
+
@unit_no = 0
|
41
|
+
@comm = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def open
|
45
|
+
open!
|
46
|
+
rescue StandardError
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def open!
|
51
|
+
return false unless @port
|
52
|
+
|
53
|
+
begin
|
54
|
+
# port, baudrate, bits, stop bits, parity(0:none, 1:even, 2:odd)
|
55
|
+
@comm ||= SerialPort.new(@port, @baudrate, 7, 2, 1).tap do |s|
|
56
|
+
s.read_timeout = (TIMEOUT * 1000.0).to_i
|
57
|
+
end
|
58
|
+
rescue StandardError => e
|
59
|
+
p e
|
60
|
+
nil
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def close
|
65
|
+
@comm&.close
|
66
|
+
@comm = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def unit_no=(no)
|
70
|
+
@unit_no = [[no, 0].max, 31].min
|
71
|
+
end
|
72
|
+
|
73
|
+
def get_bits_from_device(count, device)
|
74
|
+
device = device_by_name device
|
75
|
+
|
76
|
+
# convert to the channel device
|
77
|
+
from = device.channel_device
|
78
|
+
to = (device + count).channel_device
|
79
|
+
c = [to - from, 1].max
|
80
|
+
|
81
|
+
# get value as words
|
82
|
+
words = get_words_from_device(c, device)
|
83
|
+
|
84
|
+
# convert to bit devices
|
85
|
+
index = device.bit
|
86
|
+
bits = []
|
87
|
+
count.times do
|
88
|
+
i = index / 16
|
89
|
+
b = index % 16
|
90
|
+
f = 1 << b
|
91
|
+
bits << ((words[i] & f) == f)
|
92
|
+
index += 1
|
93
|
+
end
|
94
|
+
bits
|
95
|
+
end
|
96
|
+
|
97
|
+
def get_words_from_device(count, device)
|
98
|
+
device = device_by_name(device).channel_device
|
99
|
+
|
100
|
+
# make read packet
|
101
|
+
packet = read_packet_with device
|
102
|
+
packet << "#{device.channel.to_s.rjust(4, '0')}#{count.to_s.rjust(4, '0')}"
|
103
|
+
packet << fcs_for(packet).to_s(16).upcase.rjust(2, '0')
|
104
|
+
packet << TERMINATOR
|
105
|
+
@logger.debug("> #{dump_packet packet}")
|
106
|
+
|
107
|
+
# send command
|
108
|
+
open
|
109
|
+
send packet
|
110
|
+
|
111
|
+
# receive response
|
112
|
+
words = []
|
113
|
+
terminated = false
|
114
|
+
loop do
|
115
|
+
res = receive
|
116
|
+
data = ''
|
117
|
+
if res
|
118
|
+
ec = error_code(res)
|
119
|
+
raise "Error response: #{ec.to_i(16).rjust(2, '0')}" unless ec.zero?
|
120
|
+
|
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
|
+
|
125
|
+
data = res[7..-5]
|
126
|
+
terminated = true
|
127
|
+
elsif res[-1, 1] == DELIMITER
|
128
|
+
fcs = fcs_for(res[0..-4])
|
129
|
+
raise "Not matched FCS expected #{fcs.to_s(16).rjust(2, '0')}" unless fcs == res[-3, 2].to_i(16)
|
130
|
+
|
131
|
+
data = res[7..-4]
|
132
|
+
end
|
133
|
+
len = data.length
|
134
|
+
index = 0
|
135
|
+
while index < len
|
136
|
+
words << data[index, 4].to_i(16)
|
137
|
+
index += 4
|
138
|
+
end
|
139
|
+
return words if terminated
|
140
|
+
else
|
141
|
+
break
|
142
|
+
end
|
143
|
+
end
|
144
|
+
[]
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def device_by_name(name)
|
150
|
+
case name
|
151
|
+
when String
|
152
|
+
d = OmronDevice.new name
|
153
|
+
d.valid? ? d : nil
|
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
|
+
# loop do
|
171
|
+
# res << @comm.getc# '\r' #gets
|
172
|
+
# break if res[-1] == '\r'
|
173
|
+
# end
|
174
|
+
end
|
175
|
+
# res
|
176
|
+
rescue Timeout::Error
|
177
|
+
puts "*** ERROR: TIME OUT : #{res} ***"
|
178
|
+
end
|
179
|
+
@logger.debug("< #{dump_packet res}")
|
180
|
+
res
|
181
|
+
end
|
182
|
+
|
183
|
+
def read_packet_with(device)
|
184
|
+
packet = "@#{unit_no.to_s.rjust(2, '0')}R"
|
185
|
+
packet << case device.suffix
|
186
|
+
when 'HR'
|
187
|
+
'H'
|
188
|
+
when 'AL'
|
189
|
+
'L'
|
190
|
+
when 'DM', 'D'
|
191
|
+
'D'
|
192
|
+
when 'AR'
|
193
|
+
'J'
|
194
|
+
when 'EM', 'E'
|
195
|
+
'E'
|
196
|
+
else
|
197
|
+
'R'
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def fcs_for(packet)
|
202
|
+
fcs = packet.bytes.inject(0) do |a, b|
|
203
|
+
a ^ b
|
204
|
+
end
|
205
|
+
fcs & 0xff
|
206
|
+
end
|
207
|
+
|
208
|
+
def error_code(packet)
|
209
|
+
packet[1 + 2 + 2, 2].to_i(16)
|
210
|
+
end
|
211
|
+
|
212
|
+
def dump_packet(packet)
|
213
|
+
packet.inspect
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -0,0 +1,353 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2019 ITO SOFT DESIGN Inc.
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
# a copy of this software and associated documentation files (the
|
9
|
+
# "Software"), to deal in the Software without restriction, including
|
10
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
# the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be
|
16
|
+
# included in all copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
|
26
|
+
module PlcAccess
|
27
|
+
module Protocol
|
28
|
+
module Omron
|
29
|
+
class FinsTcpProtocol < Protocol
|
30
|
+
attr_accessor :gateway_count, :destination_network, :destination_node, :destination_unit, :source_network,
|
31
|
+
:source_node, :source_unit, :ethernet_module, :tcp_error_code
|
32
|
+
|
33
|
+
IOFINS_DESTINATION_NODE_FROM_IP = 0
|
34
|
+
IOFINS_SOURCE_AUTO_NODE = 0
|
35
|
+
|
36
|
+
# Available ethernet module.
|
37
|
+
ETHERNET_ETN21 = 0
|
38
|
+
ETHERNET_CP1E = 1
|
39
|
+
ETHERNET_CP1L = 2
|
40
|
+
ETHERNET_CP1H = 3
|
41
|
+
|
42
|
+
TIMEOUT = 5.0
|
43
|
+
|
44
|
+
def initialize(options = {})
|
45
|
+
super
|
46
|
+
@socket = nil
|
47
|
+
@host = options[:host] || '192.168.250.1'
|
48
|
+
@port = options[:port] || 9600
|
49
|
+
@gateway_count = 3
|
50
|
+
@destination_network = 0
|
51
|
+
@destination_node = 0
|
52
|
+
@destination_unit = 0
|
53
|
+
@source_network = 0
|
54
|
+
@source_node = IOFINS_SOURCE_AUTO_NODE
|
55
|
+
@source_unit = 0
|
56
|
+
@ethernet_module = ETHERNET_ETN21
|
57
|
+
|
58
|
+
@tcp_error_code = 0
|
59
|
+
end
|
60
|
+
|
61
|
+
def open
|
62
|
+
open!
|
63
|
+
rescue StandardError => e
|
64
|
+
p e
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def open!
|
69
|
+
if @socket.nil?
|
70
|
+
@socket = TCPSocket.open(@host, @port)
|
71
|
+
if @socket
|
72
|
+
self.source_node = IOFINS_SOURCE_AUTO_NODE
|
73
|
+
query_node
|
74
|
+
end
|
75
|
+
end
|
76
|
+
@socket
|
77
|
+
end
|
78
|
+
|
79
|
+
def close
|
80
|
+
@socket&.close
|
81
|
+
@socket = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
def tcp_error?
|
85
|
+
tcp_error_code != 0
|
86
|
+
end
|
87
|
+
|
88
|
+
def create_query_node
|
89
|
+
header = ['FINS'.bytes.to_a, 0, 0, 0, 0xc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].flatten
|
90
|
+
header[19] = source_node == IOFINS_SOURCE_AUTO_NODE ? 0 : source_node
|
91
|
+
header
|
92
|
+
end
|
93
|
+
|
94
|
+
def create_fins_frame(packet)
|
95
|
+
packet = packet.flatten
|
96
|
+
header = ['FINS'.bytes.to_a, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0].flatten
|
97
|
+
header[4, 4] = int_to_a(packet.length + 8, 4)
|
98
|
+
header + packet
|
99
|
+
end
|
100
|
+
|
101
|
+
def get_bits_from_device(count, device)
|
102
|
+
open
|
103
|
+
unless available_bits_range.include? count
|
104
|
+
raise ArgumentError,
|
105
|
+
"A count #{count} must be between #{available_bits_range.first} and #{available_bits_range.last} for #{__method__}"
|
106
|
+
end
|
107
|
+
|
108
|
+
device = device_by_name device
|
109
|
+
raise ArgumentError, "#{device.name} is not bit device!" unless device.bit_device?
|
110
|
+
|
111
|
+
command = [1, 1]
|
112
|
+
command << device_to_a(device)
|
113
|
+
command << int_to_a(count, 2)
|
114
|
+
|
115
|
+
send_packet create_fins_frame(fins_header + command)
|
116
|
+
res = receive
|
117
|
+
|
118
|
+
count.times.each_with_object([]) do |i, a|
|
119
|
+
a << (res[16 + 10 + 4 + i] != 0)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def get_words_from_device(count, device)
|
124
|
+
open
|
125
|
+
unless available_words_range.include? count
|
126
|
+
raise ArgumentError,
|
127
|
+
"A count #{count} must be between #{available_words_range.first} and #{available_words_range.last} for #{__method__}"
|
128
|
+
end
|
129
|
+
|
130
|
+
device = device_by_name device
|
131
|
+
device = device.channel_device
|
132
|
+
|
133
|
+
command = [1, 1]
|
134
|
+
command << device_to_a(device)
|
135
|
+
command << int_to_a(count, 2)
|
136
|
+
|
137
|
+
send_packet create_fins_frame(fins_header + command)
|
138
|
+
res = receive
|
139
|
+
count.times.each_with_object([]) do |i, a|
|
140
|
+
a << to_int(res[16 + 10 + 4 + i * 2, 2])
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def set_bits_to_device(bits, device)
|
145
|
+
open
|
146
|
+
count = bits.size
|
147
|
+
unless available_bits_range.include? count
|
148
|
+
raise ArgumentError,
|
149
|
+
"A count #{count} must be between #{available_bits_range.first} and #{available_bits_range.last} for #{__method__}"
|
150
|
+
end
|
151
|
+
|
152
|
+
device = device_by_name device
|
153
|
+
raise ArgumentError, "#{device.name} is not bit device!" unless device.bit_device?
|
154
|
+
|
155
|
+
command = [1, 2]
|
156
|
+
command << device_to_a(device)
|
157
|
+
command << int_to_a(count, 2)
|
158
|
+
bits.each do |b|
|
159
|
+
command << (b ? 1 : 0)
|
160
|
+
end
|
161
|
+
|
162
|
+
send_packet create_fins_frame(fins_header + command)
|
163
|
+
receive
|
164
|
+
end
|
165
|
+
|
166
|
+
def set_words_to_device(words, device)
|
167
|
+
open
|
168
|
+
count = words.size
|
169
|
+
unless available_words_range.include? count
|
170
|
+
raise ArgumentError,
|
171
|
+
"A count #{count} must be between #{available_words_range.first} and #{available_words_range.last} for #{__method__}"
|
172
|
+
end
|
173
|
+
|
174
|
+
device = device_by_name device
|
175
|
+
device = device.channel_device
|
176
|
+
|
177
|
+
command = [1, 2]
|
178
|
+
command << device_to_a(device)
|
179
|
+
command << int_to_a(count, 2)
|
180
|
+
words.each do |w|
|
181
|
+
command << int_to_a(w, 2)
|
182
|
+
end
|
183
|
+
|
184
|
+
send_packet create_fins_frame(fins_header + command)
|
185
|
+
receive
|
186
|
+
end
|
187
|
+
|
188
|
+
def query_node
|
189
|
+
send_packet create_query_node
|
190
|
+
res = receive
|
191
|
+
self.source_node = res[19]
|
192
|
+
end
|
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(TIMEOUT) 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]).zero?
|
224
|
+
|
225
|
+
res
|
226
|
+
end
|
227
|
+
@logger.debug("< #{dump_packet res}")
|
228
|
+
res
|
229
|
+
end
|
230
|
+
|
231
|
+
# max length:
|
232
|
+
# CS1W-ETN21, CJ1W-ETN21 : 2012
|
233
|
+
# CP1W-CIF41 option board : 540 (1004 if cpu is CP1L/H)
|
234
|
+
|
235
|
+
def available_bits_range(_device = nil)
|
236
|
+
case ethernet_module
|
237
|
+
when ETHERNET_ETN21
|
238
|
+
1..(2012 - 8)
|
239
|
+
when ETHERNET_CP1E
|
240
|
+
1..(540 - 8)
|
241
|
+
when ETHERNET_CP1L, ETHERNET_CP1H
|
242
|
+
1..(1004 - 8)
|
243
|
+
else
|
244
|
+
0..0
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
def available_words_range(_device = nil)
|
249
|
+
case ethernet_module
|
250
|
+
when ETHERNET_ETN21
|
251
|
+
1..((2012 - 8) / 2)
|
252
|
+
when ETHERNET_CP1E
|
253
|
+
1..((540 - 8) / 2)
|
254
|
+
when ETHERNET_CP1L, ETHERNET_CP1H
|
255
|
+
1..((1004 - 8) / 2)
|
256
|
+
else
|
257
|
+
0..0
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def device_by_name(name)
|
262
|
+
case name
|
263
|
+
when String
|
264
|
+
d = OmronDevice.new name
|
265
|
+
d.valid? ? d : nil
|
266
|
+
else
|
267
|
+
# it may be already OmronDevice
|
268
|
+
name
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
private
|
273
|
+
|
274
|
+
def fins_header
|
275
|
+
buf = [
|
276
|
+
0x80, # ICF
|
277
|
+
0x00, # RSV
|
278
|
+
0x02, # GCT
|
279
|
+
0x00, # DNA
|
280
|
+
0x01, # DA1
|
281
|
+
0x00, # DA2
|
282
|
+
0x00, # SNA
|
283
|
+
0x01, # SA1
|
284
|
+
0x00, # SA2
|
285
|
+
0x00 # SID
|
286
|
+
]
|
287
|
+
buf[2] = gateway_count - 1
|
288
|
+
buf[3] = destination_network
|
289
|
+
buf[4] = if destination_node == IOFINS_DESTINATION_NODE_FROM_IP
|
290
|
+
destination_ipv4.split('.').last.to_i
|
291
|
+
else
|
292
|
+
destination_node
|
293
|
+
end
|
294
|
+
buf[7] = source_node
|
295
|
+
buf[8] = source_unit
|
296
|
+
|
297
|
+
buf
|
298
|
+
end
|
299
|
+
|
300
|
+
def fins_tcp_cmnd_header
|
301
|
+
header = ['FINS'.bytes.to_a, 0, 0, 0, 0xc, 0, 0, 0, 2, 0, 0, 0, 0].flatten
|
302
|
+
header[19] = source_node == IOFINS_SOURCE_AUTO_NODE ? 0 : source_node
|
303
|
+
header
|
304
|
+
end
|
305
|
+
|
306
|
+
def device_code_of(device)
|
307
|
+
@@bit_codes ||= { nil => 0x30, '' => 0x30, 'W' => 0x31, 'H' => 0x32, 'A' => 0x33, 'T' => 0x09, 'C' => 0x09,
|
308
|
+
'D' => 0x02, 'E' => 0x0a, 'TK' => 0x06 }
|
309
|
+
@@word_codes ||= { nil => 0xB0, '' => 0xB0, 'W' => 0xB1, 'H' => 0xB2, 'A' => 0xB3, 'TIM' => 0x89, 'CNT' => 0x89,
|
310
|
+
'D' => 0x82, 'E' => 0x98, 'DR' => 0xbc }
|
311
|
+
if device.bit_device?
|
312
|
+
@@bit_codes[device.suffix]
|
313
|
+
else
|
314
|
+
@@word_codes[device.suffix]
|
315
|
+
end
|
316
|
+
end
|
317
|
+
|
318
|
+
def device_to_a(device)
|
319
|
+
a = []
|
320
|
+
a << device_code_of(device)
|
321
|
+
a << int_to_a(device.channel, 2)
|
322
|
+
a << (device.bit_device? ? (device.bit || 0) : 0)
|
323
|
+
a.flatten
|
324
|
+
end
|
325
|
+
|
326
|
+
def int_to_a(value, size)
|
327
|
+
a = []
|
328
|
+
(size - 1).downto 0 do |i|
|
329
|
+
a << ((value >> (i * 8)) & 0xff)
|
330
|
+
end
|
331
|
+
a
|
332
|
+
end
|
333
|
+
|
334
|
+
def to_int(a)
|
335
|
+
v = 0
|
336
|
+
a.each do |e|
|
337
|
+
v <<= 8
|
338
|
+
v += e
|
339
|
+
end
|
340
|
+
v
|
341
|
+
end
|
342
|
+
|
343
|
+
def dump_packet(packet)
|
344
|
+
a =
|
345
|
+
packet.map do |e|
|
346
|
+
e.to_s(16).rjust(2, '0')
|
347
|
+
end
|
348
|
+
"[#{a.join(', ')}]"
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
352
|
+
end
|
353
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The MIT License (MIT)
|
4
|
+
#
|
5
|
+
# Copyright (c) 2019 ITO SOFT DESIGN Inc.
|
6
|
+
#
|
7
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
8
|
+
# a copy of this software and associated documentation files (the
|
9
|
+
# "Software"), to deal in the Software without restriction, including
|
10
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
11
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
12
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
13
|
+
# the following conditions:
|
14
|
+
#
|
15
|
+
# The above copyright notice and this permission notice shall be
|
16
|
+
# included in all copies or substantial portions of the Software.
|
17
|
+
#
|
18
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
19
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
20
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
21
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
22
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
23
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
24
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
25
|
+
|
26
|
+
$LOAD_PATH.unshift File.dirname(__FILE__)
|
27
|
+
|
28
|
+
require 'omron_device'
|
29
|
+
require 'c_mode_protocol'
|
30
|
+
require 'fins_tcp_protocol'
|