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,162 @@
|
|
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 Transaction
|
9
|
+
|
10
|
+
class Client < Base
|
11
|
+
include EM::Deferrable
|
12
|
+
|
13
|
+
|
14
|
+
def initialize(conn, timeout)
|
15
|
+
super conn
|
16
|
+
@timeout = timeout
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
# Try to decode a response ADU from some recevied bytes and handle the ADU if decoding was successful.
|
21
|
+
#
|
22
|
+
# @param buffer [String] The bytes received from the network.
|
23
|
+
# @param conn [Modbus::Connection::TCPClient] An EM connection object to work on.
|
24
|
+
# @return [true, false] True, if there where enough bytes in the buffer and decoding was successful.
|
25
|
+
#
|
26
|
+
def self.recv_adu(buffer, conn)
|
27
|
+
adu = Modbus::TCPADU.new
|
28
|
+
|
29
|
+
if adu.decode :response, buffer, conn
|
30
|
+
transaction = conn.pick_pending_transaction adu.transaction_ident
|
31
|
+
fail ClientError, "Transaction ident #{adu.transaction_ident} not found!" unless transaction
|
32
|
+
transaction.handle_response adu
|
33
|
+
return true
|
34
|
+
else
|
35
|
+
return false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
# Sends a request for the modbus function "read input status" asynchronusly. This method is non-blocking.
|
41
|
+
#
|
42
|
+
# @param start_addr [Integer] The starting modbus register address to read registers from.
|
43
|
+
# @param bit_count [Integer] The number of input bits to read.
|
44
|
+
#
|
45
|
+
def read_input_status(start_addr, bit_count)
|
46
|
+
pdu = PDU::ReadInputStatusRequest.new
|
47
|
+
pdu.start_addr = start_addr
|
48
|
+
pdu.bit_count = bit_count
|
49
|
+
|
50
|
+
send_pdu pdu
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
# Sends a request for the modbus function "read holding registers" asynchronusly. This method is non-blocking.
|
55
|
+
#
|
56
|
+
# @param start_addr [Integer] The starting modbus register address to read registers from.
|
57
|
+
# @param reg_count [Integer] The number of registers to read.
|
58
|
+
#
|
59
|
+
def read_holding_registers(start_addr, reg_count)
|
60
|
+
pdu = PDU::ReadHoldingRegistersRequest.new
|
61
|
+
pdu.start_addr = start_addr
|
62
|
+
pdu.reg_count = reg_count
|
63
|
+
|
64
|
+
send_pdu pdu
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
# Sends a request for the modbus function "write mutliple registers" asynchronusly. This method is non-blocking.
|
69
|
+
#
|
70
|
+
# @param start_addr [Integer] The starting modbus register address to read registers from.
|
71
|
+
# @param reg_values [Integer] The register values to write.
|
72
|
+
#
|
73
|
+
def write_multiple_registers(start_addr, reg_values)
|
74
|
+
pdu = PDU::WriteMultipleRegistersRequest.new
|
75
|
+
pdu.start_addr = start_addr
|
76
|
+
pdu.reg_values = reg_values
|
77
|
+
|
78
|
+
send_pdu pdu
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
# Constructs a ADU using a PDU and send it asynchronusly to the server.
|
83
|
+
# The created ADU is stored internally and is matched to the response when the response is available.
|
84
|
+
#
|
85
|
+
# @param pdu [Modbus::PDU] The PDU to send.
|
86
|
+
# @return [Modbus::TCPADU] The sent ADU.
|
87
|
+
#
|
88
|
+
def send_pdu(pdu)
|
89
|
+
@request_adu = TCPADU.new pdu, @conn.next_transaction_ident
|
90
|
+
@conn.track_transaction self
|
91
|
+
@conn.send_data @request_adu.encode
|
92
|
+
|
93
|
+
@timeout_timer = EM.add_timer @timeout do
|
94
|
+
@conn.pick_pending_transaction @request_adu.transaction_ident
|
95
|
+
set_deferred_failure "Timeout #{@timeout}s expired"
|
96
|
+
end
|
97
|
+
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
# Handles a recevied ADU and calls the relevant callback.
|
103
|
+
# The corresponding request ADU is matched and cleaned up.
|
104
|
+
#
|
105
|
+
# @param adu [Modbus::ADU] The ADU to handle.
|
106
|
+
#
|
107
|
+
def handle_response(adu)
|
108
|
+
@response_adu = adu
|
109
|
+
fail Modbus.find_exception(@response_adu.pdu.exception_code), "Request PDU: #{@request_adu.pdu.inspect}" if @response_adu.pdu.is_a? PDU::Exception
|
110
|
+
|
111
|
+
transaction = TRANSACTIONS.find { |t| @response_adu.pdu.is_a? t[:response] }
|
112
|
+
fail "Unknown PDU #{@response_adu.pdu.inspect}" unless transaction
|
113
|
+
fail "Unexpected last sent PDU: #{@request_adu.pdu.inspect}" unless @request_adu.pdu.is_a? transaction[:request]
|
114
|
+
|
115
|
+
value = send transaction[:handler]
|
116
|
+
EM.cancel_timer @timeout_timer
|
117
|
+
set_deferred_success @request_adu.pdu.start_addr, value
|
118
|
+
|
119
|
+
rescue => e
|
120
|
+
set_deferred_failure "#{e.class} - #{e.message}"
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def handle_read_input_status
|
125
|
+
@response_adu.pdu.bit_values
|
126
|
+
end
|
127
|
+
|
128
|
+
|
129
|
+
def handle_read_holding_registers
|
130
|
+
@response_adu.pdu.reg_values
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
def handle_write_multiple_registers
|
135
|
+
@response_adu.pdu.reg_count
|
136
|
+
end
|
137
|
+
|
138
|
+
|
139
|
+
# Returns the transaction ident of this transaction which is consistent to the ident of the request PDU.
|
140
|
+
#
|
141
|
+
# @return [Integer] The transaction ident.
|
142
|
+
#
|
143
|
+
def transaction_ident
|
144
|
+
@request_adu.transaction_ident
|
145
|
+
end
|
146
|
+
|
147
|
+
|
148
|
+
# Returns the duration of a transaction.
|
149
|
+
#
|
150
|
+
# @return [Float] Time time in seconds.
|
151
|
+
#
|
152
|
+
def transaction_time
|
153
|
+
fail ClientError, 'Response ADU unknown. Can not calcluate transaction time.' unless @response_adu
|
154
|
+
((@response_adu.pdu.creation_time - @request_adu.pdu.creation_time) * 1000).round
|
155
|
+
end
|
156
|
+
|
157
|
+
end # Base
|
158
|
+
|
159
|
+
end # Request
|
160
|
+
|
161
|
+
end # Modbus
|
162
|
+
|
@@ -0,0 +1,95 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
|
5
|
+
module Modbus
|
6
|
+
module Transaction
|
7
|
+
|
8
|
+
class Server < Base
|
9
|
+
|
10
|
+
|
11
|
+
# Try to decode a response ADU from some recevied bytes and handle the ADU if decoding was successful.
|
12
|
+
#
|
13
|
+
# @param buffer [String] The bytes received from the network.
|
14
|
+
# @param conn [Modbus::Connection::TCPServer] An EM connection object to work on.
|
15
|
+
# @return [true, false] True, if there where enough bytes in the buffer and decoding was successful.
|
16
|
+
#
|
17
|
+
def self.recv_adu(buffer, conn)
|
18
|
+
adu = Modbus::TCPADU.new
|
19
|
+
|
20
|
+
if adu.decode :request, buffer, conn
|
21
|
+
transaction = self.new conn
|
22
|
+
transaction.handle_request adu
|
23
|
+
return true
|
24
|
+
else
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
# Constructs a ADU using a PDU and send it asynchronusly to the server.
|
31
|
+
# The created ADU is stored internally and is matched to the response when the response is available.
|
32
|
+
#
|
33
|
+
# @param pdu [Modbus::PDU] The PDU to send.
|
34
|
+
# @return [Modbus::TCPADU] The sent ADU.
|
35
|
+
#
|
36
|
+
def send_pdu(pdu)
|
37
|
+
@response_adu = TCPADU.new pdu, @request_adu.transaction_ident
|
38
|
+
@conn.send_data @response_adu.encode
|
39
|
+
self
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
# Handles a recevied ADU and calls the relevant callback.
|
44
|
+
# The corresponding request ADU is matched and cleaned up.
|
45
|
+
#
|
46
|
+
# @param adu [Modbus::ADU] The ADU to handle.
|
47
|
+
#
|
48
|
+
def handle_request(adu)
|
49
|
+
@request_adu = adu
|
50
|
+
|
51
|
+
transaction = TRANSACTIONS.find { |t| adu.pdu.is_a? t[:request] }
|
52
|
+
fail IllegalFunction, "Unknown PDU #{adu.pdu.inspect}" unless transaction
|
53
|
+
fail ServerDeviceFailure, "Unexpected last sent PDU: #{@request_adu.pdu.inspect}" unless @request_adu.pdu.is_a? transaction[:request]
|
54
|
+
|
55
|
+
pdu = send transaction[:handler]
|
56
|
+
send_pdu pdu
|
57
|
+
|
58
|
+
rescue ModbusError => error
|
59
|
+
send_pdu PDU::Exception.create(adu.pdu.func_code, error)
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def handle_read_input_registers
|
64
|
+
read_registers 30001, PDU::ReadInputRegistersResponse
|
65
|
+
end
|
66
|
+
|
67
|
+
|
68
|
+
def handle_read_holding_registers
|
69
|
+
read_registers 40001, PDU::ReadHoldingRegistersResponse
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
def read_registers(offset, response_class)
|
74
|
+
reg_values = @conn.read_registers offset + @request_adu.pdu.start_addr, @request_adu.pdu.reg_count
|
75
|
+
pdu = response_class.new
|
76
|
+
pdu.reg_values = reg_values
|
77
|
+
pdu
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
def handle_write_multiple_registers
|
82
|
+
reg_count = @conn.write_registers 40001 + @request_adu.pdu.start_addr, @request_adu.pdu.reg_values
|
83
|
+
pdu = PDU::WriteMultipleRegistersResponse.new
|
84
|
+
pdu.start_addr = @request_adu.pdu.start_addr
|
85
|
+
pdu.reg_count = reg_count
|
86
|
+
pdu
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
end # Server
|
91
|
+
|
92
|
+
end # Transaction
|
93
|
+
end # Modbus
|
94
|
+
|
95
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
|
2
|
+
# All rights reserved.
|
3
|
+
|
4
|
+
require 'modbus/transaction/base'
|
5
|
+
require 'modbus/transaction/client'
|
6
|
+
require 'modbus/transaction/server'
|
7
|
+
|
8
|
+
module Modbus
|
9
|
+
module Transaction
|
10
|
+
|
11
|
+
# Valid transactions (messages) and value handler config
|
12
|
+
TRANSACTIONS = [
|
13
|
+
{
|
14
|
+
:request => PDU::ReadInputStatusRequest,
|
15
|
+
:response => PDU::ReadInputStatusResponse,
|
16
|
+
:handler => :handle_read_input_status
|
17
|
+
},
|
18
|
+
{
|
19
|
+
:request => PDU::ReadInputRegistersRequest,
|
20
|
+
:response => PDU::ReadInputRegistersResponse,
|
21
|
+
:handler => :handle_read_input_registers
|
22
|
+
},
|
23
|
+
{
|
24
|
+
:request => PDU::ReadHoldingRegistersRequest,
|
25
|
+
:response => PDU::ReadHoldingRegistersResponse,
|
26
|
+
:handler => :handle_read_holding_registers
|
27
|
+
},
|
28
|
+
{
|
29
|
+
:request => PDU::WriteMultipleRegistersRequest,
|
30
|
+
:response => PDU::WriteMultipleRegistersResponse,
|
31
|
+
:handler => :handle_write_multiple_registers
|
32
|
+
}
|
33
|
+
]
|
34
|
+
|
35
|
+
end # Transaction
|
36
|
+
|
37
|
+
end # Modbus
|
38
|
+
|
39
|
+
|
data/lib/modbus.rb
ADDED
data/spec/adi_spec.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'modbus'
|
metadata
ADDED
@@ -0,0 +1,162 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: em-modbus
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Andy Rohr
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2017-07-28 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
type: :runtime
|
22
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ">="
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
hash: 3
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
requirement: *id001
|
32
|
+
prerelease: false
|
33
|
+
name: eventmachine
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
type: :development
|
36
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ~>
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
hash: 3
|
42
|
+
segments:
|
43
|
+
- 1
|
44
|
+
- 6
|
45
|
+
version: "1.6"
|
46
|
+
requirement: *id002
|
47
|
+
prerelease: false
|
48
|
+
name: bundler
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
type: :development
|
51
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
52
|
+
none: false
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
hash: 3
|
57
|
+
segments:
|
58
|
+
- 0
|
59
|
+
version: "0"
|
60
|
+
requirement: *id003
|
61
|
+
prerelease: false
|
62
|
+
name: rake
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
type: :development
|
65
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
requirement: *id004
|
75
|
+
prerelease: false
|
76
|
+
name: rspec
|
77
|
+
description: Ruby implementation of the Modbus protocol.
|
78
|
+
email:
|
79
|
+
- andy.rohr@mindclue.ch
|
80
|
+
executables: []
|
81
|
+
|
82
|
+
extensions: []
|
83
|
+
|
84
|
+
extra_rdoc_files: []
|
85
|
+
|
86
|
+
files:
|
87
|
+
- .gitignore
|
88
|
+
- Gemfile
|
89
|
+
- LICENSE.txt
|
90
|
+
- README.md
|
91
|
+
- Rakefile
|
92
|
+
- em-modbus.gemspec
|
93
|
+
- examples/client.rb
|
94
|
+
- examples/client_batch.rb
|
95
|
+
- examples/server.rb
|
96
|
+
- lib/modbus.rb
|
97
|
+
- lib/modbus/adu/adu.rb
|
98
|
+
- lib/modbus/adu/rtu_adu.rb
|
99
|
+
- lib/modbus/adu/tcp_adu.rb
|
100
|
+
- lib/modbus/client.rb
|
101
|
+
- lib/modbus/connection/base.rb
|
102
|
+
- lib/modbus/connection/connection.rb
|
103
|
+
- lib/modbus/connection/protocol_data.rb
|
104
|
+
- lib/modbus/connection/tcp_client.rb
|
105
|
+
- lib/modbus/connection/tcp_server.rb
|
106
|
+
- lib/modbus/exceptions.rb
|
107
|
+
- lib/modbus/modbus.rb
|
108
|
+
- lib/modbus/pdu/exception.rb
|
109
|
+
- lib/modbus/pdu/pdu.rb
|
110
|
+
- lib/modbus/pdu/read_holding_registers.rb
|
111
|
+
- lib/modbus/pdu/read_input_registers.rb
|
112
|
+
- lib/modbus/pdu/read_input_status.rb
|
113
|
+
- lib/modbus/pdu/read_registers.rb
|
114
|
+
- lib/modbus/pdu/write_multiple_registers.rb
|
115
|
+
- lib/modbus/register/base.rb
|
116
|
+
- lib/modbus/register/bit_register.rb
|
117
|
+
- lib/modbus/register/register.rb
|
118
|
+
- lib/modbus/register/word_register.rb
|
119
|
+
- lib/modbus/server.rb
|
120
|
+
- lib/modbus/transaction/base.rb
|
121
|
+
- lib/modbus/transaction/client.rb
|
122
|
+
- lib/modbus/transaction/server.rb
|
123
|
+
- lib/modbus/transaction/transaction.rb
|
124
|
+
- lib/modbus/version.rb
|
125
|
+
- spec/adi_spec.rb
|
126
|
+
- spec/spec_helper.rb
|
127
|
+
homepage: https://github.com/arohr/em-modbus
|
128
|
+
licenses:
|
129
|
+
- MPLv2
|
130
|
+
post_install_message:
|
131
|
+
rdoc_options: []
|
132
|
+
|
133
|
+
require_paths:
|
134
|
+
- lib
|
135
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
136
|
+
none: false
|
137
|
+
requirements:
|
138
|
+
- - ">="
|
139
|
+
- !ruby/object:Gem::Version
|
140
|
+
hash: 3
|
141
|
+
segments:
|
142
|
+
- 0
|
143
|
+
version: "0"
|
144
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
145
|
+
none: false
|
146
|
+
requirements:
|
147
|
+
- - ">="
|
148
|
+
- !ruby/object:Gem::Version
|
149
|
+
hash: 3
|
150
|
+
segments:
|
151
|
+
- 0
|
152
|
+
version: "0"
|
153
|
+
requirements: []
|
154
|
+
|
155
|
+
rubyforge_project:
|
156
|
+
rubygems_version: 1.8.29
|
157
|
+
signing_key:
|
158
|
+
specification_version: 3
|
159
|
+
summary: Modbus Client and Server for eventmachine.
|
160
|
+
test_files:
|
161
|
+
- spec/adi_spec.rb
|
162
|
+
- spec/spec_helper.rb
|