rmodbus 1.0.0-java
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 +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
|