rmodbus 1.0.0-java
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 +31 -0
- data/examples/perfomance_rtu.rb +56 -0
- data/examples/perfomance_tcp.rb +55 -0
- data/examples/use_rtu_via_tcp_modbus.rb +22 -0
- data/examples/use_tcp_modbus.rb +23 -0
- data/lib/rmodbus/client.rb +91 -0
- data/lib/rmodbus/common.rb +50 -0
- data/lib/rmodbus/errors.rb +50 -0
- data/lib/rmodbus/ext.rb +88 -0
- data/lib/rmodbus/proxy.rb +54 -0
- data/lib/rmodbus/rtu.rb +140 -0
- data/lib/rmodbus/rtu_client.rb +41 -0
- data/lib/rmodbus/rtu_server.rb +61 -0
- data/lib/rmodbus/rtu_slave.rb +59 -0
- data/lib/rmodbus/rtu_via_tcp_client.rb +41 -0
- data/lib/rmodbus/rtu_via_tcp_server.rb +50 -0
- data/lib/rmodbus/rtu_via_tcp_slave.rb +58 -0
- data/lib/rmodbus/server.rb +142 -0
- 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 +39 -0
- data/lib/rmodbus/tcp_server.rb +61 -0
- data/lib/rmodbus/tcp_slave.rb +64 -0
- data/lib/rmodbus/version.rb +17 -0
- data/lib/rmodbus.rb +35 -0
- data/spec/client_spec.rb +31 -0
- data/spec/exception_spec.rb +116 -0
- data/spec/ext_spec.rb +46 -0
- data/spec/logging_spec.rb +65 -0
- data/spec/proxy_spec.rb +73 -0
- data/spec/read_rtu_response_spec.rb +91 -0
- data/spec/rtu_client_spec.rb +74 -0
- data/spec/rtu_server_spec.rb +29 -0
- data/spec/rtu_via_tcp_client_spec.rb +78 -0
- data/spec/slave_spec.rb +55 -0
- data/spec/tcp_client_spec.rb +81 -0
- data/spec/tcp_server_spec.rb +99 -0
- metadata +165 -0
data/lib/rmodbus/rtu.rb
ADDED
@@ -0,0 +1,140 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010 - 2011 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
|
+
|
16
|
+
module ModBus
|
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
|
86
|
+
def crc16(msg)
|
87
|
+
crc_lo = 0xff
|
88
|
+
crc_hi = 0xff
|
89
|
+
|
90
|
+
msg.unpack('c*').each do |byte|
|
91
|
+
i = crc_hi ^ byte
|
92
|
+
crc_hi = crc_lo ^ CrcHiTable[i]
|
93
|
+
crc_lo = CrcLoTable[i]
|
94
|
+
end
|
95
|
+
|
96
|
+
return ((crc_hi << 8) + crc_lo)
|
97
|
+
end
|
98
|
+
|
99
|
+
CrcHiTable = [
|
100
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
101
|
+
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
102
|
+
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
|
103
|
+
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
|
104
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81,
|
105
|
+
0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
|
106
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
|
107
|
+
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
|
108
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
109
|
+
0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
110
|
+
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01,
|
111
|
+
0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
112
|
+
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
113
|
+
0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
|
114
|
+
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01,
|
115
|
+
0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
|
116
|
+
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81,
|
117
|
+
0x40]
|
118
|
+
CrcLoTable = [
|
119
|
+
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06, 0x07, 0xC7, 0x05, 0xC5, 0xC4,
|
120
|
+
0x04, 0xCC, 0x0C, 0x0D, 0xCD, 0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
|
121
|
+
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A, 0x1E, 0xDE, 0xDF, 0x1F, 0xDD,
|
122
|
+
0x1D, 0x1C, 0xDC, 0x14, 0xD4, 0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
|
123
|
+
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3, 0xF2, 0x32, 0x36, 0xF6, 0xF7,
|
124
|
+
0x37, 0xF5, 0x35, 0x34, 0xF4, 0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
|
125
|
+
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29, 0xEB, 0x2B, 0x2A, 0xEA, 0xEE,
|
126
|
+
0x2E, 0x2F, 0xEF, 0x2D, 0xED, 0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
|
127
|
+
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60, 0x61, 0xA1, 0x63, 0xA3, 0xA2,
|
128
|
+
0x62, 0x66, 0xA6, 0xA7, 0x67, 0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
|
129
|
+
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68, 0x78, 0xB8, 0xB9, 0x79, 0xBB,
|
130
|
+
0x7B, 0x7A, 0xBA, 0xBE, 0x7E, 0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
|
131
|
+
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71, 0x70, 0xB0, 0x50, 0x90, 0x91,
|
132
|
+
0x51, 0x93, 0x53, 0x52, 0x92, 0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
|
133
|
+
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B, 0x99, 0x59, 0x58, 0x98, 0x88,
|
134
|
+
0x48, 0x49, 0x89, 0x4B, 0x8B, 0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
|
135
|
+
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42, 0x43, 0x83, 0x41, 0x81, 0x80,
|
136
|
+
0x40]
|
137
|
+
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2009-2011 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
|
+
module ModBus
|
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
|
25
|
+
class RTUClient < Client
|
26
|
+
include RTU
|
27
|
+
include SP
|
28
|
+
|
29
|
+
protected
|
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)
|
34
|
+
end
|
35
|
+
|
36
|
+
# @private
|
37
|
+
def get_slave(uid, io)
|
38
|
+
RTUSlave.new(uid, io)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2010-2011 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 'serialport'
|
15
|
+
|
16
|
+
module ModBus
|
17
|
+
# RTU server implementation
|
18
|
+
# @example
|
19
|
+
# srv = RTUServer.new('/dev/ttyS1', 9600, 1)
|
20
|
+
# srv.coils = [1,0,1,1]
|
21
|
+
# srv.discrete_inputs = [1,1,0,0]
|
22
|
+
# srv.holding_registers = [1,2,3,4]
|
23
|
+
# srv.input_registers = [1,2,3,4]
|
24
|
+
# srv.debug = true
|
25
|
+
# srv.start
|
26
|
+
class RTUServer
|
27
|
+
include Common
|
28
|
+
include Server
|
29
|
+
include RTU
|
30
|
+
include SP
|
31
|
+
|
32
|
+
# Init RTU server
|
33
|
+
# @param [Integer] uid slave device
|
34
|
+
# @see SP#open_serial_port
|
35
|
+
def initialize(port, baud=9600, uid=1, opts = {})
|
36
|
+
Thread.abort_on_exception = true
|
37
|
+
@sp = open_serial_port(port, baud, opts)
|
38
|
+
@uid = uid
|
39
|
+
end
|
40
|
+
|
41
|
+
# Start server
|
42
|
+
def start
|
43
|
+
@serv = Thread.new do
|
44
|
+
serv_rtu_requests(@sp) do |msg|
|
45
|
+
exec_req(msg[1..-3], @coils, @discrete_inputs, @holding_registers, @input_registers)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Stop server
|
51
|
+
def stop
|
52
|
+
Thread.kill(@serv)
|
53
|
+
@sp.close
|
54
|
+
end
|
55
|
+
|
56
|
+
# Join server
|
57
|
+
def join
|
58
|
+
@serv.join
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2011 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
|
+
module ModBus
|
15
|
+
# RTU slave implementation
|
16
|
+
# @example
|
17
|
+
# RTUClient.connect(port, baud, opts) 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#with_slave
|
25
|
+
# @see Slave
|
26
|
+
class RTUSlave < Slave
|
27
|
+
include RTU
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
# overide method for RTU implamentaion
|
32
|
+
# @see Slave#query
|
33
|
+
def send_pdu(pdu)
|
34
|
+
msg = @uid.chr + pdu
|
35
|
+
msg << crc16(msg).to_word
|
36
|
+
@io.write msg
|
37
|
+
|
38
|
+
log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
|
39
|
+
end
|
40
|
+
|
41
|
+
# overide method for RTU implamentaion
|
42
|
+
# @see Slave#query
|
43
|
+
def read_pdu
|
44
|
+
msg = read_rtu_response(@io)
|
45
|
+
|
46
|
+
log "Rx (#{msg.size} bytes): " + logging_bytes(msg)
|
47
|
+
|
48
|
+
if msg.getbyte(0) == @uid
|
49
|
+
return msg[1..-3] if msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
|
50
|
+
log "Ignore package: don't match CRC"
|
51
|
+
else
|
52
|
+
log "Ignore package: don't match uid ID"
|
53
|
+
end
|
54
|
+
loop do
|
55
|
+
#waite timeout
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2009-2011 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
|
+
module ModBus
|
16
|
+
# RTU over TCP client implementation
|
17
|
+
# @example
|
18
|
+
# RTUViaTCPClient.connect('127.0.0.1', 10002) do |cl|
|
19
|
+
# cl.with_slave(uid) do |slave|
|
20
|
+
# slave.holding_registers[0..100]
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# @see RTUViaTCPClient#open_connection
|
25
|
+
# @see Client#initialize
|
26
|
+
class RTUViaTCPClient < Client
|
27
|
+
include RTU
|
28
|
+
include TCP
|
29
|
+
|
30
|
+
protected
|
31
|
+
# Open TCP\IP connection
|
32
|
+
# @see TCP#open_tcp_connection
|
33
|
+
def open_connection(ipaddr, port = 10002, opts = {})
|
34
|
+
io = open_tcp_connection(ipaddr, port, opts)
|
35
|
+
end
|
36
|
+
|
37
|
+
def get_slave(uid, io)
|
38
|
+
RTUViaTCPSlave.new(uid, io)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +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
|
+
|
16
|
+
require 'gserver'
|
17
|
+
|
18
|
+
module ModBus
|
19
|
+
# RTU over TCP server implementation
|
20
|
+
# @example
|
21
|
+
# srv = RTUViaTCPServer.new(10002, 1)
|
22
|
+
# srv.coils = [1,0,1,1]
|
23
|
+
# srv.discrete_inputs = [1,1,0,0]
|
24
|
+
# srv.holding_registers = [1,2,3,4]
|
25
|
+
# srv.input_registers = [1,2,3,4]
|
26
|
+
# srv.debug = true
|
27
|
+
# srv.start
|
28
|
+
class RTUViaTCPServer < GServer
|
29
|
+
include Common
|
30
|
+
include RTU
|
31
|
+
include Server
|
32
|
+
|
33
|
+
# Init server
|
34
|
+
# @param [Integer] port listen port
|
35
|
+
# @param [Integer] uid slave device
|
36
|
+
def initialize(port = 10002, uid = 1)
|
37
|
+
@uid = uid
|
38
|
+
super(port)
|
39
|
+
end
|
40
|
+
|
41
|
+
protected
|
42
|
+
# Serve requests
|
43
|
+
# @param [TCPSocket] io socket
|
44
|
+
def serve(io)
|
45
|
+
serv_rtu_requests(io) do |msg|
|
46
|
+
exec_req(msg[1..-3], @coils, @discrete_inputs, @holding_registers, @input_registers)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# RModBus - free implementation of ModBus protocol in Ruby.
|
2
|
+
#
|
3
|
+
# Copyright (C) 2011 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
|
+
module ModBus
|
15
|
+
# RTU over TCP slave implementation
|
16
|
+
# @example
|
17
|
+
# RTUViaTCP.connect('127.0.0.1', 10002) do |cl|
|
18
|
+
# cl.with_slave(uid) do |slave|
|
19
|
+
# slave.holding_registers[0..100]
|
20
|
+
# end
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# @see RTUViaTCPClient#open_connection
|
24
|
+
# @see Client#with_slave
|
25
|
+
# @see Slave
|
26
|
+
class RTUViaTCPSlave < Slave
|
27
|
+
include RTU
|
28
|
+
|
29
|
+
protected
|
30
|
+
# overide method for RTU over TCP implamentaion
|
31
|
+
# @see Slave#query
|
32
|
+
def send_pdu(pdu)
|
33
|
+
msg = @uid.chr + pdu
|
34
|
+
msg << crc16(msg).to_word
|
35
|
+
@io.write msg
|
36
|
+
|
37
|
+
log "Tx (#{msg.size} bytes): " + logging_bytes(msg)
|
38
|
+
end
|
39
|
+
|
40
|
+
# overide method for RTU over TCP implamentaion
|
41
|
+
# @see Slave#query
|
42
|
+
def read_pdu
|
43
|
+
# Read the response appropriately
|
44
|
+
msg = read_rtu_response(@io)
|
45
|
+
|
46
|
+
log "Rx (#{msg.size} bytes): " + logging_bytes(msg)
|
47
|
+
if msg.getbyte(0) == @uid
|
48
|
+
return msg[1..-3] if msg[-2,2].unpack('n')[0] == crc16(msg[0..-3])
|
49
|
+
log "Ignore package: don't match CRC"
|
50
|
+
else
|
51
|
+
log "Ignore package: don't match uid ID"
|
52
|
+
end
|
53
|
+
loop do
|
54
|
+
#waite timeout
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,142 @@
|
|
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
|
+
|
16
|
+
module ModBus
|
17
|
+
# Module for implementation ModBus server
|
18
|
+
module Server
|
19
|
+
Funcs = [1,2,3,4,5,6,15,16]
|
20
|
+
|
21
|
+
attr_accessor :coils, :discrete_inputs, :holding_registers, :input_registers, :uid
|
22
|
+
@coils = []
|
23
|
+
@discrete_inputs = []
|
24
|
+
@holding_registers =[]
|
25
|
+
@input_registers = []
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def exec_req(req, coils, discrete_inputs, holding_registers, input_registers)
|
30
|
+
func = req.getbyte(0)
|
31
|
+
|
32
|
+
unless Funcs.include?(func)
|
33
|
+
params = { :err => 1 }
|
34
|
+
end
|
35
|
+
|
36
|
+
case func
|
37
|
+
when 1
|
38
|
+
params = parse_read_func(req, coils)
|
39
|
+
if params[:err] == 0
|
40
|
+
val = coils[params[:addr],params[:quant]].pack_to_word
|
41
|
+
pdu = func.chr + val.size.chr + val
|
42
|
+
end
|
43
|
+
when 2
|
44
|
+
params = parse_read_func(req, discrete_inputs)
|
45
|
+
if params[:err] == 0
|
46
|
+
val = discrete_inputs[params[:addr],params[:quant]].pack_to_word
|
47
|
+
pdu = func.chr + val.size.chr + val
|
48
|
+
end
|
49
|
+
when 3
|
50
|
+
params = parse_read_func(req, holding_registers)
|
51
|
+
if params[:err] == 0
|
52
|
+
pdu = func.chr + (params[:quant] * 2).chr + holding_registers[params[:addr],params[:quant]].pack('n*')
|
53
|
+
end
|
54
|
+
when 4
|
55
|
+
params = parse_read_func(req, input_registers)
|
56
|
+
if params[:err] == 0
|
57
|
+
pdu = func.chr + (params[:quant] * 2).chr + input_registers[params[:addr],params[:quant]].pack('n*')
|
58
|
+
end
|
59
|
+
when 5
|
60
|
+
params = parse_write_coil_func(req)
|
61
|
+
if params[:err] == 0
|
62
|
+
coils[params[:addr]] = params[:val]
|
63
|
+
pdu = req
|
64
|
+
end
|
65
|
+
when 6
|
66
|
+
params = parse_write_register_func(req)
|
67
|
+
if params[:err] == 0
|
68
|
+
holding_registers[params[:addr]] = params[:val]
|
69
|
+
pdu = req
|
70
|
+
end
|
71
|
+
when 15
|
72
|
+
params = parse_write_multiple_coils_func(req)
|
73
|
+
if params[:err] == 0
|
74
|
+
coils[params[:addr],params[:quant]] = params[:val][0,params[:quant]]
|
75
|
+
pdu = req[0,5]
|
76
|
+
end
|
77
|
+
when 16
|
78
|
+
params = parse_write_multiple_registers_func(req)
|
79
|
+
if params[:err] == 0
|
80
|
+
holding_registers[params[:addr],params[:quant]] = params[:val][0,params[:quant]]
|
81
|
+
pdu = req[0,5]
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
if params[:err] == 0
|
86
|
+
pdu
|
87
|
+
else
|
88
|
+
pdu = (func | 0x80).chr + params[:err].chr
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def parse_read_func(req, field)
|
93
|
+
quant = req[3,2].unpack('n')[0]
|
94
|
+
|
95
|
+
return { :err => 3} unless quant <= 0x7d
|
96
|
+
|
97
|
+
addr = req[1,2].unpack('n')[0]
|
98
|
+
return { :err => 2 } unless addr + quant <= field.size
|
99
|
+
|
100
|
+
return { :err => 0, :quant => quant, :addr => addr }
|
101
|
+
end
|
102
|
+
|
103
|
+
def parse_write_coil_func(req)
|
104
|
+
addr = req[1,2].unpack('n')[0]
|
105
|
+
return { :err => 2 } unless addr <= @coils.size
|
106
|
+
|
107
|
+
val = req[3,2].unpack('n')[0]
|
108
|
+
return { :err => 3 } unless val == 0 or val == 0xff00
|
109
|
+
|
110
|
+
val = 1 if val == 0xff00
|
111
|
+
return { :err => 0, :addr => addr, :val => val }
|
112
|
+
end
|
113
|
+
|
114
|
+
def parse_write_register_func(req)
|
115
|
+
addr = req[1,2].unpack('n')[0]
|
116
|
+
return { :err => 2 } unless addr <= @coils.size
|
117
|
+
|
118
|
+
val = req[3,2].unpack('n')[0]
|
119
|
+
|
120
|
+
return { :err => 0, :addr => addr, :val => val }
|
121
|
+
end
|
122
|
+
|
123
|
+
def parse_write_multiple_coils_func(req)
|
124
|
+
params = parse_read_func(req, @coils)
|
125
|
+
|
126
|
+
if params[:err] == 0
|
127
|
+
params = {:err => 0, :addr => params[:addr], :quant => params[:quant], :val => req[6,params[:quant]].unpack_bits }
|
128
|
+
end
|
129
|
+
params
|
130
|
+
end
|
131
|
+
|
132
|
+
def parse_write_multiple_registers_func(req)
|
133
|
+
params = parse_read_func(req, @holding_registers)
|
134
|
+
|
135
|
+
if params[:err] == 0
|
136
|
+
params = {:err => 0, :addr => params[:addr], :quant => params[:quant], :val => req[6,params[:quant] * 2].unpack('n*')}
|
137
|
+
end
|
138
|
+
params
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|