rmodbus 0.5.0 → 1.0.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.
- 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
|