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.
Files changed (41) hide show
  1. data/.gitignore +5 -0
  2. data/Gemfile +4 -0
  3. data/LICENSE.txt +373 -0
  4. data/README.md +30 -0
  5. data/Rakefile +15 -0
  6. data/em-modbus.gemspec +26 -0
  7. data/examples/client.rb +33 -0
  8. data/examples/client_batch.rb +47 -0
  9. data/examples/server.rb +43 -0
  10. data/lib/modbus/adu/adu.rb +5 -0
  11. data/lib/modbus/adu/rtu_adu.rb +18 -0
  12. data/lib/modbus/adu/tcp_adu.rb +82 -0
  13. data/lib/modbus/client.rb +64 -0
  14. data/lib/modbus/connection/base.rb +41 -0
  15. data/lib/modbus/connection/connection.rb +7 -0
  16. data/lib/modbus/connection/protocol_data.rb +78 -0
  17. data/lib/modbus/connection/tcp_client.rb +82 -0
  18. data/lib/modbus/connection/tcp_server.rb +36 -0
  19. data/lib/modbus/exceptions.rb +106 -0
  20. data/lib/modbus/modbus.rb +12 -0
  21. data/lib/modbus/pdu/exception.rb +56 -0
  22. data/lib/modbus/pdu/pdu.rb +87 -0
  23. data/lib/modbus/pdu/read_holding_registers.rb +26 -0
  24. data/lib/modbus/pdu/read_input_registers.rb +26 -0
  25. data/lib/modbus/pdu/read_input_status.rb +147 -0
  26. data/lib/modbus/pdu/read_registers.rb +135 -0
  27. data/lib/modbus/pdu/write_multiple_registers.rb +158 -0
  28. data/lib/modbus/register/base.rb +19 -0
  29. data/lib/modbus/register/bit_register.rb +49 -0
  30. data/lib/modbus/register/register.rb +7 -0
  31. data/lib/modbus/register/word_register.rb +30 -0
  32. data/lib/modbus/server.rb +120 -0
  33. data/lib/modbus/transaction/base.rb +25 -0
  34. data/lib/modbus/transaction/client.rb +162 -0
  35. data/lib/modbus/transaction/server.rb +95 -0
  36. data/lib/modbus/transaction/transaction.rb +39 -0
  37. data/lib/modbus/version.rb +7 -0
  38. data/lib/modbus.rb +2 -0
  39. data/spec/adi_spec.rb +16 -0
  40. data/spec/spec_helper.rb +1 -0
  41. 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
+
@@ -0,0 +1,7 @@
1
+ # Copyright © 2016 Andy Rohr <andy.rohr@mindclue.ch>
2
+ # All rights reserved.
3
+
4
+
5
+ module Modbus
6
+ VERSION = '0.1.0'
7
+ end
data/lib/modbus.rb ADDED
@@ -0,0 +1,2 @@
1
+ require 'modbus/version'
2
+ require 'modbus/modbus'
data/spec/adi_spec.rb ADDED
@@ -0,0 +1,16 @@
1
+ require 'rspec'
2
+ require 'modbus'
3
+
4
+ describe Modbus do
5
+
6
+ describe Modbus::Transaction do
7
+ it 'should do something' do
8
+
9
+
10
+
11
+
12
+ end
13
+
14
+ end
15
+
16
+ end
@@ -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