rmodbus 0.5.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/NEWS.md +52 -0
- data/README.md +87 -0
- data/Rakefile +22 -36
- data/examples/perfomance_rtu.rb +35 -37
- data/examples/perfomance_tcp.rb +36 -38
- data/examples/use_rtu_via_tcp_modbus.rb +8 -5
- data/examples/use_tcp_modbus.rb +10 -6
- data/lib/rmodbus/client.rb +52 -174
- data/lib/rmodbus/common.rb +45 -18
- data/lib/rmodbus/{exceptions.rb → errors.rb} +3 -0
- data/lib/rmodbus/ext.rb +25 -2
- data/lib/rmodbus/proxy.rb +54 -0
- data/lib/rmodbus/{crc16.rb → rtu.rb} +73 -2
- data/lib/rmodbus/rtu_client.rb +20 -116
- data/lib/rmodbus/rtu_server.rb +28 -57
- data/lib/rmodbus/rtu_slave.rb +59 -0
- data/lib/rmodbus/rtu_via_tcp_client.rb +22 -86
- data/lib/rmodbus/rtu_via_tcp_server.rb +31 -95
- data/lib/rmodbus/rtu_via_tcp_slave.rb +58 -0
- data/lib/rmodbus/{parsers.rb → server.rb} +24 -15
- data/lib/rmodbus/slave.rb +268 -0
- data/lib/rmodbus/sp.rb +45 -0
- data/lib/rmodbus/tcp.rb +49 -0
- data/lib/rmodbus/tcp_client.rb +19 -88
- data/lib/rmodbus/tcp_server.rb +16 -19
- data/lib/rmodbus/tcp_slave.rb +64 -0
- data/lib/rmodbus/version.rb +17 -0
- data/lib/rmodbus.rb +20 -4
- data/spec/client_spec.rb +19 -45
- data/spec/exception_spec.rb +26 -27
- data/spec/ext_spec.rb +24 -1
- data/spec/logging_spec.rb +31 -37
- data/spec/proxy_spec.rb +73 -0
- data/spec/read_rtu_response_spec.rb +2 -4
- data/spec/rtu_client_spec.rb +17 -19
- data/spec/rtu_server_spec.rb +1 -3
- data/spec/rtu_via_tcp_client_spec.rb +69 -63
- data/spec/slave_spec.rb +55 -0
- data/spec/tcp_client_spec.rb +77 -69
- data/spec/tcp_server_spec.rb +34 -49
- metadata +123 -37
- data/AUTHORS +0 -3
- data/ChangeLog +0 -82
- data/LICENSE +0 -675
- data/README +0 -53
- data/examples/add_new_function.rb +0 -19
data/lib/rmodbus/client.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# RModBus - free implementation of ModBus protocol on Ruby.
|
2
2
|
#
|
3
|
-
# Copyright (C) 2008 Timin Aleksey
|
3
|
+
# Copyright (C) 2008-2011 Timin Aleksey
|
4
4
|
#
|
5
5
|
# This program is free software: you can redistribute it and/or modify
|
6
6
|
# it under the terms of the GNU General Public License as published by
|
@@ -12,202 +12,80 @@
|
|
12
12
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
13
|
# GNU General Public License for more details.
|
14
14
|
|
15
|
-
require 'rmodbus/common'
|
16
|
-
require 'rmodbus/exceptions'
|
17
|
-
require 'rmodbus/ext'
|
18
|
-
|
19
|
-
|
20
15
|
module ModBus
|
16
|
+
# @abstract
|
21
17
|
class Client
|
22
18
|
include Errors
|
23
|
-
include Common
|
24
|
-
# Number of times to retry on connection and read timeouts
|
25
|
-
attr_accessor :read_retries, :read_retry_timeout
|
26
|
-
|
27
|
-
def connection_retries
|
28
|
-
warn "[DEPRECATION] `connection_retries` is deprecated. Please don't use it."
|
29
|
-
@connection_retries
|
30
|
-
end
|
31
|
-
|
32
|
-
def connection_retries=(value)
|
33
|
-
warn "[DEPRECATION] `connection_retries=` is deprecated. Please don't use it."
|
34
|
-
@connection_retries = value
|
35
|
-
end
|
36
|
-
|
37
19
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
end
|
52
|
-
# Read value *ncoils* coils starting with *addr*
|
53
|
-
#
|
54
|
-
# Return array of their values
|
55
|
-
def read_coils(addr, ncoils)
|
56
|
-
query("\x1" + addr.to_word + ncoils.to_word).unpack_bits[0..ncoils-1]
|
57
|
-
end
|
58
|
-
|
59
|
-
# Read value *ncoils* discrete inputs starting with *addr*
|
60
|
-
#
|
61
|
-
# Return array of their values
|
62
|
-
def read_discrete_inputs(addr, ncoils)
|
63
|
-
query("\x2" + addr.to_word + ncoils.to_word).unpack_bits[0..ncoils-1]
|
64
|
-
end
|
65
|
-
|
66
|
-
# Deprecated version of read_discrete_inputs
|
67
|
-
def read_discret_inputs(addr, ncoils)
|
68
|
-
#warn "[DEPRECATION] `read_discret_inputs` is deprecated. Please use `read_discrete_inputs` instead."
|
69
|
-
read_discrete_inputs(addr, ncoils)
|
70
|
-
end
|
71
|
-
|
72
|
-
# Read value *nreg* holding registers starting with *addr*
|
73
|
-
#
|
74
|
-
# Return array of their values
|
75
|
-
def read_holding_registers(addr, nreg)
|
76
|
-
query("\x3" + addr.to_word + nreg.to_word).unpack('n*')
|
77
|
-
end
|
78
|
-
|
79
|
-
# Read value *nreg* input registers starting with *addr*
|
80
|
-
#
|
81
|
-
# Return array of their values
|
82
|
-
def read_input_registers(addr, nreg)
|
83
|
-
query("\x4" + addr.to_word + nreg.to_word).unpack('n*')
|
84
|
-
end
|
85
|
-
|
86
|
-
# Write *val* in *addr* coil
|
87
|
-
#
|
88
|
-
# if *val* lager 0 write 1
|
89
|
-
#
|
90
|
-
# Return self
|
91
|
-
def write_single_coil(addr, val)
|
92
|
-
if val == 0
|
93
|
-
query("\x5" + addr.to_word + 0.to_word)
|
20
|
+
# Initialized client (alias :connect)
|
21
|
+
# @example
|
22
|
+
# Client.new(any_args) do |client|
|
23
|
+
# client.closed? #=> false
|
24
|
+
# end
|
25
|
+
# @param *args depends on implementation
|
26
|
+
# @yield return client object and close it before exit
|
27
|
+
# @return [Client] client object
|
28
|
+
def initialize(*args, &block)
|
29
|
+
@io = open_connection(*args)
|
30
|
+
if block_given?
|
31
|
+
yield self
|
32
|
+
close
|
94
33
|
else
|
95
|
-
|
34
|
+
self
|
96
35
|
end
|
97
|
-
self
|
98
36
|
end
|
99
37
|
|
100
|
-
|
101
|
-
|
102
|
-
# Return self
|
103
|
-
def write_single_register(addr, val)
|
104
|
-
query("\x6" + addr.to_word + val.to_word)
|
105
|
-
self
|
38
|
+
class << self
|
39
|
+
alias_method :connect, :new
|
106
40
|
end
|
107
41
|
|
108
|
-
#
|
42
|
+
# Given slave object
|
43
|
+
# @example
|
44
|
+
# cl = Client.new
|
45
|
+
# cl.with_slave(1) do |slave|
|
46
|
+
# slave.holding_registers[0..100]
|
47
|
+
# end
|
109
48
|
#
|
110
|
-
#
|
111
|
-
#
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
sum |= 1 if val[i] > 0
|
119
|
-
end
|
120
|
-
|
121
|
-
s_val = ""
|
122
|
-
nbyte.times do
|
123
|
-
s_val << (sum & 0xff).chr
|
124
|
-
sum >>= 8
|
125
|
-
end
|
126
|
-
|
127
|
-
query("\xf" + addr.to_word + val.size.to_word + nbyte.chr + s_val)
|
128
|
-
self
|
129
|
-
end
|
130
|
-
|
131
|
-
# Write *val* in registers starting with *addr*
|
132
|
-
#
|
133
|
-
# *val* it is array of integer
|
134
|
-
#
|
135
|
-
# Return self
|
136
|
-
def write_multiple_registers(addr, val)
|
137
|
-
s_val = ""
|
138
|
-
val.each do |reg|
|
139
|
-
s_val << reg.to_word
|
49
|
+
# @param [Integer, #read] uid slave devise
|
50
|
+
# @return [Slave] slave object
|
51
|
+
def with_slave(uid, &block)
|
52
|
+
slave = get_slave(uid, @io)
|
53
|
+
if block_given?
|
54
|
+
yield slave
|
55
|
+
else
|
56
|
+
slave
|
140
57
|
end
|
141
|
-
|
142
|
-
query("\x10" + addr.to_word + val.size.to_word + (val.size * 2).chr + s_val)
|
143
|
-
self
|
144
58
|
end
|
145
59
|
|
146
|
-
#
|
147
|
-
#
|
148
|
-
|
149
|
-
|
150
|
-
query("\x16" + addr.to_word + and_mask.to_word + or_mask.to_word)
|
151
|
-
self
|
60
|
+
# Check connections
|
61
|
+
# @return [Boolean]
|
62
|
+
def closed?
|
63
|
+
@io.closed?
|
152
64
|
end
|
153
65
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
timeout(@read_retry_timeout, ModBusTimeout) do
|
158
|
-
send_pdu(pdu)
|
159
|
-
pdu = read_pdu
|
160
|
-
end
|
161
|
-
rescue ModBusTimeout => err
|
162
|
-
log "Timeout of read operation: (#{@read_retries - tried})"
|
163
|
-
tried += 1
|
164
|
-
retry unless tried >= @read_retries
|
165
|
-
raise ModBusTimeout.new, "Timed out during read attempt"
|
166
|
-
end
|
167
|
-
|
168
|
-
return nil if pdu.size == 0
|
169
|
-
|
170
|
-
if pdu.getbyte(0) >= 0x80
|
171
|
-
exc_id = pdu.getbyte(1)
|
172
|
-
raise Exceptions[exc_id] unless Exceptions[exc_id].nil?
|
173
|
-
|
174
|
-
raise ModBusException.new, "Unknown error"
|
175
|
-
end
|
176
|
-
pdu[2..-1]
|
66
|
+
# Close connections
|
67
|
+
def close
|
68
|
+
@io.close unless @io.closed?
|
177
69
|
end
|
178
70
|
|
179
71
|
protected
|
72
|
+
def open_connection(*args)
|
73
|
+
#Stub conn object
|
74
|
+
@io = Object.new
|
180
75
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
def read_pdu
|
185
|
-
end
|
76
|
+
@io.instance_eval """
|
77
|
+
def close
|
78
|
+
end
|
186
79
|
|
187
|
-
|
80
|
+
def closed?
|
81
|
+
true
|
82
|
+
end
|
83
|
+
"""
|
84
|
+
@io
|
188
85
|
end
|
189
86
|
|
190
|
-
|
191
|
-
|
192
|
-
# Read the slave_id and function code
|
193
|
-
msg = io.read(2)
|
194
|
-
function_code = msg.getbyte(1)
|
195
|
-
case function_code
|
196
|
-
when 1,2,3,4 then
|
197
|
-
# read the third byte to find out how much more
|
198
|
-
# we need to read + CRC
|
199
|
-
msg += io.read(1)
|
200
|
-
msg += io.read(msg.getbyte(2)+2)
|
201
|
-
when 5,6,15,16 then
|
202
|
-
# We just read in an additional 6 bytes
|
203
|
-
msg += io.read(6)
|
204
|
-
when 22 then
|
205
|
-
msg += io.read(8)
|
206
|
-
when 0x80..0xff then
|
207
|
-
msg += io.read(4)
|
208
|
-
else
|
209
|
-
raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
|
210
|
-
end
|
87
|
+
def get_slave(uid,io)
|
88
|
+
Slave.new(uid, io)
|
211
89
|
end
|
212
90
|
end
|
213
91
|
end
|
data/lib/rmodbus/common.rb
CHANGED
@@ -1,23 +1,50 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 Timin Aleksey
|
4
|
+
# Copyright (C) 2010 Kelley Reynolds
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
|
1
16
|
module ModBus
|
2
|
-
|
17
|
+
module Common
|
18
|
+
# @return [Boolean] debug mode
|
19
|
+
# default false
|
20
|
+
attr_accessor :debug
|
21
|
+
|
22
|
+
@debug = false
|
3
23
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
24
|
+
private
|
25
|
+
# Put log message on standart output
|
26
|
+
# @param [String] msg message for log
|
27
|
+
def log(msg)
|
28
|
+
$stdout.puts msg if @debug
|
29
|
+
end
|
8
30
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
31
|
+
# Convert string of byte to string for log
|
32
|
+
# @example
|
33
|
+
# logging_bytes("\x1\xa\x8") => "[01][0a][08]"
|
34
|
+
# @param [String] msg input string
|
35
|
+
# @return [String] readable string of bytes
|
36
|
+
def logging_bytes(msg)
|
37
|
+
result = ""
|
38
|
+
msg.each_byte do |c|
|
39
|
+
byte = if c < 16
|
40
|
+
'0' + c.to_s(16)
|
41
|
+
else
|
42
|
+
c.to_s(16)
|
43
|
+
end
|
44
|
+
result << "[#{byte}]"
|
45
|
+
end
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|
22
49
|
end
|
23
50
|
|
data/lib/rmodbus/ext.rb
CHANGED
@@ -27,19 +27,42 @@ class String
|
|
27
27
|
end
|
28
28
|
array_bit
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
end
|
32
32
|
|
33
33
|
class Integer
|
34
34
|
|
35
|
+
# Shortcut or turning an integer into a word
|
35
36
|
def to_word
|
36
|
-
|
37
|
+
[self].pack('n')
|
37
38
|
end
|
38
39
|
|
39
40
|
end
|
40
41
|
|
41
42
|
class Array
|
42
43
|
|
44
|
+
# Given an array of 16bit Fixnum, we turn it into 32bit Int in big-endian order, halving the size
|
45
|
+
def to_32f
|
46
|
+
raise "Array requires an even number of elements to pack to 32bits: was #{self.size}" unless self.size.even?
|
47
|
+
self.each_slice(2).map { |(lsb, msb)| [msb, lsb].pack('n*').unpack('g')[0] }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Given an array of 32bit Floats, we turn it into an array of 16bit Fixnums, doubling the size
|
51
|
+
def from_32f
|
52
|
+
self.pack('g*').unpack('n*').each_slice(2).map { |arr| arr.reverse }.flatten
|
53
|
+
end
|
54
|
+
|
55
|
+
# Given an array of 16bit Fixnum, we turn it into 32bit Float in big-endian order, halving the size
|
56
|
+
def to_32i
|
57
|
+
raise "Array requires an even number of elements to pack to 32bits: was #{self.size}" unless self.size.even?
|
58
|
+
self.each_slice(2).map { |(lsb, msb)| [msb, lsb].pack('n*').unpack('N')[0] }
|
59
|
+
end
|
60
|
+
|
61
|
+
# Given an array of 32bit Fixnum, we turn it into an array of 16bit fixnums, doubling the size
|
62
|
+
def from_32i
|
63
|
+
self.pack('N*').unpack('n*').each_slice(2).map { |arr| arr.reverse }.flatten
|
64
|
+
end
|
65
|
+
|
43
66
|
def pack_to_word
|
44
67
|
word = 0
|
45
68
|
s = ""
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# ReadOnly and ReadWrite hash interface for modbus registers and coils
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 Kelley Reynolds
|
4
|
+
#
|
5
|
+
# This program is free software: you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU General Public License as published by
|
7
|
+
# the Free Software Foundation, either version 3 of the License, or
|
8
|
+
# (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This program is distributed in the hope that it will be useful,
|
11
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
|
+
# GNU General Public License for more details.
|
14
|
+
module ModBus
|
15
|
+
# Given a slave and a type of operation, execute a single or multiple read using hash syntax
|
16
|
+
class ReadOnlyProxy
|
17
|
+
# Initialize a proxy for a slave and a type of operation
|
18
|
+
def initialize(slave, type)
|
19
|
+
@slave, @type = slave, type
|
20
|
+
end
|
21
|
+
|
22
|
+
# Read single or multiple values from a modbus slave depending on whether a Fixnum or a Range was given.
|
23
|
+
# Note that in the case of multiples, a pluralized version of the method is sent to the slave
|
24
|
+
def [](key)
|
25
|
+
if key.instance_of?(Fixnum)
|
26
|
+
@slave.send("read_#{@type}", key, 1)
|
27
|
+
elsif key.instance_of?(Range)
|
28
|
+
@slave.send("read_#{@type}s", key.first, key.count)
|
29
|
+
else
|
30
|
+
raise ProxyException, "Invalid argument, must be integer or range. Was #{key.class}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class ReadWriteProxy < ReadOnlyProxy
|
36
|
+
# Write single or multiple values to a modbus slave depending on whether a Fixnum or a Range was given.
|
37
|
+
# Note that in the case of multiples, a pluralized version of the method is sent to the slave. Also when
|
38
|
+
# writing multiple values, the number of elements must match the number of registers in the range or an exception is raised
|
39
|
+
def []=(key, val)
|
40
|
+
if key.instance_of?(Fixnum)
|
41
|
+
@slave.send("write_#{@type}", key, val)
|
42
|
+
elsif key.instance_of?(Range)
|
43
|
+
if key.count != val.size
|
44
|
+
raise ProxyException, "The size of the range must match the size of the values (#{key.count} != #{val.size})"
|
45
|
+
end
|
46
|
+
|
47
|
+
@slave.send("write_#{@type}s", key.first, val)
|
48
|
+
else
|
49
|
+
raise ProxyException, "Invalid argument, must be integer or range. Was #{key.class}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
2
|
#
|
3
|
-
# Copyright (C) 2010 Timin Aleksey
|
3
|
+
# Copyright (C) 2010 - 2011 Timin Aleksey
|
4
|
+
# Copyright (C) 2010 Kelley Reynolds
|
4
5
|
#
|
5
6
|
# This program is free software: you can redistribute it and/or modify
|
6
7
|
# it under the terms of the GNU General Public License as published by
|
@@ -13,7 +14,75 @@
|
|
13
14
|
# GNU General Public License for more details.
|
14
15
|
|
15
16
|
module ModBus
|
16
|
-
module
|
17
|
+
module RTU
|
18
|
+
private
|
19
|
+
|
20
|
+
# We have to read specific amounts of numbers of bytes from the network depending on the function code and content
|
21
|
+
def read_rtu_response(io)
|
22
|
+
# Read the slave_id and function code
|
23
|
+
msg = io.read(2)
|
24
|
+
function_code = msg.getbyte(1)
|
25
|
+
case function_code
|
26
|
+
when 1,2,3,4 then
|
27
|
+
# read the third byte to find out how much more
|
28
|
+
# we need to read + CRC
|
29
|
+
msg += io.read(1)
|
30
|
+
msg += io.read(msg.getbyte(2)+2)
|
31
|
+
when 5,6,15,16 then
|
32
|
+
# We just read in an additional 6 bytes
|
33
|
+
msg += io.read(6)
|
34
|
+
when 22 then
|
35
|
+
msg += io.read(8)
|
36
|
+
when 0x80..0xff then
|
37
|
+
msg += io.read(4)
|
38
|
+
else
|
39
|
+
raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def read_rtu_request(io)
|
44
|
+
# Read the slave_id and function code
|
45
|
+
msg = io.read(2)
|
46
|
+
|
47
|
+
# If msg is nil, then our client never sent us anything and it's time to disconnect
|
48
|
+
return if msg.nil?
|
49
|
+
|
50
|
+
function_code = msg.getbyte(1)
|
51
|
+
if [1, 2, 3, 4, 5, 6].include?(function_code)
|
52
|
+
# read 6 more bytes and return the message total message
|
53
|
+
msg += io.read(6)
|
54
|
+
elsif [15, 16].include?(function_code)
|
55
|
+
# Read in first register, register count, and data bytes
|
56
|
+
msg += io.read(5)
|
57
|
+
# Read in however much data we need to + 2 CRC bytes
|
58
|
+
msg += io.read(msg.getbyte(6) + 2)
|
59
|
+
else
|
60
|
+
raise ModBus::Errors::IllegalFunction, "Illegal function: #{function_code}"
|
61
|
+
end
|
62
|
+
|
63
|
+
log "Server RX (#{msg.size} bytes): #{logging_bytes(msg)}"
|
64
|
+
|
65
|
+
msg
|
66
|
+
end
|
67
|
+
|
68
|
+
def serv_rtu_requests(io, &blk)
|
69
|
+
loop do
|
70
|
+
# read the RTU message
|
71
|
+
msg = read_rtu_request(io)
|
72
|
+
# If there is no RTU message, we're done serving this client
|
73
|
+
break if msg.nil?
|
74
|
+
|
75
|
+
if msg.getbyte(0) == @uid and msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
|
76
|
+
pdu = yield msg
|
77
|
+
resp = @uid.chr + pdu
|
78
|
+
resp << crc16(resp).to_word
|
79
|
+
log "Server TX (#{resp.size} bytes): #{logging_bytes(resp)}"
|
80
|
+
io.write resp
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# Calc CRC16 for massage
|
17
86
|
def crc16(msg)
|
18
87
|
crc_lo = 0xff
|
19
88
|
crc_hi = 0xff
|
@@ -65,5 +134,7 @@ module ModBus
|
|
65
134
|
0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
|
66
135
|
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
|
67
136
|
0x40]
|
137
|
+
|
68
138
|
end
|
69
139
|
end
|
140
|
+
|
data/lib/rmodbus/rtu_client.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
2
|
#
|
3
|
-
# Copyright (C) 2009 Timin Aleksey
|
3
|
+
# Copyright (C) 2009-2011 Timin Aleksey
|
4
4
|
#
|
5
5
|
# This program is free software: you can redistribute it and/or modify
|
6
6
|
# it under the terms of the GNU General Public License as published by
|
@@ -11,127 +11,31 @@
|
|
11
11
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
12
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
13
13
|
# GNU General Public License for more details.
|
14
|
-
require 'rmodbus/crc16'
|
15
|
-
|
16
|
-
begin
|
17
|
-
require 'rubygems'
|
18
|
-
rescue
|
19
|
-
end
|
20
|
-
require 'serialport'
|
21
|
-
|
22
14
|
module ModBus
|
23
|
-
|
15
|
+
# RTU client implementation
|
16
|
+
# @example
|
17
|
+
# RTUClient.connect('/dev/ttyS1', 9600) do |cl|
|
18
|
+
# cl.with_slave(uid) do |slave|
|
19
|
+
# slave.holding_registers[0..100]
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @see RTUClient#open_connection
|
24
|
+
# @see Client#initialize
|
24
25
|
class RTUClient < Client
|
25
|
-
|
26
|
-
include
|
27
|
-
attr_reader :port, :baud, :slave, :data_bits, :stop_bits, :parity, :read_timeout
|
28
|
-
attr_accessor :debug
|
29
|
-
|
30
|
-
# Connect with RTU server
|
31
|
-
#
|
32
|
-
# port - serial port of connections with RTU server
|
33
|
-
#
|
34
|
-
# baud - rate sp of connections with RTU server
|
35
|
-
#
|
36
|
-
# slaveaddr - slave ID of the RTU server
|
37
|
-
#
|
38
|
-
# Options:
|
39
|
-
#
|
40
|
-
# :data_bits => from 5 to 8
|
41
|
-
#
|
42
|
-
# :stop_bits => 1 or 2
|
43
|
-
#
|
44
|
-
# :parity => NONE, EVEN or ODD
|
45
|
-
#
|
46
|
-
# RTUClient.connect('/dev/port1') do |cl|
|
47
|
-
#
|
48
|
-
# put cl.read_holding_registers(0, 10)
|
49
|
-
#
|
50
|
-
# end
|
51
|
-
def self.connect(port, baud=9600, slaveaddr=1, options = {})
|
52
|
-
cl = RTUClient.new(port, baud, slaveaddr, options)
|
53
|
-
yield cl
|
54
|
-
cl.close
|
55
|
-
end
|
56
|
-
|
57
|
-
# Connect with RTU server
|
58
|
-
#
|
59
|
-
# port - serial port of connections with RTU server
|
60
|
-
#
|
61
|
-
# baud - rate sp of connections with RTU server
|
62
|
-
#
|
63
|
-
# data_bits - from 5 to 8
|
64
|
-
#
|
65
|
-
# stop_bits - 1 or 2
|
66
|
-
#
|
67
|
-
# parity - NONE, EVEN or ODD
|
68
|
-
#
|
69
|
-
# slaveaddr - slave ID of the RTU server # Connect with RTU server
|
70
|
-
#
|
71
|
-
# port - serial port of connections with RTU server
|
72
|
-
#
|
73
|
-
# baud - rate sp of connections with RTU server
|
74
|
-
#
|
75
|
-
# slaveaddr - slave ID of the RTU server
|
76
|
-
#
|
77
|
-
# Options:
|
78
|
-
#
|
79
|
-
# :data_bits => from 5 to 8
|
80
|
-
#
|
81
|
-
# :stop_bits => 1 or 2
|
82
|
-
#
|
83
|
-
# :parity => NONE, EVEN or ODD
|
84
|
-
#
|
85
|
-
# :read_timeout => default 5 ms
|
86
|
-
def initialize(port, baud=9600, slaveaddr=1, options = {})
|
87
|
-
@port, @baud, @slave = port, baud, slaveaddr
|
88
|
-
|
89
|
-
@data_bits, @stop_bits, @parity, @read_timeout = 8, 1, SerialPort::NONE, 5
|
90
|
-
|
91
|
-
@data_bits = options[:data_bits] unless options[:data_bits].nil?
|
92
|
-
@stop_bits = options[:stop_bits] unless options[:stop_bits].nil?
|
93
|
-
@parity = options[:parity] unless options[:parity].nil?
|
94
|
-
@read_timeout = options[:read_timeout] unless options[:read_timeout].nil?
|
95
|
-
|
96
|
-
@debug = false
|
97
|
-
|
98
|
-
@sp = SerialPort.new(@port, @baud, @data_bits, @stop_bits, @parity)
|
99
|
-
@sp.read_timeout = @read_timeout
|
100
|
-
|
101
|
-
super()
|
102
|
-
end
|
103
|
-
|
104
|
-
def close
|
105
|
-
@sp.close unless @sp.closed?
|
106
|
-
end
|
107
|
-
|
108
|
-
def closed?
|
109
|
-
@sp.closed?
|
110
|
-
end
|
26
|
+
include RTU
|
27
|
+
include SP
|
111
28
|
|
112
29
|
protected
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
|
30
|
+
# Open serial port
|
31
|
+
# @see SP#open_serial_port
|
32
|
+
def open_connection(port, baud=9600, opts = {})
|
33
|
+
open_serial_port(port, baud, opts)
|
119
34
|
end
|
120
35
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
log "Rx (#{msg.size} bytes): " + logging_bytes(msg)
|
125
|
-
|
126
|
-
if msg.getbyte(0) == @slave
|
127
|
-
return msg[1..-3] if msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
|
128
|
-
log "Ignore package: don't match CRC"
|
129
|
-
else
|
130
|
-
log "Ignore package: don't match slave ID"
|
131
|
-
end
|
132
|
-
loop do
|
133
|
-
#waite timeout
|
134
|
-
end
|
36
|
+
# @private
|
37
|
+
def get_slave(uid, io)
|
38
|
+
RTUSlave.new(uid, io)
|
135
39
|
end
|
136
40
|
end
|
137
41
|
end
|