em-modbus 0.1.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/.gitignore +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +373 -0
- data/README.md +30 -0
- data/Rakefile +15 -0
- data/em-modbus.gemspec +26 -0
- data/examples/client.rb +33 -0
- data/examples/client_batch.rb +47 -0
- data/examples/server.rb +43 -0
- data/lib/modbus/adu/adu.rb +5 -0
- data/lib/modbus/adu/rtu_adu.rb +18 -0
- data/lib/modbus/adu/tcp_adu.rb +82 -0
- data/lib/modbus/client.rb +64 -0
- data/lib/modbus/connection/base.rb +41 -0
- data/lib/modbus/connection/connection.rb +7 -0
- data/lib/modbus/connection/protocol_data.rb +78 -0
- data/lib/modbus/connection/tcp_client.rb +82 -0
- data/lib/modbus/connection/tcp_server.rb +36 -0
- data/lib/modbus/exceptions.rb +106 -0
- data/lib/modbus/modbus.rb +12 -0
- data/lib/modbus/pdu/exception.rb +56 -0
- data/lib/modbus/pdu/pdu.rb +87 -0
- data/lib/modbus/pdu/read_holding_registers.rb +26 -0
- data/lib/modbus/pdu/read_input_registers.rb +26 -0
- data/lib/modbus/pdu/read_input_status.rb +147 -0
- data/lib/modbus/pdu/read_registers.rb +135 -0
- data/lib/modbus/pdu/write_multiple_registers.rb +158 -0
- data/lib/modbus/register/base.rb +19 -0
- data/lib/modbus/register/bit_register.rb +49 -0
- data/lib/modbus/register/register.rb +7 -0
- data/lib/modbus/register/word_register.rb +30 -0
- data/lib/modbus/server.rb +120 -0
- data/lib/modbus/transaction/base.rb +25 -0
- data/lib/modbus/transaction/client.rb +162 -0
- data/lib/modbus/transaction/server.rb +95 -0
- data/lib/modbus/transaction/transaction.rb +39 -0
- data/lib/modbus/version.rb +7 -0
- data/lib/modbus.rb +2 -0
- data/spec/adi_spec.rb +16 -0
- data/spec/spec_helper.rb +1 -0
- metadata +162 -0
@@ -0,0 +1,64 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
require 'uri'
|
5
|
+
|
6
|
+
module Modbus
|
7
|
+
|
8
|
+
class Client
|
9
|
+
|
10
|
+
|
11
|
+
def initialize(uri)
|
12
|
+
@base_polling_interval = 1
|
13
|
+
@last_poll_time = Time.now
|
14
|
+
@uri = URI uri
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def connected(conn)
|
19
|
+
@conn = conn
|
20
|
+
schedule_next_poll
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def disconnected(conn)
|
25
|
+
reconnect
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
def connect
|
30
|
+
EM.connect @uri.host, @uri.port, Modbus::Connection::TCPClient, self
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
|
37
|
+
def reconnect
|
38
|
+
EM.add_timer(5) { connect }
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def schedule_next_poll
|
43
|
+
poll_time = Time.now - @last_poll_time
|
44
|
+
next_poll_time = [@base_polling_interval - poll_time, @base_polling_interval*0.25].max
|
45
|
+
|
46
|
+
EM.add_timer(next_poll_time) do
|
47
|
+
@last_poll_time = Time.now
|
48
|
+
poll
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def poll
|
54
|
+
# no-op
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def transaction(timeout = 2)
|
59
|
+
yield Modbus::Transaction::Client.new @conn, timeout
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
module Modbus
|
5
|
+
module Connection
|
6
|
+
|
7
|
+
class Base < EM::Connection
|
8
|
+
|
9
|
+
|
10
|
+
def initialize(handler)
|
11
|
+
@handler = handler
|
12
|
+
reset_buffer
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
def receive_data(data)
|
17
|
+
@buffer << data
|
18
|
+
analyze_buffer
|
19
|
+
|
20
|
+
rescue => e
|
21
|
+
# TODO log exception
|
22
|
+
# puts e.message
|
23
|
+
reset_buffer
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def reset_buffer
|
28
|
+
@buffer = String.new
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
def analyze_buffer
|
33
|
+
success = transaction_class.recv_adu @buffer, self
|
34
|
+
analyze_buffer if success && !@buffer.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end # Base
|
39
|
+
|
40
|
+
end # Connection
|
41
|
+
end # Modbus
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
|
5
|
+
module Modbus
|
6
|
+
|
7
|
+
|
8
|
+
# Helper class for dealing with the modbus wire format.
|
9
|
+
#
|
10
|
+
class ProtocolData < ::Array
|
11
|
+
|
12
|
+
|
13
|
+
# Initializes a new ProtocolData instance. Unpacks a buffer string if given.
|
14
|
+
#
|
15
|
+
# @param buffer [String, Array] The buffer data. If it's a String, it's automatically unpacked.
|
16
|
+
#
|
17
|
+
def initialize(buffer = nil)
|
18
|
+
case buffer
|
19
|
+
when String
|
20
|
+
super buffer.unpack('C*')
|
21
|
+
when Array
|
22
|
+
super buffer
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
# Shifts two bytes off the from front of the array and interprets them as a word (network byte order).
|
28
|
+
#
|
29
|
+
# @return [Integer, NilClass] The shifted word or nil if there are not enough bytes.
|
30
|
+
#
|
31
|
+
def shift_word
|
32
|
+
return nil if size < 2
|
33
|
+
# self.shift(2).pack('C2').unpack('n').first
|
34
|
+
self.slice!(0,2).pack('C2').unpack('n').first
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
# Interprets a value as a word (network byte order) and pushes two bytes to the end of the array.
|
39
|
+
#
|
40
|
+
# @param word [Integer] The value to push.
|
41
|
+
#
|
42
|
+
def push_word(word)
|
43
|
+
self.concat [word].pack('n').unpack('C2')
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
# Shifts one bytes off the from front of the array.
|
48
|
+
#
|
49
|
+
# @return [Integer] The shifted byte.
|
50
|
+
#
|
51
|
+
def shift_byte
|
52
|
+
# self.shift
|
53
|
+
self.slice!(0,1).first
|
54
|
+
end
|
55
|
+
|
56
|
+
|
57
|
+
# Interprets a value as a byte (network byte order) and pushes the byte to the end of the array.
|
58
|
+
#
|
59
|
+
# @param byte [Integer] The value to push.
|
60
|
+
#
|
61
|
+
def push_byte(byte)
|
62
|
+
self.concat [byte].pack('C').unpack('C')
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
# Converts the array data into a string.
|
67
|
+
#
|
68
|
+
# @return [String] The data string (frozen).
|
69
|
+
#
|
70
|
+
def to_buffer
|
71
|
+
self.pack('C*').freeze
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end # Modbus
|
77
|
+
|
78
|
+
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'eventmachine'
|
6
|
+
|
7
|
+
module Modbus
|
8
|
+
module Connection
|
9
|
+
|
10
|
+
class TCPClient < Base
|
11
|
+
|
12
|
+
|
13
|
+
# Initializes a new connection instance.
|
14
|
+
#
|
15
|
+
# @param handler The managing handler object.
|
16
|
+
#
|
17
|
+
def initialize(handler)
|
18
|
+
super
|
19
|
+
|
20
|
+
@transaction_ident = 0
|
21
|
+
@pending_transactions = []
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
# EM callback. Called when the TCP connection is sucessfully established.
|
26
|
+
#
|
27
|
+
def connection_completed
|
28
|
+
@handler.connected self
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
# EM callback. Called when the TCP connection is closed.
|
33
|
+
#
|
34
|
+
def unbind
|
35
|
+
@handler.disconnected self
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
# Creates a successor transaction identfication in the range of 0..65535
|
40
|
+
#
|
41
|
+
# @return [Integer] The transaction identification
|
42
|
+
#
|
43
|
+
def next_transaction_ident
|
44
|
+
@transaction_ident = @transaction_ident.succ.modulo 2**16
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
# Adds Transaction object to the list of tracked transactions
|
49
|
+
#
|
50
|
+
# @param transaction [Modbus::Transaction] The transaction object to add.
|
51
|
+
#
|
52
|
+
def track_transaction(transaction)
|
53
|
+
# TODO log "exception"
|
54
|
+
# puts "Too many pending pending transactions: #{@pending_transactions.size}" if @pending_transactions.size > 100
|
55
|
+
@pending_transactions << transaction
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# Looks for a matching transaction in the internal store by a transaction ident and returns it if found.
|
60
|
+
#
|
61
|
+
# @param transaction_ident [Integer] The transaction ident to match.
|
62
|
+
# @return [Modbus::Transaction] The found Transaction object.
|
63
|
+
#
|
64
|
+
def pick_pending_transaction(transaction_ident)
|
65
|
+
transaction = @pending_transactions.find { |r| r.transaction_ident == transaction_ident }
|
66
|
+
@pending_transactions.delete transaction
|
67
|
+
transaction
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
|
74
|
+
def transaction_class
|
75
|
+
Modbus::Transaction::Client
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
end # TCPClient
|
80
|
+
|
81
|
+
end # Connection
|
82
|
+
end # Modbus
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
require 'forwardable'
|
5
|
+
|
6
|
+
module Modbus
|
7
|
+
module Connection
|
8
|
+
|
9
|
+
class TCPServer < Base
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def_delegator :@handler, :read_registers, :read_registers
|
13
|
+
def_delegator :@handler, :write_registers, :write_registers
|
14
|
+
|
15
|
+
|
16
|
+
def post_init
|
17
|
+
@handler.client_connected signature
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
def unbind
|
22
|
+
@handler.client_disconnected signature
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
|
29
|
+
def transaction_class
|
30
|
+
Modbus::Transaction::Server
|
31
|
+
end
|
32
|
+
|
33
|
+
end # TCPServer
|
34
|
+
|
35
|
+
end # Connection
|
36
|
+
end # Modbus
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
|
5
|
+
module Modbus
|
6
|
+
ClientError = Class.new StandardError
|
7
|
+
ModbusError = Class.new StandardError
|
8
|
+
|
9
|
+
class ModbusError < StandardError
|
10
|
+
attr_reader :code
|
11
|
+
|
12
|
+
def initialize(msg)
|
13
|
+
super
|
14
|
+
@code = self.class::CODE
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class IllegalFunction < ModbusError
|
19
|
+
CODE = 0x01
|
20
|
+
|
21
|
+
def initialize(msg = 'ILLEGAL FUNCTION')
|
22
|
+
super
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class IllegalDataAddress < ModbusError
|
27
|
+
CODE = 0x02
|
28
|
+
|
29
|
+
def initialize(msg = 'ILLEGAL DATA ADDRESS')
|
30
|
+
super
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class IllegalDataValue < ModbusError
|
35
|
+
CODE = 0x03
|
36
|
+
|
37
|
+
def initialize(msg = 'ILLEGAL DATA VALUE')
|
38
|
+
super
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class ServerDeviceFailure < ModbusError
|
43
|
+
CODE = 0x04
|
44
|
+
|
45
|
+
def initialize(msg = 'SERVER DEVICE FAILURE')
|
46
|
+
super
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class Acknowledge < ModbusError
|
51
|
+
CODE = 0x05
|
52
|
+
|
53
|
+
def initialize(msg = 'ACKNOWLEDGE')
|
54
|
+
super
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
class ServerDeviceBusy < ModbusError
|
59
|
+
CODE = 0x06
|
60
|
+
|
61
|
+
def initialize(msg = 'SERVER DEVICE BUSY')
|
62
|
+
super
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class MemoryParityError < ModbusError
|
67
|
+
CODE = 0x08
|
68
|
+
|
69
|
+
def initialize(msg = 'MEMORY PARITY ERROR')
|
70
|
+
super
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class GatewayPathUnavailable < ModbusError
|
75
|
+
CODE = 0x0A
|
76
|
+
|
77
|
+
def initialize(msg = 'GATEWAY PATH UNAVAILABLE')
|
78
|
+
super
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class GatewayTargetDeviceFailedToRespond < ModbusError
|
83
|
+
CODE = 0x0B
|
84
|
+
|
85
|
+
def initialize(msg = 'GATEWAY TARGET DEVICE FAILED TO RESPOND')
|
86
|
+
super
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def self.find_exception(code)
|
91
|
+
exceptions = [
|
92
|
+
IllegalFunction,
|
93
|
+
IllegalDataAddress,
|
94
|
+
IllegalDataValue,
|
95
|
+
ServerDeviceFailure,
|
96
|
+
Acknowledge,
|
97
|
+
ServerDeviceBusy,
|
98
|
+
MemoryParityError,
|
99
|
+
GatewayPathUnavailable,
|
100
|
+
GatewayTargetDeviceFailedToRespond
|
101
|
+
]
|
102
|
+
|
103
|
+
exceptions.find { |e| e::CODE == code } || RuntimeError
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
|
5
|
+
require 'modbus/exceptions'
|
6
|
+
require 'modbus/pdu/pdu'
|
7
|
+
require 'modbus/adu/adu'
|
8
|
+
require 'modbus/transaction/transaction'
|
9
|
+
require 'modbus/connection/connection'
|
10
|
+
require 'modbus/register/register'
|
11
|
+
require 'modbus/client'
|
12
|
+
require 'modbus/server'
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
|
5
|
+
module Modbus
|
6
|
+
|
7
|
+
class PDU
|
8
|
+
|
9
|
+
# PDU for modbus exception (response message)
|
10
|
+
#
|
11
|
+
class Exception < PDU
|
12
|
+
attr_accessor :exception_code
|
13
|
+
|
14
|
+
|
15
|
+
def self.create(func_code, error)
|
16
|
+
obj = self.new nil, func_code + 0x80
|
17
|
+
obj.exception_code = error.code
|
18
|
+
obj
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# Decodes a PDU from protocol data.
|
23
|
+
#
|
24
|
+
# @param data [Modbus::ProtocolData] The protocol data to decode.
|
25
|
+
#
|
26
|
+
def decode(data)
|
27
|
+
@exception_code = data.shift_byte
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
# Encodes a PDU into protocol data.
|
32
|
+
#
|
33
|
+
# @return [Modbus::ProtocolData] The protocol data representation of this object.
|
34
|
+
#
|
35
|
+
def encode
|
36
|
+
data = super
|
37
|
+
data.push_byte @exception_code
|
38
|
+
data
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# Returns the length of the PDU in bytes.
|
43
|
+
#
|
44
|
+
# @return [Integer] The length.
|
45
|
+
#
|
46
|
+
def length
|
47
|
+
2
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
end # Modbus
|
55
|
+
|
56
|
+
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
require 'modbus/pdu/exception'
|
5
|
+
require 'modbus/pdu/read_input_status'
|
6
|
+
require 'modbus/pdu/read_registers'
|
7
|
+
require 'modbus/pdu/read_input_registers'
|
8
|
+
require 'modbus/pdu/read_holding_registers'
|
9
|
+
require 'modbus/pdu/write_multiple_registers'
|
10
|
+
|
11
|
+
module Modbus
|
12
|
+
|
13
|
+
|
14
|
+
# Base class modelling a Modbus PDU (Protocol Data Unit)
|
15
|
+
#
|
16
|
+
class PDU
|
17
|
+
|
18
|
+
# Maps the Modbus function code to the corresponding class (for request messages)
|
19
|
+
REQ_PDU_MAP = {}
|
20
|
+
[
|
21
|
+
ReadInputStatusRequest,
|
22
|
+
ReadInputRegistersRequest,
|
23
|
+
ReadHoldingRegistersRequest,
|
24
|
+
WriteMultipleRegistersRequest
|
25
|
+
].each { |klass| REQ_PDU_MAP[klass::FUNC_CODE] = klass }
|
26
|
+
|
27
|
+
|
28
|
+
# Maps the Modbus function code to the corresponding class (for response messages)
|
29
|
+
RSP_PDU_MAP = {}
|
30
|
+
[
|
31
|
+
ReadInputStatusResponse,
|
32
|
+
ReadInputRegistersResponse,
|
33
|
+
ReadHoldingRegistersResponse,
|
34
|
+
WriteMultipleRegistersResponse
|
35
|
+
].each { |klass| RSP_PDU_MAP[klass::FUNC_CODE] = klass }
|
36
|
+
|
37
|
+
|
38
|
+
attr_reader :creation_time, :func_code
|
39
|
+
|
40
|
+
|
41
|
+
# Factory method for creating PDUs. Decodes a PDU from protocol data and returns a new PDU instance.
|
42
|
+
#
|
43
|
+
# @param type [Symbol] The type of PDU which should be created. Must be :request or :response.
|
44
|
+
# @param func_code [Integer] The modbus function code of the PDU
|
45
|
+
# @param data [Modbus::ProtocolData] The protocol data to decode.
|
46
|
+
# @return [Modbus::PDU] The created PDU instance.
|
47
|
+
#
|
48
|
+
def self.create(type, func_code, data)
|
49
|
+
map = { :request => REQ_PDU_MAP, :response => RSP_PDU_MAP }[type]
|
50
|
+
fail ArgumentError, "Type is expected to be :request or :response, got #{type}" unless map
|
51
|
+
|
52
|
+
# 0x80 is the offset in case of a modbus exception
|
53
|
+
klass = func_code > 0x80 ? PDU::Exception : map[func_code]
|
54
|
+
fail IllegalFunction, "Unknown function code 0x#{func_code.to_s(16)}" if klass.nil?
|
55
|
+
|
56
|
+
klass.new data, func_code
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
# Initializes a new PDU instance. Decodes from protocol data if given.
|
61
|
+
#
|
62
|
+
# @param data [Modbus::ProtocolData] The protocol data to decode.
|
63
|
+
# @param func_code [Fixnum] Modbus function code.
|
64
|
+
#
|
65
|
+
def initialize(data = nil, func_code = nil)
|
66
|
+
@creation_time = Time.now.utc
|
67
|
+
@func_code = func_code || self.class::FUNC_CODE
|
68
|
+
|
69
|
+
self.decode data if data
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
# Encodes a PDU into protocol data.
|
74
|
+
#
|
75
|
+
# @return [Modbus::ProtocolData] The protocol data representation of this object.
|
76
|
+
#
|
77
|
+
def encode
|
78
|
+
data = ProtocolData.new
|
79
|
+
data.push_byte @func_code
|
80
|
+
data
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end # Modbus
|
86
|
+
|
87
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
|
5
|
+
module Modbus
|
6
|
+
|
7
|
+
class PDU
|
8
|
+
|
9
|
+
# PDU for modbus function "read holding register" (request message)
|
10
|
+
#
|
11
|
+
class ReadHoldingRegistersRequest < ReadRegistersRequest
|
12
|
+
FUNC_CODE = 0x03
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
# PDU for modbus function "read holding register" (response message)
|
17
|
+
#
|
18
|
+
class ReadHoldingRegistersResponse < ReadRegistersResponse
|
19
|
+
FUNC_CODE = 0x03
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end # Modbus
|
25
|
+
|
26
|
+
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
|
5
|
+
module Modbus
|
6
|
+
|
7
|
+
class PDU
|
8
|
+
|
9
|
+
# PDU for modbus function "read input register" (request message)
|
10
|
+
#
|
11
|
+
class ReadInputRegistersRequest < ReadRegistersRequest
|
12
|
+
FUNC_CODE = 0x04
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
# PDU for modbus function "read input register" (response message)
|
17
|
+
#
|
18
|
+
class ReadInputRegistersResponse < ReadRegistersResponse
|
19
|
+
FUNC_CODE = 0x04
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end # Modbus
|
25
|
+
|
26
|
+
|