ladder_drive 0.6.3 → 0.6.7
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 +5 -5
- data/Gemfile +7 -2
- data/Gemfile.lock +107 -62
- data/README.md +1 -0
- data/README_jp.md +2 -2
- data/ladder_drive.gemspec +12 -8
- data/lib/ladder_drive/cli.rb +16 -4
- data/lib/ladder_drive/config.rb +4 -1
- 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 +1 -11
- data/lib/ladder_drive/protocol/mitsubishi/fx_protocol.rb +0 -11
- data/lib/ladder_drive/protocol/mitsubishi/mc_protocol.rb +1 -11
- data/lib/ladder_drive/protocol/omron/c_mode_protocol.rb +224 -0
- data/lib/ladder_drive/protocol/omron/fins_tcp_protocol.rb +380 -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 +16 -5
- data/lib/ladder_drive/tasks/build.rb +1 -1
- data/lib/ladder_drive/version.rb +1 -1
- data/lib/ladder_drive.rb +3 -0
- data/lib/plc/emulator/emu_plc_server.rb +3 -1
- data/lib/plc/emulator/plc_plugins.rb +20 -13
- data/lib/plc/emulator/plugin_trigger_state.rb +152 -0
- data/plugins/ambient_plugin.rb +155 -0
- data/plugins/config/ambient.yaml.example +34 -0
- data/plugins/config/google_drive.yaml.example +39 -0
- data/plugins/config/ifttt.yaml.example +24 -0
- data/plugins/config/plc_mapper.yaml.example +36 -0
- data/plugins/config/slack.yaml.example +36 -0
- data/plugins/config/trello.yaml.example +41 -0
- data/plugins/ifttt_plugin.rb +4 -27
- data/plugins/plc_mapper_plugin.rb +27 -15
- data/plugins/slack_plugin.rb +14 -3
- data/plugins/trello_plugin.rb +30 -42
- metadata +72 -15
@@ -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
|
|
@@ -179,7 +169,7 @@ module Mitsubishi
|
|
179
169
|
res = []
|
180
170
|
len = 0
|
181
171
|
begin
|
182
|
-
Timeout.timeout(
|
172
|
+
Timeout.timeout(TIMEOUT) do
|
183
173
|
loop do
|
184
174
|
c = @socket.read(1)
|
185
175
|
next if c.nil? || c == ""
|
@@ -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,380 @@
|
|
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
|
+
TIMEOUT = 5.0
|
52
|
+
|
53
|
+
def initialize options={}
|
54
|
+
super
|
55
|
+
@socket = nil
|
56
|
+
@host = options[:host] || "192.168.250.1"
|
57
|
+
@port = options[:port] || 9600
|
58
|
+
@gateway_count = 3
|
59
|
+
@destination_network = 0
|
60
|
+
@destination_node = 0
|
61
|
+
@destination_unit = 0
|
62
|
+
@source_network = 0
|
63
|
+
@source_node = IOFINS_SOURCE_AUTO_NODE
|
64
|
+
@source_unit = 0
|
65
|
+
@ethernet_module = ETHERNET_ETN21
|
66
|
+
|
67
|
+
@tcp_error_code = 0
|
68
|
+
|
69
|
+
prepare_device_map
|
70
|
+
end
|
71
|
+
|
72
|
+
def open
|
73
|
+
open!
|
74
|
+
rescue =>e
|
75
|
+
p e
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def open!
|
80
|
+
if @socket.nil?
|
81
|
+
@socket = TCPSocket.open(@host, @port)
|
82
|
+
if @socket
|
83
|
+
source_node = IOFINS_SOURCE_AUTO_NODE
|
84
|
+
query_node
|
85
|
+
end
|
86
|
+
end
|
87
|
+
@socket
|
88
|
+
end
|
89
|
+
|
90
|
+
def close
|
91
|
+
@socket.close if @socket
|
92
|
+
@socket = nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def tcp_error?
|
96
|
+
tcp_error_code != 0
|
97
|
+
end
|
98
|
+
|
99
|
+
def create_query_node
|
100
|
+
header = [ "FINS".bytes.to_a, 0, 0, 0, 0xc, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0].flatten
|
101
|
+
header[19] = source_node == IOFINS_SOURCE_AUTO_NODE ? 0 : source_node
|
102
|
+
header
|
103
|
+
end
|
104
|
+
|
105
|
+
def create_fins_frame packet
|
106
|
+
packet = packet.flatten
|
107
|
+
header = [ "FINS".bytes.to_a, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0].flatten
|
108
|
+
header[4, 4] = int_to_a(packet.length + 8, 4)
|
109
|
+
header + packet
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_bits_from_device(count, device)
|
113
|
+
open
|
114
|
+
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
|
115
|
+
|
116
|
+
device = device_by_name device
|
117
|
+
raise ArgumentError.new("#{device.name} is not bit device!") unless device.bit_device?
|
118
|
+
|
119
|
+
command = [1, 1]
|
120
|
+
command << device_to_a(device)
|
121
|
+
command << int_to_a(count, 2)
|
122
|
+
|
123
|
+
send_packet create_fins_frame(fins_header + command)
|
124
|
+
res = receive
|
125
|
+
|
126
|
+
count.times.inject([]) do |a, i|
|
127
|
+
a << (res[16 + 10 + 4 + i] == 0 ? false : true)
|
128
|
+
a
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def get_words_from_device(count, device)
|
133
|
+
open
|
134
|
+
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
|
135
|
+
|
136
|
+
device = device_by_name device
|
137
|
+
device = device.channel_device
|
138
|
+
|
139
|
+
command = [1, 1]
|
140
|
+
command << device_to_a(device)
|
141
|
+
command << int_to_a(count, 2)
|
142
|
+
|
143
|
+
send_packet create_fins_frame(fins_header + command)
|
144
|
+
res = receive
|
145
|
+
count.times.inject([]) do |a, i|
|
146
|
+
a << to_int(res[16 + 10 + 4 + i * 2, 2])
|
147
|
+
a
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def set_bits_to_device(bits, device)
|
152
|
+
open
|
153
|
+
count = bits.size
|
154
|
+
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
|
155
|
+
|
156
|
+
device = device_by_name device
|
157
|
+
raise ArgumentError.new("#{device.name} is not bit device!") unless device.bit_device?
|
158
|
+
|
159
|
+
command = [1, 2]
|
160
|
+
command << device_to_a(device)
|
161
|
+
command << int_to_a(count, 2)
|
162
|
+
bits.each do |b|
|
163
|
+
command << (b ? 1 : 0)
|
164
|
+
end
|
165
|
+
|
166
|
+
send_packet create_fins_frame(fins_header + command)
|
167
|
+
res = receive
|
168
|
+
end
|
169
|
+
|
170
|
+
def set_words_to_device(words, device)
|
171
|
+
open
|
172
|
+
count = words.size
|
173
|
+
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
|
174
|
+
|
175
|
+
device = device_by_name device
|
176
|
+
device = device.channel_device
|
177
|
+
|
178
|
+
command = [1, 2]
|
179
|
+
command << device_to_a(device)
|
180
|
+
command << int_to_a(count, 2)
|
181
|
+
words.each do |w|
|
182
|
+
command << int_to_a(w, 2)
|
183
|
+
end
|
184
|
+
|
185
|
+
send_packet create_fins_frame(fins_header + command)
|
186
|
+
res = receive
|
187
|
+
end
|
188
|
+
|
189
|
+
def query_node
|
190
|
+
send_packet create_query_node
|
191
|
+
res = receive
|
192
|
+
self.source_node = res[19]
|
193
|
+
end
|
194
|
+
|
195
|
+
|
196
|
+
def send_packet packet
|
197
|
+
@socket.write(packet.flatten.pack("c*"))
|
198
|
+
@socket.flush
|
199
|
+
@logger.debug("> #{dump_packet packet}")
|
200
|
+
end
|
201
|
+
|
202
|
+
def receive
|
203
|
+
res = []
|
204
|
+
len = 0
|
205
|
+
begin
|
206
|
+
Timeout.timeout(TIMEOUT) do
|
207
|
+
loop do
|
208
|
+
c = @socket.getc
|
209
|
+
next if c.nil? || c == ""
|
210
|
+
|
211
|
+
res << c.bytes.first
|
212
|
+
next if res.length < 8
|
213
|
+
|
214
|
+
len = to_int(res[4, 4])
|
215
|
+
next if res.length < 8 + len
|
216
|
+
|
217
|
+
tcp_command = to_int(res[8, 4])
|
218
|
+
case tcp_command
|
219
|
+
when 3 # ERROR
|
220
|
+
raise "Invalidate tcp header: #{res}"
|
221
|
+
end
|
222
|
+
break
|
223
|
+
end
|
224
|
+
end
|
225
|
+
raise "Response error code: #{res[15]}" unless res[15] == 0
|
226
|
+
res
|
227
|
+
end
|
228
|
+
@logger.debug("< #{dump_packet res}")
|
229
|
+
res
|
230
|
+
end
|
231
|
+
|
232
|
+
# max length:
|
233
|
+
# CS1W-ETN21, CJ1W-ETN21 : 2012
|
234
|
+
# CP1W-CIF41 option board : 540 (1004 if cpu is CP1L/H)
|
235
|
+
|
236
|
+
def available_bits_range device=nil
|
237
|
+
case ethernet_module
|
238
|
+
when ETHERNET_ETN21
|
239
|
+
1..(2012 - 8)
|
240
|
+
when ETHERNET_CP1E
|
241
|
+
1..(540 - 8)
|
242
|
+
when ETHERNET_CP1L, ETHERNET_CP1H
|
243
|
+
1..(1004 - 8)
|
244
|
+
else
|
245
|
+
0..0
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
def available_words_range device=nil
|
250
|
+
case ethernet_module
|
251
|
+
when ETHERNET_ETN21
|
252
|
+
1..((2012 - 8) / 2)
|
253
|
+
when ETHERNET_CP1E
|
254
|
+
1..((540 - 8) / 2)
|
255
|
+
when ETHERNET_CP1L, ETHERNET_CP1H
|
256
|
+
1..((1004 - 8) / 2)
|
257
|
+
else
|
258
|
+
0..0
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def device_by_name name
|
263
|
+
case name
|
264
|
+
when String
|
265
|
+
d = OmronDevice.new name
|
266
|
+
d.valid? ? d : nil
|
267
|
+
when EscDevice
|
268
|
+
local_device_of name
|
269
|
+
else
|
270
|
+
# it may be already OmronDevice
|
271
|
+
name
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
private
|
276
|
+
|
277
|
+
def fins_header
|
278
|
+
buf = [
|
279
|
+
0x80, # ICF
|
280
|
+
0x00, # RSV
|
281
|
+
0x02, # GCT
|
282
|
+
0x00, # DNA
|
283
|
+
0x01, # DA1
|
284
|
+
0x00, # DA2
|
285
|
+
0x00, # SNA
|
286
|
+
0x01, # SA1
|
287
|
+
0x00, # SA2
|
288
|
+
0x00, # SID
|
289
|
+
]
|
290
|
+
buf[2] = gateway_count - 1
|
291
|
+
buf[3] = destination_network
|
292
|
+
if destination_node == IOFINS_DESTINATION_NODE_FROM_IP
|
293
|
+
buf[4] = destination_ipv4.split(".").last.to_i
|
294
|
+
else
|
295
|
+
buf[4] = destination_node
|
296
|
+
end
|
297
|
+
buf[7] = source_node
|
298
|
+
buf[8] = source_unit
|
299
|
+
|
300
|
+
buf
|
301
|
+
end
|
302
|
+
|
303
|
+
def fins_tcp_cmnd_header
|
304
|
+
header = [ "FINS".bytes.to_a, 0, 0, 0, 0xc, 0, 0, 0, 2, 0, 0, 0, 0].flatten
|
305
|
+
header[19] = source_node == IOFINS_SOURCE_AUTO_NODE ? 0 : source_node
|
306
|
+
header
|
307
|
+
end
|
308
|
+
|
309
|
+
def device_code_of device
|
310
|
+
@@bit_codes ||= { nil => 0x30, "" => 0x30, "W" => 0x31, "H" => 0x32, "A" => 0x33, "T" => 0x09, "C" => 0x09, "D" => 0x02, "E" => 0x0a, "TK" => 0x06 }
|
311
|
+
@@word_codes ||= { nil => 0xB0, "" => 0xB0, "W" => 0xB1, "H" => 0xB2, "A" => 0xB3, "TIM" => 0x89, "CNT" => 0x89, "D" => 0x82, "E" => 0x98, "DR" => 0xbc }
|
312
|
+
if device.bit_device?
|
313
|
+
@@bit_codes[device.suffix]
|
314
|
+
else
|
315
|
+
@@word_codes[device.suffix]
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
def device_to_a device
|
320
|
+
a = []
|
321
|
+
a << device_code_of(device)
|
322
|
+
a << int_to_a(device.channel, 2)
|
323
|
+
a << (device.bit_device? ? (device.bit || 0) : 0)
|
324
|
+
a.flatten
|
325
|
+
end
|
326
|
+
|
327
|
+
|
328
|
+
# FIXME: It's dummy currently.
|
329
|
+
def prepare_device_map
|
330
|
+
@conv_dev_dict ||= begin
|
331
|
+
h = {}
|
332
|
+
[
|
333
|
+
["X", "0.0", 1024],
|
334
|
+
["Y", "400.0", 1024],
|
335
|
+
["M", "M0.0", 1024],
|
336
|
+
["C", "C0", 256],
|
337
|
+
["T", "T0", 256],
|
338
|
+
["L", "H0.0", 1024],
|
339
|
+
["SC", "M400.0", 1024],
|
340
|
+
["D", "D0", 1024],
|
341
|
+
["H", "D1024", 1024],
|
342
|
+
["SD", "D2048", 1024],
|
343
|
+
["PRG", "D3072", 1024] # ..D4095
|
344
|
+
].each do |s,d,c|
|
345
|
+
h[s] = [OmronDevice.new(d), c]
|
346
|
+
end
|
347
|
+
h
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
def int_to_a value, size
|
352
|
+
a = []
|
353
|
+
(size - 1).downto 0 do |i|
|
354
|
+
a << ((value >> (i * 8)) & 0xff)
|
355
|
+
end
|
356
|
+
a
|
357
|
+
end
|
358
|
+
|
359
|
+
def to_int a
|
360
|
+
v = 0
|
361
|
+
a.each do |e|
|
362
|
+
v <<= 8
|
363
|
+
v += e
|
364
|
+
end
|
365
|
+
v
|
366
|
+
end
|
367
|
+
|
368
|
+
def dump_packet packet
|
369
|
+
a =
|
370
|
+
packet.map{|e|
|
371
|
+
e.to_s(16).rjust(2, '0')
|
372
|
+
}
|
373
|
+
"[#{a.join(', ')}]"
|
374
|
+
end
|
375
|
+
|
376
|
+
end
|
377
|
+
|
378
|
+
end
|
379
|
+
end
|
380
|
+
end
|