rmodbus 0.1.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rmodbus.rb CHANGED
@@ -10,3 +10,4 @@
10
10
  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
11
  # GNU General Public License for more details.
12
12
  require 'rmodbus/tcp_client'
13
+ require 'rmodbus/tcp_server'
@@ -54,11 +54,43 @@ class Integer
54
54
  end
55
55
  end
56
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
+
57
86
  module ModBus
58
87
 
59
88
  class Client
60
89
 
61
90
  include Errors
91
+ # Number of times to retry on connection and read timeouts
92
+ CONNECTION_RETRIES = 10
93
+ READ_RETRIES = 10
62
94
 
63
95
  # Read value *ncoils* coils starting with *addr*
64
96
  #
@@ -70,10 +102,16 @@ module ModBus
70
102
  # Read value *ncoils* discrete inputs starting with *addr*
71
103
  #
72
104
  # Return array of their values
73
- def read_discret_inputs(addr, ncoils)
105
+ def read_discrete_inputs(addr, ncoils)
74
106
  query("\x2" + addr.to_bytes + ncoils.to_bytes).to_array_bit[0..ncoils-1]
75
107
  end
76
108
 
109
+ # Deprecated version of read_discrete_inputs
110
+ def read_discret_inputs(addr, ncoils)
111
+ warn "[DEPRECATION] `read_discret_inputs` is deprecated. Please use `read_discrete_inputs` instead."
112
+ read_discrete_inputs(addr, ncoils)
113
+ end
114
+
77
115
  # Read value *nreg* holding registers starting with *addr*
78
116
  #
79
117
  # Return array of their values
@@ -159,8 +197,15 @@ module ModBus
159
197
  def query(pdu)
160
198
  send_pdu(pdu)
161
199
 
162
- timeout(1) do
163
- pdu = read_pdu
200
+ tried = 0
201
+ begin
202
+ timeout(1, ModBusTimeout) do
203
+ pdu = read_pdu
204
+ end
205
+ rescue ModBusTimeout => err
206
+ tried += 1
207
+ retry unless tried >= READ_RETRIES
208
+ raise ModBusTimeout.new, 'Timed out during read attempt'
164
209
  end
165
210
 
166
211
  if pdu[0].to_i >= 0x80
@@ -176,23 +221,26 @@ module ModBus
176
221
  when 5
177
222
  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"
178
223
  when 6
179
- raise SlaveDiviceBus.new, "The server is engaged in processing a long duration program command"
224
+ raise SlaveDeviceBus.new, "The server is engaged in processing a long duration program command"
180
225
  when 8
181
226
  raise MemoryParityError.new, "The extended file area failed to pass a consistency check"
182
227
  else
183
- raise ModBusException.new, "Unknow error"
228
+ raise ModBusException.new, "Unknown error"
184
229
  end
185
230
  end
186
231
  pdu[2..-1]
187
232
  end
188
233
 
189
- protected
234
+ protected
190
235
  def send_pdu(pdu)
191
236
  end
192
237
 
193
238
  def read_pdu
194
239
  end
195
240
 
241
+ def close
242
+ end
243
+
196
244
  end
197
245
 
198
246
  end
@@ -33,12 +33,15 @@ module ModBus
33
33
  class Acknowledge < ModBusException
34
34
  end
35
35
 
36
- class SlaveDiviceBus < ModBusException
36
+ class SlaveDeviceBus < ModBusException
37
37
  end
38
38
 
39
39
  class MemoryParityError < ModBusException
40
40
  end
41
41
 
42
+ class ModBusTimeout < ModBusException
43
+ end
44
+
42
45
  end
43
46
 
44
47
  end
@@ -0,0 +1,6 @@
1
+ module ModBus
2
+
3
+ class RTUClient
4
+ end
5
+
6
+ end
@@ -15,7 +15,6 @@ require 'socket'
15
15
  require 'timeout'
16
16
  require 'rmodbus/client'
17
17
  require 'rmodbus/exceptions'
18
- require 'rmodbus/adu'
19
18
 
20
19
  module ModBus
21
20
 
@@ -24,26 +23,49 @@ module ModBus
24
23
 
25
24
  include Timeout
26
25
 
26
+ @@transaction = 0
27
+
27
28
  # Connect with a ModBus server
28
29
  def initialize(ipaddr, port = 502, slaveaddr = 1)
29
- timeout(1) do
30
+
31
+ tried = 0
32
+ begin
33
+ timeout(1, ModBusTimeout) do
30
34
  @sock = TCPSocket.new(ipaddr, port)
31
35
  end
36
+ rescue ModBusTimeout => err
37
+ tried += 1
38
+ retry unless tried >= CONNECTION_RETRIES
39
+ raise ModBusTimeout.new, 'Timed out attempting to create connection'
40
+ end
32
41
  @slave = slaveaddr
33
42
  end
43
+
44
+ def close
45
+ @sock.close unless @sock.closed?
46
+ end
34
47
 
48
+
49
+ def self.transaction
50
+ @@transaction
51
+ end
52
+
35
53
  private
36
54
  def send_pdu(pdu)
37
- @adu = ADU.new(pdu,@slave)
38
- @sock.write @adu.serialize
55
+ @@transaction += 1
56
+ @sock.write @@transaction.to_bytes + "\0\0" + (pdu.size + 1).to_bytes + @slave.chr + pdu
39
57
  end
40
58
 
41
59
  def read_pdu
42
60
  header = @sock.read(7)
43
- tin = header[0,2].to_int16
44
- raise Errors::ModBusException.new("Transaction number mismatch") unless tin == @adu.transaction_id
45
- len = header[4,2].to_int16
46
- @sock.read(len-1)
61
+ if header
62
+ tin = header[0,2].to_int16
63
+ raise Errors::ModBusException.new("Transaction number mismatch") unless tin == @@transaction
64
+ len = header[4,2].to_int16
65
+ @sock.read(len-1)
66
+ else
67
+ raise Errors::ModBusException.new("Server did not respond")
68
+ end
47
69
  end
48
70
 
49
71
  end
@@ -0,0 +1,158 @@
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 'rmodbus/exceptions'
15
+ require 'gserver'
16
+
17
+
18
+ module ModBus
19
+
20
+ class TCPServer < GServer
21
+
22
+ attr_accessor :coils, :discret_inputs, :holding_registers, :input_registers
23
+
24
+ @@funcs = [1,2,3,4,5,6,15,16]
25
+
26
+ def initialize(port = 502, uid = 1)
27
+ @coils = []
28
+ @discret_inputs = []
29
+ @holding_registers =[]
30
+ @input_registers = []
31
+ @uid = uid
32
+ super(port)
33
+ end
34
+
35
+ def serve(io)
36
+ req = io.read(7)
37
+ if req[2,2] != "\x00\x00" or req[6].to_i != @uid
38
+ io.close
39
+ return
40
+ end
41
+
42
+ tr = req[0,2]
43
+ len = req[4,2].to_int16
44
+ req = io.read(len - 1)
45
+ func = req[0].to_i
46
+
47
+ unless @@funcs.include?(func)
48
+ param = { :err => 1 }
49
+ end
50
+
51
+ case func
52
+ when 1
53
+ param = parse_read_func(req, @coils)
54
+ if param[:err] == 0
55
+ val = @coils[param[:addr],param[:quant]].bits_to_bytes
56
+ res = func.chr + val.size.chr + val
57
+ end
58
+ when 2
59
+ param = parse_read_func(req, @discret_inputs)
60
+ if param[:err] == 0
61
+ val = @discret_inputs[param[:addr],param[:quant]].bits_to_bytes
62
+ res = func.chr + val.size.chr + val
63
+ end
64
+ when 3
65
+ param = parse_read_func(req, @holding_registers)
66
+ if param[:err] == 0
67
+ res = func.chr + (param[:quant] * 2).chr + @holding_registers[param[:addr],param[:quant]].to_ints16
68
+ end
69
+ when 4
70
+ param = parse_read_func(req, @input_registers)
71
+ if param[:err] == 0
72
+ res = func.chr + (param[:quant] * 2).chr + @input_registers[param[:addr],param[:quant]].to_ints16
73
+ end
74
+ when 5
75
+ param = parse_write_coil_func(req)
76
+ if param[:err] == 0
77
+ @coils[param[:addr]] = param[:val]
78
+ res = func.chr + req
79
+ end
80
+ when 6
81
+ param = parse_write_register_func(req)
82
+ if param[:err] == 0
83
+ @holding_registers[param[:addr]] = param[:val]
84
+ res = func.chr + req
85
+ end
86
+ when 15
87
+ param = parse_write_multiple_coils_func(req)
88
+ if param[:err] == 0
89
+ @coils[param[:addr],param[:quant]] = param[:val][0,param[:quant]]
90
+ res = func.chr + req
91
+ end
92
+ when 16
93
+ param = parse_write_multiple_registers_func(req)
94
+ if param[:err] == 0
95
+ @holding_registers[param[:addr],param[:quant]] = param[:val][0,param[:quant]]
96
+ res = func.chr + req
97
+ end
98
+ end
99
+ if param[:err] == 0
100
+ io.write tr + "\0\0" + (res.size + 1).to_bytes + @uid.chr + res
101
+ else
102
+ io.write tr + "\0\0\0\3" + @uid.chr + (func | 0x80).chr + param[:err].chr
103
+ end
104
+ end
105
+
106
+ private
107
+
108
+ def parse_read_func(req, field)
109
+ quant = req[3,2].to_int16
110
+
111
+ return { :err => 3} unless quant <= 0x7d
112
+
113
+ addr = req[1,2].to_int16
114
+ return { :err => 2 } unless addr + quant <= field.size
115
+
116
+ return { :err => 0, :quant => quant, :addr => addr }
117
+ end
118
+
119
+ def parse_write_coil_func(req)
120
+ addr = req[1,2].to_int16
121
+ return { :err => 2 } unless addr <= @coils.size
122
+
123
+ val = req[3,2].to_int16
124
+ return { :err => 3 } unless val == 0 or val == 0xff00
125
+
126
+ val = 1 if val == 0xff00
127
+ return { :err => 0, :addr => addr, :val => val }
128
+ end
129
+
130
+ def parse_write_register_func(req)
131
+ addr = req[1,2].to_int16
132
+ return { :err => 2 } unless addr <= @coils.size
133
+
134
+ val = req[3,2].to_int16
135
+
136
+ return { :err => 0, :addr => addr, :val => val }
137
+ end
138
+
139
+ def parse_write_multiple_coils_func(req)
140
+ param = parse_read_func(req, @coils)
141
+
142
+ if param[:err] == 0
143
+ param = {:err => 0, :addr => param[:addr], :quant => param[:quant], :val => req[6,param[:quant]].to_array_bit }
144
+ end
145
+ param
146
+ end
147
+
148
+ def parse_write_multiple_registers_func(req)
149
+ param = parse_read_func(req, @holding_registers)
150
+
151
+ if param[:err] == 0
152
+ param = {:err => 0, :addr => param[:addr], :quant => param[:quant], :val => req[6,param[:quant] * 2].to_array_int16 }
153
+ end
154
+ param
155
+ end
156
+
157
+ end
158
+ end
metadata CHANGED
@@ -1,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rmodbus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: "0.2"
5
5
  platform: ruby
6
6
  authors:
7
- - A.Timin, D.Samatov
7
+ - A.Timin, J. Sanders
8
8
  autorequire: rmodbus
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-01-25 00:00:00 +05:00
12
+ date: 2009-01-30 00:00:00 +05:00
13
13
  default_executable:
14
14
  dependencies: []
15
15
 
@@ -25,11 +25,14 @@ extra_rdoc_files:
25
25
  - LICENSE
26
26
  - CHANGES
27
27
  files:
28
+ - lib/rmodbus/rtu_client.rb
29
+ - lib/rmodbus/tcp_server.rb
30
+ - lib/rmodbus/client.rb
28
31
  - lib/rmodbus/exceptions.rb
29
32
  - lib/rmodbus/tcp_client.rb
30
- - lib/rmodbus/adu.rb
31
- - lib/rmodbus/client.rb
32
33
  - lib/rmodbus.rb
34
+ - ext/extconf.rb
35
+ - ext/serialport.c
33
36
  - README
34
37
  - AUTHORS
35
38
  - LICENSE
@@ -60,7 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
60
63
  requirements: []
61
64
 
62
65
  rubyforge_project:
63
- rubygems_version: 1.0.1
66
+ rubygems_version: 1.3.1
64
67
  signing_key:
65
68
  specification_version: 2
66
69
  summary: RModBus - free implementation of protocol ModBus
data/lib/rmodbus/adu.rb DELETED
@@ -1,36 +0,0 @@
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
- class ADU
17
-
18
- @@transaction_id = 0
19
-
20
- attr_reader :unit_id, :transaction_id, :pdu, :size
21
-
22
- def initialize(pdu, uid)
23
- @pdu = pdu
24
- @size = pdu.size + 1
25
- @unit_id = uid
26
- @transaction_id = @@transaction_id
27
- @@transaction_id += 1
28
- end
29
-
30
- def serialize
31
- @transaction_id.to_bytes + "\x00\x00" + @size.to_bytes + @unit_id.chr + pdu
32
- end
33
-
34
- end
35
-
36
- end