sanderjd-rmodbus 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,49 @@
1
+ = RModBus
2
+
3
+ *RModBus* - free implementation of protocol ModBus.
4
+
5
+ == Feature
6
+
7
+ * Client ModBus-TCP
8
+ * Support functions:
9
+ * 01 (0x01) Read Coils
10
+ * 02 (0x02) Read Discrete Inputs
11
+ * 03 (0x03) Read Holding Registers
12
+ * 04 (0x04) Read Input Registers
13
+ * 05 (0x05) Write Single Coil
14
+ * 06 (0x06) Write Single Register
15
+ * 15 (0x0F) Write Multiple Coils
16
+ * 16 (0x10) Write Multiple registers
17
+
18
+ == Installation
19
+
20
+ You can install RModBus with the following command
21
+
22
+ $ rake install
23
+
24
+ from its distribution directory
25
+
26
+ == Gem installation
27
+
28
+ Download and install RModBus with the following
29
+
30
+ $ gem sources -a http://gems.github.com
31
+ $ gem install -r sanderjd-rmodbus
32
+
33
+ == CSM
34
+
35
+ You can checkout source code from the GIT repositry
36
+
37
+ $ git clone git://github.com/sanderjd/rmodbus.git
38
+
39
+ == Reference
40
+
41
+ RModBus project: http://rubyforge.org/projects/rmodbus
42
+
43
+ ModBus community: http://www.modbus-ida.org
44
+
45
+
46
+
47
+
48
+
49
+
@@ -0,0 +1,49 @@
1
+ begin
2
+ require 'rubygems'
3
+ rescue Exception
4
+ end
5
+
6
+ begin
7
+ require 'spec/rake/spectask'
8
+
9
+ Spec::Rake::SpecTask.new do |t|
10
+ t.spec_opts = ['-c']
11
+ t.libs << 'lib'
12
+ t.spec_files = FileList['spec/**/*_spec.rb']
13
+ t.rcov = true
14
+ end
15
+ rescue Exception
16
+ puts 'RSpec not available. Install it with: sudo gem install rspec'
17
+ end
18
+
19
+ begin
20
+ require 'jeweler'
21
+ Jeweler::Tasks.new do |s|
22
+ s.name = "rmodbus"
23
+ s.summary = "RModBus - free implementation of protocol ModBus"
24
+ s.description = "A free Ruby implementation of the ModBus protocol"
25
+ s.email = 'sanderjd@gmail.com'
26
+ s.homepage = 'http://rubyforge.org/var/svn/rmodbus/trunk'
27
+ s.authors = ['Aleksey Timin', 'D.Samatov', 'James Sanders']
28
+ s.has_rdoc = true
29
+ s.files = ["VERSION.yml",
30
+ "AUTHORS",
31
+ "CHANGES",
32
+ "LICENSE",
33
+ "README",
34
+ "Rakefile",
35
+ "lib/rmodbus",
36
+ "lib/rmodbus/client.rb",
37
+ "lib/rmodbus/exceptions.rb",
38
+ "lib/rmodbus/tcp_client.rb",
39
+ "lib/rmodbus/tcp_server.rb",
40
+ "lib/rmodbus.rb",
41
+ "spec/client_spec.rb",
42
+ "spec/ext_spec.rb",
43
+ "spec/rtu_client_spec.rb",
44
+ "spec/tcp_client_spec.rb",
45
+ "spec/tcp_server_spec.rb"]
46
+ end
47
+ rescue LoadError
48
+ puts 'Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com'
49
+ end
@@ -0,0 +1,4 @@
1
+ ---
2
+ :minor: 2
3
+ :patch: 0
4
+ :major: 0
@@ -0,0 +1,13 @@
1
+ # RModBus - free implementation of ModBus protocol on Ruby.
2
+ # Copyright (C) 2008 Timin Aleksey
3
+ # This program is free software: you can redistribute it and/or modify
4
+ # it under the terms of the GNU General Public License as published by
5
+ # the Free Software Foundation, either version 3 of the License, or
6
+ # (at your option) any later version.
7
+ #
8
+ # This program is distributed in the hope that it will be useful,
9
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
10
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
+ # GNU General Public License for more details.
12
+ require 'rmodbus/tcp_client'
13
+ require 'rmodbus/tcp_server'
@@ -0,0 +1,247 @@
1
+ # RModBus - free implementation of ModBus protocol on Ruby.
2
+ #
3
+ # Copyright (C) 2008 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
+
15
+ require 'rmodbus/exceptions'
16
+
17
+ class String
18
+
19
+ def to_int16
20
+ self[0]*256 + self[1]
21
+ end
22
+
23
+ def to_array_int16
24
+ array_int16 = []
25
+ i = 0
26
+ while(i < self.size) do
27
+ array_int16 << self[i]*256 + self[i+1]
28
+ i += 2
29
+ end
30
+ array_int16
31
+ end
32
+
33
+ def to_array_bit
34
+ array_bit = []
35
+ self.each_byte do |byte|
36
+ mask = 0x01
37
+ 8.times {
38
+ unless (mask & byte) == 0
39
+ array_bit << 1
40
+ else
41
+ array_bit << 0
42
+ end
43
+ mask = mask << 1
44
+ }
45
+ end
46
+ array_bit
47
+ end
48
+
49
+ end
50
+
51
+ class Integer
52
+ def to_bytes
53
+ (self >> 8).chr + (self & 0xff).chr
54
+ end
55
+ end
56
+
57
+ class Array
58
+
59
+ def to_ints16
60
+ s = ""
61
+ self.each do |int16|
62
+ s << int16.to_bytes
63
+ end
64
+ s
65
+ end
66
+
67
+ def bits_to_bytes
68
+ int16 = 0
69
+ s = ""
70
+ mask = 0x01
71
+
72
+ self.each do |bit|
73
+ int16 |= mask if bit > 0
74
+ mask <<= 1
75
+ if mask == 0x100
76
+ mask = 0x01
77
+ s << int16.chr
78
+ int16 = 0
79
+ end
80
+ end
81
+ s << int16.chr unless mask == 0x01
82
+ end
83
+
84
+ end
85
+
86
+ module ModBus
87
+
88
+ class Client
89
+
90
+ include Errors
91
+
92
+ # Number of times to retry on connection and read timeouts
93
+ CONNECTION_RETRIES = 10
94
+ READ_RETRIES = 10
95
+
96
+ # Read value *ncoils* coils starting with *addr*
97
+ #
98
+ # Return array of their values
99
+ def read_coils(addr, ncoils)
100
+ query("\x1" + addr.to_bytes + ncoils.to_bytes).to_array_bit[0..ncoils-1]
101
+ end
102
+
103
+ # Read value *ncoils* discrete inputs starting with *addr*
104
+ #
105
+ # Return array of their values
106
+ def read_discrete_inputs(addr, ncoils)
107
+ query("\x2" + addr.to_bytes + ncoils.to_bytes).to_array_bit[0..ncoils-1]
108
+ end
109
+
110
+ # Deprecated version of read_discrete_inputs
111
+ def read_discret_inputs(addr, ncoils)
112
+ warn "[DEPRECATION] `read_discret_inputs` is deprecated. Please use `read_discrete_inputs` instead."
113
+ read_discrete_inputs(addr, ncoils)
114
+ end
115
+
116
+ # Read value *nreg* holding registers starting with *addr*
117
+ #
118
+ # Return array of their values
119
+ def read_holding_registers(addr, nreg)
120
+ query("\x3" + addr.to_bytes + nreg.to_bytes).to_array_int16
121
+ end
122
+
123
+ # Read value *nreg* input registers starting with *addr*
124
+ #
125
+ # Return array of their values
126
+ def read_input_registers(addr, nreg)
127
+ query("\x4" + addr.to_bytes + nreg.to_bytes).to_array_int16
128
+ end
129
+
130
+ # Write *val* in *addr* coil
131
+ #
132
+ # if *val* lager 0 write 1
133
+ #
134
+ # Return self
135
+ def write_single_coil(addr, val)
136
+ if val == 0
137
+ query("\x5" + addr.to_bytes + 0.to_bytes)
138
+ else
139
+ query("\x5" + addr.to_bytes + 0xff00.to_bytes)
140
+ end
141
+ self
142
+ end
143
+
144
+ # Write *val* in *addr* register
145
+ #
146
+ # Return self
147
+ def write_single_register(addr, val)
148
+ query("\x6" + addr.to_bytes + val.to_bytes)
149
+ self
150
+ end
151
+
152
+ # Write *val* in coils starting with *addr*
153
+ #
154
+ # *val* it is array of bits
155
+ #
156
+ # Return self
157
+ def write_multiple_coils(addr, val)
158
+ nbyte = ((val.size-1) >> 3) + 1
159
+ sum = 0
160
+ (val.size - 1).downto(0) do |i|
161
+ sum = sum << 1
162
+ sum |= 1 if val[i] > 0
163
+ end
164
+
165
+ s_val = ""
166
+ nbyte.times do
167
+ s_val << (sum & 0xff).chr
168
+ sum >>= 8
169
+ end
170
+
171
+ query("\xf" + addr.to_bytes + val.size.to_bytes + nbyte.chr + s_val)
172
+ self
173
+ end
174
+
175
+ # Write *val* in registers starting with *addr*
176
+ #
177
+ # *val* it is array of integer
178
+ #
179
+ # Return self
180
+ def write_multiple_registers(addr, val)
181
+ s_val = ""
182
+ val.each do |reg|
183
+ s_val << reg.to_bytes
184
+ end
185
+
186
+ query("\x10" + addr.to_bytes + val.size.to_bytes + (val.size * 2).chr + s_val)
187
+ self
188
+ end
189
+
190
+ # Write *current value & and_mask | or mask in *addr* register
191
+ #
192
+ # Return self
193
+ def mask_write_register(addr, and_mask, or_mask)
194
+ query("\x16" + addr.to_bytes + and_mask.to_bytes + or_mask.to_bytes)
195
+ self
196
+ end
197
+
198
+ def query(pdu)
199
+ send_pdu(pdu)
200
+
201
+ tried = 0
202
+ begin
203
+ timeout(1, ModBusTimeout) do
204
+ pdu = read_pdu
205
+ end
206
+ rescue ModBusTimeout => err
207
+ tried += 1
208
+ retry unless tried >= READ_RETRIES
209
+ raise ModBusTimeout.new, 'Timed out during read attempt'
210
+ end
211
+
212
+ if pdu[0].to_i >= 0x80
213
+ case pdu[1].to_i
214
+ when 1
215
+ raise IllegalFunction.new, "The function code received in the query is not an allowable action for the server"
216
+ when 2
217
+ raise IllegalDataAddress.new, "The data address received in the query is not an allowable address for the server"
218
+ when 3
219
+ raise IllegalDataValue.new, "A value contained in the query data field is not an allowable value for server"
220
+ when 4
221
+ raise SlaveDeviceFailure.new, "An unrecoverable error occurred while the server was attempting to perform the requested action"
222
+ when 5
223
+ raise Acknowledge.new, "The server has accepted the request and is processing it, but a long duration of time will be required to do so"
224
+ when 6
225
+ raise SlaveDeviceBus.new, "The server is engaged in processing a long duration program command"
226
+ when 8
227
+ raise MemoryParityError.new, "The extended file area failed to pass a consistency check"
228
+ else
229
+ raise ModBusException.new, "Unknown error"
230
+ end
231
+ end
232
+ pdu[2..-1]
233
+ end
234
+
235
+ protected
236
+ def send_pdu(pdu)
237
+ end
238
+
239
+ def read_pdu
240
+ end
241
+
242
+ def close
243
+ end
244
+
245
+ end
246
+
247
+ end
@@ -0,0 +1,47 @@
1
+ # RModBus - free implementation of ModBus protocol on Ruby.
2
+ #
3
+ # Copyright (C) 2008 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
+
16
+ module Errors
17
+
18
+ class ModBusException < Exception
19
+ end
20
+
21
+ class ModBusTimeout < ModBusException
22
+ end
23
+
24
+ class IllegalFunction < ModBusException
25
+ end
26
+
27
+ class IllegalDataAddress < ModBusException
28
+ end
29
+
30
+ class IllegalDataValue < ModBusException
31
+ end
32
+
33
+ class SlaveDeviceFailure < ModBusException
34
+ end
35
+
36
+ class Acknowledge < ModBusException
37
+ end
38
+
39
+ class SlaveDeviceBus < ModBusException
40
+ end
41
+
42
+ class MemoryParityError < ModBusException
43
+ end
44
+
45
+ end
46
+
47
+ end
@@ -0,0 +1,72 @@
1
+ # RModBus - free implementation of ModBus protocol on Ruby.
2
+ #
3
+ # Copyright (C) 2008 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 'socket'
15
+ require 'timeout'
16
+ require 'rmodbus/client'
17
+ require 'rmodbus/exceptions'
18
+
19
+ module ModBus
20
+
21
+ # Implementation clients(master) ModBusTCP
22
+ class TCPClient < Client
23
+
24
+ include Timeout
25
+
26
+ @@transaction = 0
27
+
28
+ # Connect with a ModBus server
29
+ def initialize(ipaddr, port = 502, slaveaddr = 1)
30
+ tried = 0
31
+ begin
32
+ timeout(1, ModBusTimeout) do
33
+ @sock = TCPSocket.new(ipaddr, port)
34
+ end
35
+ rescue ModBusTimeout => err
36
+ tried += 1
37
+ retry unless tried >= CONNECTION_RETRIES
38
+ raise ModBusTimeout.new, 'Timed out attempting to create connection'
39
+ end
40
+
41
+ @slave = slaveaddr
42
+ end
43
+
44
+ def close
45
+ @sock.close unless @sock.closed?
46
+ end
47
+
48
+ def self.transaction
49
+ @@transaction
50
+ end
51
+
52
+ private
53
+ def send_pdu(pdu)
54
+ @@transaction += 1
55
+ @sock.write @@transaction.to_bytes + "\0\0" + (pdu.size + 1).to_bytes + @slave.chr + pdu
56
+ end
57
+
58
+ def read_pdu
59
+ header = @sock.read(7)
60
+ if header
61
+ tin = header[0,2].to_int16
62
+ raise Errors::ModBusException.new("Transaction number mismatch") unless tin == @@transaction
63
+ len = header[4,2].to_int16
64
+ @sock.read(len-1)
65
+ else
66
+ raise Errors::ModBusException.new("Server did not respond")
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ end