rmodbus 0.3.1 → 0.4.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/ChangeLog +36 -2
- data/README +14 -15
- data/Rakefile +45 -0
- data/examples/perfomance_rtu.rb +58 -0
- data/examples/perfomance_tcp.rb +57 -0
- data/examples/use_tcp_modbus.rb +1 -0
- data/lib/rmodbus/client.rb +20 -7
- data/lib/rmodbus/crc16.rb +69 -0
- data/lib/rmodbus/ext.rb +65 -61
- data/lib/rmodbus/parsers.rb +133 -0
- data/lib/rmodbus/rtu_client.rb +107 -62
- data/lib/rmodbus/rtu_server.rb +90 -0
- data/lib/rmodbus/tcp_client.rb +47 -3
- data/lib/rmodbus/tcp_server.rb +18 -118
- data/lib/rmodbus.rb +1 -0
- data/spec/exception_spec.rb +117 -0
- data/spec/ext_spec.rb +5 -1
- data/spec/logging_spec.rb +69 -0
- data/spec/rtu_client_spec.rb +40 -10
- data/spec/rtu_server_spec.rb +31 -0
- data/spec/tcp_client_spec.rb +25 -0
- data/spec/tcp_server_spec.rb +2 -2
- metadata +15 -6
@@ -0,0 +1,133 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in purge Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 Timin Aleksey
|
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
|
+
|
15
|
+
module ModBus
|
16
|
+
module Parsers
|
17
|
+
|
18
|
+
Funcs = [1,2,3,4,5,6,15,16]
|
19
|
+
|
20
|
+
def exec_req(req, coils, discrete_inputs, holding_registers, input_registers)
|
21
|
+
func = req.getbyte(0)
|
22
|
+
|
23
|
+
unless Funcs.include?(func)
|
24
|
+
params = { :err => 1 }
|
25
|
+
end
|
26
|
+
|
27
|
+
case func
|
28
|
+
when 1
|
29
|
+
params = parse_read_func(req, coils)
|
30
|
+
if params[:err] == 0
|
31
|
+
val = coils[params[:addr],params[:quant]].pack_to_word
|
32
|
+
pdu = func.chr + val.size.chr + val
|
33
|
+
end
|
34
|
+
when 2
|
35
|
+
params = parse_read_func(req, discrete_inputs)
|
36
|
+
if params[:err] == 0
|
37
|
+
val = discrete_inputs[params[:addr],params[:quant]].pack_to_word
|
38
|
+
pdu = func.chr + val.size.chr + val
|
39
|
+
end
|
40
|
+
when 3
|
41
|
+
params = parse_read_func(req, holding_registers)
|
42
|
+
if params[:err] == 0
|
43
|
+
pdu = func.chr + (params[:quant] * 2).chr + holding_registers[params[:addr],params[:quant]].pack('n*')
|
44
|
+
end
|
45
|
+
when 4
|
46
|
+
params = parse_read_func(req, input_registers)
|
47
|
+
if params[:err] == 0
|
48
|
+
pdu = func.chr + (params[:quant] * 2).chr + input_registers[params[:addr],params[:quant]].pack('n*')
|
49
|
+
end
|
50
|
+
when 5
|
51
|
+
params = parse_write_coil_func(req)
|
52
|
+
if params[:err] == 0
|
53
|
+
coils[params[:addr]] = params[:val]
|
54
|
+
pdu = req
|
55
|
+
end
|
56
|
+
when 6
|
57
|
+
params = parse_write_register_func(req)
|
58
|
+
if params[:err] == 0
|
59
|
+
holding_registers[params[:addr]] = params[:val]
|
60
|
+
pdu = req
|
61
|
+
end
|
62
|
+
when 15
|
63
|
+
params = parse_write_multiple_coils_func(req)
|
64
|
+
if params[:err] == 0
|
65
|
+
coils[params[:addr],params[:quant]] = params[:val][0,params[:quant]]
|
66
|
+
pdu = req[0,5]
|
67
|
+
end
|
68
|
+
when 16
|
69
|
+
params = parse_write_multiple_registers_func(req)
|
70
|
+
if params[:err] == 0
|
71
|
+
holding_registers[params[:addr],params[:quant]] = params[:val][0,params[:quant]]
|
72
|
+
pdu = req[0,5]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
if params[:err] == 0
|
77
|
+
pdu
|
78
|
+
else
|
79
|
+
pdu = (func | 0x80).chr + params[:err].chr
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
def parse_read_func(req, field)
|
85
|
+
quant = req[3,2].unpack('n')[0]
|
86
|
+
|
87
|
+
return { :err => 3} unless quant <= 0x7d
|
88
|
+
|
89
|
+
addr = req[1,2].unpack('n')[0]
|
90
|
+
return { :err => 2 } unless addr + quant <= field.size
|
91
|
+
|
92
|
+
return { :err => 0, :quant => quant, :addr => addr }
|
93
|
+
end
|
94
|
+
|
95
|
+
def parse_write_coil_func(req)
|
96
|
+
addr = req[1,2].unpack('n')[0]
|
97
|
+
return { :err => 2 } unless addr <= @coils.size
|
98
|
+
|
99
|
+
val = req[3,2].unpack('n')[0]
|
100
|
+
return { :err => 3 } unless val == 0 or val == 0xff00
|
101
|
+
|
102
|
+
val = 1 if val == 0xff00
|
103
|
+
return { :err => 0, :addr => addr, :val => val }
|
104
|
+
end
|
105
|
+
|
106
|
+
def parse_write_register_func(req)
|
107
|
+
addr = req[1,2].unpack('n')[0]
|
108
|
+
return { :err => 2 } unless addr <= @coils.size
|
109
|
+
|
110
|
+
val = req[3,2].unpack('n')[0]
|
111
|
+
|
112
|
+
return { :err => 0, :addr => addr, :val => val }
|
113
|
+
end
|
114
|
+
|
115
|
+
def parse_write_multiple_coils_func(req)
|
116
|
+
params = parse_read_func(req, @coils)
|
117
|
+
|
118
|
+
if params[:err] == 0
|
119
|
+
params = {:err => 0, :addr => params[:addr], :quant => params[:quant], :val => req[6,params[:quant]].unpack_bits }
|
120
|
+
end
|
121
|
+
params
|
122
|
+
end
|
123
|
+
|
124
|
+
def parse_write_multiple_registers_func(req)
|
125
|
+
params = parse_read_func(req, @holding_registers)
|
126
|
+
|
127
|
+
if params[:err] == 0
|
128
|
+
params = {:err => 0, :addr => params[:addr], :quant => params[:quant], :val => req[6,params[:quant] * 2].unpack('n*')}
|
129
|
+
end
|
130
|
+
params
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
data/lib/rmodbus/rtu_client.rb
CHANGED
@@ -1,3 +1,18 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2009 Timin Aleksey
|
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
|
+
require 'rmodbus/crc16'
|
15
|
+
|
1
16
|
begin
|
2
17
|
require 'rubygems'
|
3
18
|
rescue
|
@@ -7,86 +22,116 @@ require 'serialport'
|
|
7
22
|
module ModBus
|
8
23
|
|
9
24
|
class RTUClient < Client
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
25
|
+
|
26
|
+
include CRC16
|
27
|
+
attr_reader :port, :baud, :slave, :data_bits, :stop_bits, :parity
|
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
|
+
def initialize(port, baud=9600, slaveaddr=1, options = {})
|
85
|
+
@port, @baud, @slave = port, baud, slaveaddr
|
86
|
+
|
87
|
+
@data_bits, @stop_bits, @parity = 8, 1, SerialPort::NONE
|
88
|
+
|
89
|
+
@data_bits = options[:data_bits] unless options[:data_bits].nil?
|
90
|
+
@stop_bits = options[:stop_bits] unless options[:stop_bits].nil?
|
91
|
+
@parity = options[:parity] unless options[:parity].nil?
|
92
|
+
|
93
|
+
@debug = false
|
94
|
+
|
95
|
+
@sp = SerialPort.new(@port, @baud, @data_bits, @stop_bits, @parity)
|
96
|
+
@sp.read_timeout = 5
|
97
|
+
|
15
98
|
super()
|
16
99
|
end
|
17
100
|
|
101
|
+
def close
|
102
|
+
@sp.close unless @sp.closed?
|
103
|
+
end
|
104
|
+
|
105
|
+
def closed?
|
106
|
+
@sp.closed?
|
107
|
+
end
|
108
|
+
|
18
109
|
protected
|
19
110
|
def send_pdu(pdu)
|
20
111
|
msg = @slave.chr + pdu
|
21
112
|
msg << crc16(msg).to_word
|
22
|
-
@
|
113
|
+
@sp.write msg
|
114
|
+
if @debug
|
115
|
+
STDOUT << "Tx (#{msg.size} bytes): " + logging_bytes(msg) + "\n"
|
116
|
+
end
|
23
117
|
end
|
24
118
|
|
25
119
|
def read_pdu
|
26
120
|
msg = ''
|
27
|
-
while msg.size == 0
|
28
|
-
msg = @
|
121
|
+
while msg.size == 0
|
122
|
+
msg = @sp.read
|
123
|
+
end
|
124
|
+
|
125
|
+
if @debug
|
126
|
+
STDOUT << "Rx (#{msg.size} bytes): " + logging_bytes(msg) + "\n"
|
29
127
|
end
|
30
128
|
|
31
129
|
if msg.getbyte(0) == @slave
|
32
|
-
return msg[1..-3] if msg[-2,2] == crc16(msg[0..-3])
|
130
|
+
return msg[1..-3] if msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
|
33
131
|
end
|
34
132
|
loop do
|
35
133
|
#waite timeout
|
36
134
|
end
|
37
135
|
end
|
38
|
-
|
39
|
-
def crc16(msg)
|
40
|
-
crc_lo = 0xff
|
41
|
-
crc_hi = 0xff
|
42
|
-
|
43
|
-
msg.unpack('c*').each do |byte|
|
44
|
-
i = crc_hi ^ byte
|
45
|
-
crc_hi = crc_lo ^ CrcHiTable[i]
|
46
|
-
crc_lo = CrcLoTable[i]
|
47
|
-
end
|
48
|
-
|
49
|
-
return ((crc_hi << 8) + crc_lo)
|
50
|
-
end
|
51
|
-
|
52
|
-
CrcHiTable = [
|
53
|
-
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
54
|
-
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
55
|
-
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
|
56
|
-
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
57
|
-
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
|
58
|
-
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
59
|
-
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
|
60
|
-
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
61
|
-
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
62
|
-
0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
63
|
-
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
|
64
|
-
0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
65
|
-
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
66
|
-
0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
67
|
-
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
|
68
|
-
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
69
|
-
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
70
|
-
0x40]
|
71
|
-
CrcLoTable = [
|
72
|
-
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
|
73
|
-
0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
|
74
|
-
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
|
75
|
-
0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
|
76
|
-
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
|
77
|
-
0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
|
78
|
-
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
|
79
|
-
0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
|
80
|
-
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
|
81
|
-
0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
|
82
|
-
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
|
83
|
-
0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
|
84
|
-
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
|
85
|
-
0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
|
86
|
-
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
|
87
|
-
0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
|
88
|
-
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
|
89
|
-
0x40]
|
90
136
|
end
|
91
|
-
|
92
137
|
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 Timin Aleksey
|
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
|
+
|
15
|
+
require 'rmodbus/parsers'
|
16
|
+
|
17
|
+
begin
|
18
|
+
require 'rubygems'
|
19
|
+
rescue
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'serialport'
|
23
|
+
|
24
|
+
module ModBus
|
25
|
+
|
26
|
+
class RTUServer
|
27
|
+
include Parsers
|
28
|
+
include CRC16
|
29
|
+
|
30
|
+
attr_accessor :coils, :discrete_inputs, :holding_registers, :input_registers
|
31
|
+
attr_reader :port, :baud, :slave, :data_bits, :stop_bits, :parity
|
32
|
+
|
33
|
+
def discret_inputs
|
34
|
+
warn "[DEPRECATION] `discret_inputs` is deprecated. Please use `discrete_inputs` instead."
|
35
|
+
@discrete_inputs
|
36
|
+
end
|
37
|
+
|
38
|
+
def discret_inputs=(val)
|
39
|
+
warn "[DEPRECATION] `discret_inputs=` is deprecated. Please use `discrete_inputs=` instead."
|
40
|
+
@discrete_inputs=val
|
41
|
+
end
|
42
|
+
|
43
|
+
|
44
|
+
def initialize(port, baud=9600, slaveaddr=1, options = {})
|
45
|
+
Thread.abort_on_exception = true
|
46
|
+
|
47
|
+
@port, @baud = port, baud
|
48
|
+
@data_bits, @stop_bits, @parity = 8, 1, SerialPort::NONE
|
49
|
+
|
50
|
+
@data_bits = options[:data_bits] unless options[:data_bits].nil?
|
51
|
+
@stop_bits = options[:stop_bits] unless options[:stop_bits].nil?
|
52
|
+
@parity = options[:parity] unless options[:parity].nil?
|
53
|
+
|
54
|
+
@sp = SerialPort.new(@port, @baud, @data_bits, @stop_bits, @parity)
|
55
|
+
@sp.read_timeout = 5
|
56
|
+
|
57
|
+
@coils = []
|
58
|
+
@discrete_inputs = []
|
59
|
+
@holding_registers = []
|
60
|
+
@input_registers = []
|
61
|
+
@slave = slaveaddr
|
62
|
+
end
|
63
|
+
|
64
|
+
def start
|
65
|
+
@serv = Thread.new do
|
66
|
+
loop do
|
67
|
+
req = ''
|
68
|
+
while req.size == 0
|
69
|
+
req = @sp.read
|
70
|
+
end
|
71
|
+
if req.getbyte(0) == @slave and req[-2,2].unpack('n')[0] == crc16(req[0..-3])
|
72
|
+
pdu = exec_req(req[1..-1], @coils, @discrete_inputs, @holding_registers, @input_registers)
|
73
|
+
resp = @slave.chr + pdu
|
74
|
+
resp << crc16(resp).to_word
|
75
|
+
@sp.write resp
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def stop
|
82
|
+
Thread.kill(@serv)
|
83
|
+
@sp.close
|
84
|
+
end
|
85
|
+
|
86
|
+
def join
|
87
|
+
@serv.join
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
data/lib/rmodbus/tcp_client.rb
CHANGED
@@ -23,12 +23,39 @@ module ModBus
|
|
23
23
|
|
24
24
|
include Timeout
|
25
25
|
|
26
|
+
attr_reader :ipaddr, :port, :slave
|
27
|
+
attr_accessor :debug
|
28
|
+
|
26
29
|
@@transaction = 0
|
30
|
+
|
31
|
+
# Connect with ModBus server
|
32
|
+
#
|
33
|
+
# ipaddr - ip of the server
|
34
|
+
#
|
35
|
+
# port - port TCP connections
|
36
|
+
#
|
37
|
+
# slaveaddr - slave ID of the server
|
38
|
+
#
|
39
|
+
# TCPClient.connect('127.0.0.1') do |cl|
|
40
|
+
#
|
41
|
+
# put cl.read_holding_registers(0, 10)
|
42
|
+
#
|
43
|
+
# end
|
44
|
+
def self.connect(ipaddr, port = 502, slaveaddr = 1)
|
45
|
+
cl = TCPClient.new(ipaddr, port, slaveaddr)
|
46
|
+
yield cl
|
47
|
+
cl.close
|
48
|
+
end
|
27
49
|
|
28
50
|
# Connect with a ModBus server
|
51
|
+
#
|
52
|
+
# ipaddr - ip of the server
|
53
|
+
#
|
54
|
+
# port - port TCP connections
|
55
|
+
#
|
56
|
+
# slaveaddr - slave ID of the server
|
29
57
|
def initialize(ipaddr, port = 502, slaveaddr = 1)
|
30
58
|
@ipaddr, @port = ipaddr, port
|
31
|
-
super()
|
32
59
|
tried = 0
|
33
60
|
begin
|
34
61
|
timeout(1, ModBusTimeout) do
|
@@ -40,12 +67,20 @@ module ModBus
|
|
40
67
|
raise ModBusTimeout.new, 'Timed out attempting to create connection'
|
41
68
|
end
|
42
69
|
@slave = slaveaddr
|
70
|
+
@debug = false
|
71
|
+
super()
|
43
72
|
end
|
44
73
|
|
74
|
+
# Close TCP connections
|
45
75
|
def close
|
46
76
|
@sock.close unless @sock.closed?
|
47
77
|
end
|
48
78
|
|
79
|
+
# Check TCP connections
|
80
|
+
def closed?
|
81
|
+
@sock.closed?
|
82
|
+
end
|
83
|
+
|
49
84
|
def self.transaction
|
50
85
|
@@transaction
|
51
86
|
end
|
@@ -54,7 +89,12 @@ module ModBus
|
|
54
89
|
def send_pdu(pdu)
|
55
90
|
@@transaction = 0 if @@transaction.next > 65535
|
56
91
|
@@transaction += 1
|
57
|
-
|
92
|
+
msg = @@transaction.to_word + "\0\0" + (pdu.size + 1).to_word + @slave.chr + pdu
|
93
|
+
@sock.write msg
|
94
|
+
|
95
|
+
if debug
|
96
|
+
STDOUT << "Tx (#{msg.size} bytes): " + logging_bytes(msg) + "\n"
|
97
|
+
end
|
58
98
|
end
|
59
99
|
|
60
100
|
def read_pdu
|
@@ -63,7 +103,11 @@ module ModBus
|
|
63
103
|
tin = header[0,2].unpack('n')[0]
|
64
104
|
raise Errors::ModBusException.new("Transaction number mismatch") unless tin == @@transaction
|
65
105
|
len = header[4,2].unpack('n')[0]
|
66
|
-
@sock.read(len-1)
|
106
|
+
msg = @sock.read(len-1)
|
107
|
+
if @debug
|
108
|
+
STDOUT << "Rx (#{(header + msg).size} bytes): " + logging_bytes(header + msg) + "\n"
|
109
|
+
end
|
110
|
+
msg
|
67
111
|
else
|
68
112
|
raise Errors::ModBusException.new("Server did not respond")
|
69
113
|
end
|