rmodbus 1.0.0-java

Sign up to get free protection for your applications and to get access to all the features.
data/NEWS.md ADDED
@@ -0,0 +1,52 @@
1
+ Release 1.0.0
2
+ =====================================
3
+ New API for client part of library
4
+ ---------------------------------------
5
+
6
+ Example:
7
+
8
+ require 'rmodbus'
9
+
10
+ ModBus::TCPClient.new('127.0.0.1', 8502) do |cl|
11
+ cl.with_slave(1) do |slave|
12
+ # Read a single holding register at address 16
13
+ slave.holding_registers[16]
14
+
15
+ # Write a single holding register at address 16
16
+ slave.holding_registers[16] = 123
17
+
18
+ # Read holding registers 16 through 20
19
+ slave.holding_registers[16..20]
20
+
21
+ # Write holding registers 16 through 20 with some values
22
+ slave.holding_registers[16..20] = [1, 2, 3, 4, 5]
23
+ end
24
+ end
25
+
26
+ for more information [see](http://rdoc.info/gems/rmodbus/1.0.0/frames)
27
+
28
+ Conversion to/from 32bit registers
29
+ -----------------------------------
30
+
31
+ Some modbus devices use two registers to store 32bit values.
32
+ RModbus provides some helper functions to go back and forth between these two things when reading/writing.
33
+ The built-in examples assume registers in a particular order but it's trivial to change.
34
+
35
+ # Reading values in multiple registers (you can read more than 2 and convert them all so long as they are in multiples of 2)
36
+ res = slave.holding_registers[0..1]
37
+ res.inspect => [20342, 17344]
38
+ res.to_32i => [1136676726]
39
+ res.to_32f => [384.620788574219]
40
+
41
+ # Writing 32b values to multiple registers
42
+ cl.holding_registers[0..1] = [1136676726].from_32i
43
+ cl.holding_registers[0..1] => [20342, 17344]
44
+ cl.holding_registers[2..3] = [384.620788574219].from_32f
45
+ cl.holding_registers[2..3] => [20342, 17344]
46
+
47
+ Support JRuby
48
+ --------------------------------------
49
+ Now you could use RModBus on JRuby without RTU implementation.
50
+
51
+ RTU classes requires gem [serialport](https://github.com/hparra/ruby-serialport) which
52
+ currently not compatible with JRuby
data/README.md ADDED
@@ -0,0 +1,87 @@
1
+ RModBus
2
+ ==========================
3
+
4
+ **RModBus** - free implementation of protocol ModBus.
5
+
6
+ Features
7
+ ---------------------------
8
+ - Ruby 1.8, Ruby 1.9, JRuby (without serial ModBus RTU)
9
+ - TCP, RTU, RTU over TCP protocols
10
+ - Client(master) and server(slave)
11
+ - 16, 32 -bit and float registers
12
+
13
+ Support functions
14
+ ---------------------------
15
+ * Read Coils (0x01)
16
+ * Read Discrete Inputs (0x02)
17
+ * Read Holding Registers (0x03)
18
+ * Read Input Registers (0x04)
19
+ * Write Single Coil (0x05)
20
+ * Write Single Register (0x06)
21
+ * Write Multiple Coils (0x0F)
22
+ * Write Multiple registers (0x10)
23
+ * Mask Write register (0x16)
24
+
25
+ Installation
26
+ ------------------------------------
27
+
28
+ Download and install RModBus with the following
29
+
30
+ **$ gem install rmodbus**
31
+
32
+ Example
33
+ ------------------------------------
34
+
35
+ require 'rmodbus'
36
+
37
+ ModBus::TCPClient.new('127.0.0.1', 8502) do |cl|
38
+ cl.with_slave(1) do |slave|
39
+ # Read a single holding register at address 16
40
+ slave.holding_registers[16]
41
+
42
+ # Write a single holding register at address 16
43
+ slave.holding_registers[16] = 123
44
+
45
+ # Read holding registers 16 through 20
46
+ slave.holding_registers[16..20]
47
+
48
+ # Write holding registers 16 through 20 with some values
49
+ slave.holding_registers[16..20] = [1, 2, 3, 4, 5]
50
+ end
51
+ end
52
+
53
+
54
+ Conversion to/from 32bit registers
55
+ -----------------------------------
56
+
57
+ Some modbus devices use two registers to store 32bit values.
58
+ RModbus provides some helper functions to go back and forth between these two things when reading/writing.
59
+ The built-in examples assume registers in a particular order but it's trivial to change.
60
+
61
+ # Reading values in multiple registers (you can read more than 2 and convert them all so long as they are in multiples of 2)
62
+ res = slave.holding_registers[0..1]
63
+ res.inspect => [20342, 17344]
64
+ res.to_32i => [1136676726]
65
+ res.to_32f => [384.620788574219]
66
+
67
+ # Writing 32b values to multiple registers
68
+ cl.holding_registers[0..1] = [1136676726].from_32i
69
+ cl.holding_registers[0..1] => [20342, 17344]
70
+ cl.holding_registers[2..3] = [384.620788574219].from_32f
71
+ cl.holding_registers[2..3] => [20342, 17344]
72
+
73
+ GitHub
74
+ ----------------------------------
75
+
76
+ You can checkout source code from GitHub repositry
77
+
78
+ **$ git clone git://github.com/flipback/RModBus.git**
79
+
80
+ Reference
81
+ ----------------------------------
82
+
83
+ Home page: http://rmodbus.heroku.com
84
+
85
+ RModBud on GitHub: http://github.com/flipback/RModBus
86
+
87
+ ModBus community: http://www.modbus-ida.org
data/Rakefile ADDED
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+
13
+ require 'rake'
14
+ require 'rspec/core'
15
+ require 'rspec/core/rake_task'
16
+ RSpec::Core::RakeTask.new(:spec) do |spec|
17
+ spec.pattern = FileList['spec/**/*_spec.rb']
18
+ if RUBY_PLATFORM == "java"
19
+ spec.pattern.exclude("spec/rtu_client_spec.rb", "spec/rtu_server_spec.rb")
20
+ end
21
+ end
22
+
23
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
24
+ spec.pattern = FileList['spec/**/*_spec.rb']
25
+ spec.rcov = true
26
+ end
27
+
28
+ task :default => :spec
29
+
30
+ require 'yard'
31
+ YARD::Rake::YardocTask.new
@@ -0,0 +1,56 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'../lib')
2
+
3
+ require 'rmodbus'
4
+ require 'benchmark'
5
+
6
+ include ModBus
7
+
8
+ BAUD = 9600
9
+ TIMES = 100
10
+
11
+ srv = RTUServer.new 'com3', BAUD
12
+ srv.coils = [0,1] * 50
13
+ srv.discrete_inputs = [1,0] * 50
14
+ srv.holding_registers = [0,1,2,3,4,5,6,7,8,9] * 10
15
+ srv.input_registers = [0,1,2,3,4,5,6,7,8,9] * 10
16
+ srv.start
17
+
18
+
19
+ cl = RTUClient.new('com4', BAUD)
20
+ cl.with_slave(1) do |slave|
21
+ Benchmark.bmbm do |x|
22
+ x.report('Read coils') do
23
+ TIMES.times { slave.read_coils 0, 100 }
24
+ end
25
+
26
+ x.report('Read discrete inputs') do
27
+ TIMES.times { slave.read_discrete_inputs 0, 100 }
28
+ end
29
+
30
+ x.report('Read holding registers') do
31
+ TIMES.times { slave.read_holding_registers 0, 100 }
32
+ end
33
+
34
+ x.report('Read input registers') do
35
+ TIMES.times { slave.read_input_registers 0, 100 }
36
+ end
37
+
38
+ x.report('Write single coil') do
39
+ TIMES.times { slave.write_single_coil 0, 1 }
40
+ end
41
+
42
+ x.report('Write single register') do
43
+ TIMES.times { slave.write_single_register 100, 0xAAAA }
44
+ end
45
+
46
+ x.report('Write multiple coils') do
47
+ TIMES.times { slave.write_multiple_coils 0, [1,0] * 50 }
48
+ end
49
+
50
+ x.report('Write multiple registers') do
51
+ TIMES.times { slave.write_multiple_registers 0, [0,1,2,3,4,5,6,7,8,9] * 10 }
52
+ end
53
+ end
54
+ end
55
+ srv.stop
56
+ cl.close
@@ -0,0 +1,55 @@
1
+ $:.unshift File.join(File.dirname(__FILE__),'../lib')
2
+
3
+ require 'rmodbus'
4
+ require 'benchmark'
5
+
6
+ include ModBus
7
+
8
+ TIMES = 1000
9
+
10
+ srv = ModBus::TCPServer.new 1502
11
+ srv.coils = [0,1] * 50
12
+ srv.discrete_inputs = [1,0] * 50
13
+ srv.holding_registers = [0,1,2,3,4,5,6,7,8,9] * 10
14
+ srv.input_registers = [0,1,2,3,4,5,6,7,8,9] * 10
15
+ srv.start
16
+
17
+
18
+ cl = TCPClient.new('127.0.0.1', 1502)
19
+ cl.with_slave(1) do |slave|
20
+ Benchmark.bmbm do |x|
21
+ x.report('Read coils') do
22
+ TIMES.times { slave.read_coils 0, 100 }
23
+ end
24
+
25
+ x.report('Read discrete inputs') do
26
+ TIMES.times { slave.read_discrete_inputs 0, 100 }
27
+ end
28
+
29
+ x.report('Read holding registers') do
30
+ TIMES.times { slave.read_holding_registers 0, 100 }
31
+ end
32
+
33
+ x.report('Read input registers') do
34
+ TIMES.times { slave.read_input_registers 0, 100 }
35
+ end
36
+
37
+ x.report('Write single coil') do
38
+ TIMES.times { slave.write_single_coil 0, 1 }
39
+ end
40
+
41
+ x.report('Write single register') do
42
+ TIMES.times { slave.write_single_register 100, 0xAAAA }
43
+ end
44
+
45
+ x.report('Write multiple coils') do
46
+ TIMES.times { slave.write_multiple_coils 0, [1,0] * 50 }
47
+ end
48
+
49
+ x.report('Write multiple registers') do
50
+ TIMES.times { slave.write_multiple_registers 0, [0,1,2,3,4,5,6,7,8,9] * 10 }
51
+ end
52
+ end
53
+ end
54
+ cl.close
55
+ srv.stop
@@ -0,0 +1,22 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib/')
2
+ require 'rmodbus'
3
+
4
+ srv = ModBus::RTUViaTCPServer.new(10002,1)
5
+ srv.coils = [1,0,1,1]
6
+ srv.discrete_inputs = [1,1,0,0]
7
+ srv.holding_registers = [1,2,3,4]
8
+ srv.input_registers = [1,2,3,4]
9
+ srv.debug = true
10
+ srv.start
11
+
12
+ ModBus::RTUViaTCPClient.connect('127.0.0.1', 10002) do |cl|
13
+ cl.with_slave(1) do |slave|
14
+ slave.debug = true
15
+ regs = slave.holding_registers
16
+ puts regs[0..3]
17
+ regs[0..3] = [2,0,1,1]
18
+ puts regs[0..3]
19
+ end
20
+ end
21
+
22
+ srv.shutdown
@@ -0,0 +1,23 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/../lib/')
2
+ require 'rmodbus'
3
+
4
+ srv = ModBus::TCPServer.new(8502,1)
5
+ srv.coils = [1,0,1,1]
6
+ srv.discrete_inputs = [1,1,0,0]
7
+ srv.holding_registers = [1,2,3,4]
8
+ srv.input_registers = [1,2,3,4]
9
+ srv.debug = true
10
+ srv.audit = true
11
+ srv.start
12
+
13
+ ModBus::TCPClient.connect('127.0.0.1', 8502) do |cl|
14
+ cl.with_slave(1) do |slave|
15
+ slave.debug = true
16
+ regs = slave.holding_registers
17
+ puts regs[0..3]
18
+ regs[0..3] = [2,0,1,1]
19
+ puts regs[0..3]
20
+ end
21
+ end
22
+
23
+ srv.stop
@@ -0,0 +1,91 @@
1
+ # RModBus - free implementation of ModBus protocol on Ruby.
2
+ #
3
+ # Copyright (C) 2008-2011 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
+ module ModBus
16
+ # @abstract
17
+ class Client
18
+ include Errors
19
+
20
+ # Initialized client (alias :connect)
21
+ # @example
22
+ # Client.new(any_args) do |client|
23
+ # client.closed? #=> false
24
+ # end
25
+ # @param *args depends on implementation
26
+ # @yield return client object and close it before exit
27
+ # @return [Client] client object
28
+ def initialize(*args, &block)
29
+ @io = open_connection(*args)
30
+ if block_given?
31
+ yield self
32
+ close
33
+ else
34
+ self
35
+ end
36
+ end
37
+
38
+ class << self
39
+ alias_method :connect, :new
40
+ end
41
+
42
+ # Given slave object
43
+ # @example
44
+ # cl = Client.new
45
+ # cl.with_slave(1) do |slave|
46
+ # slave.holding_registers[0..100]
47
+ # end
48
+ #
49
+ # @param [Integer, #read] uid slave devise
50
+ # @return [Slave] slave object
51
+ def with_slave(uid, &block)
52
+ slave = get_slave(uid, @io)
53
+ if block_given?
54
+ yield slave
55
+ else
56
+ slave
57
+ end
58
+ end
59
+
60
+ # Check connections
61
+ # @return [Boolean]
62
+ def closed?
63
+ @io.closed?
64
+ end
65
+
66
+ # Close connections
67
+ def close
68
+ @io.close unless @io.closed?
69
+ end
70
+
71
+ protected
72
+ def open_connection(*args)
73
+ #Stub conn object
74
+ @io = Object.new
75
+
76
+ @io.instance_eval """
77
+ def close
78
+ end
79
+
80
+ def closed?
81
+ true
82
+ end
83
+ """
84
+ @io
85
+ end
86
+
87
+ def get_slave(uid,io)
88
+ Slave.new(uid, io)
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,50 @@
1
+ # RModBus - free implementation of ModBus protocol in Ruby.
2
+ #
3
+ # Copyright (C) 2010 Timin Aleksey
4
+ # Copyright (C) 2010 Kelley Reynolds
5
+ #
6
+ # This program is free software: you can redistribute it and/or modify
7
+ # it under the terms of the GNU General Public License as published by
8
+ # the Free Software Foundation, either version 3 of the License, or
9
+ # (at your option) any later version.
10
+ #
11
+ # This program is distributed in the hope that it will be useful,
12
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ # GNU General Public License for more details.
15
+
16
+ module ModBus
17
+ module Common
18
+ # @return [Boolean] debug mode
19
+ # default false
20
+ attr_accessor :debug
21
+
22
+ @debug = false
23
+
24
+ private
25
+ # Put log message on standart output
26
+ # @param [String] msg message for log
27
+ def log(msg)
28
+ $stdout.puts msg if @debug
29
+ end
30
+
31
+ # Convert string of byte to string for log
32
+ # @example
33
+ # logging_bytes("\x1\xa\x8") => "[01][0a][08]"
34
+ # @param [String] msg input string
35
+ # @return [String] readable string of bytes
36
+ def logging_bytes(msg)
37
+ result = ""
38
+ msg.each_byte do |c|
39
+ byte = if c < 16
40
+ '0' + c.to_s(16)
41
+ else
42
+ c.to_s(16)
43
+ end
44
+ result << "[#{byte}]"
45
+ end
46
+ result
47
+ end
48
+ end
49
+ end
50
+
@@ -0,0 +1,50 @@
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 ProxyException < StandardError
19
+ end
20
+
21
+ class ModBusException < StandardError
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
+ class ModBusTimeout < ModBusException
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,88 @@
1
+ # RModBus - free implementation of ModBus protocol on Ruby.
2
+ #
3
+ # Copyright (C) 2009 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
+ class String
16
+
17
+ unless RUBY_VERSION =~ /^1\.9/
18
+ def getbyte(index)
19
+ self[index].to_i
20
+ end
21
+ end
22
+
23
+ def unpack_bits
24
+ array_bit = []
25
+ self.unpack('b*')[0].each_char do |c|
26
+ array_bit << c.to_i
27
+ end
28
+ array_bit
29
+ end
30
+
31
+ end
32
+
33
+ class Integer
34
+
35
+ # Shortcut or turning an integer into a word
36
+ def to_word
37
+ [self].pack('n')
38
+ end
39
+
40
+ end
41
+
42
+ class Array
43
+
44
+ # Given an array of 16bit Fixnum, we turn it into 32bit Int in big-endian order, halving the size
45
+ def to_32f
46
+ raise "Array requires an even number of elements to pack to 32bits: was #{self.size}" unless self.size.even?
47
+ self.each_slice(2).map { |(lsb, msb)| [msb, lsb].pack('n*').unpack('g')[0] }
48
+ end
49
+
50
+ # Given an array of 32bit Floats, we turn it into an array of 16bit Fixnums, doubling the size
51
+ def from_32f
52
+ self.pack('g*').unpack('n*').each_slice(2).map { |arr| arr.reverse }.flatten
53
+ end
54
+
55
+ # Given an array of 16bit Fixnum, we turn it into 32bit Float in big-endian order, halving the size
56
+ def to_32i
57
+ raise "Array requires an even number of elements to pack to 32bits: was #{self.size}" unless self.size.even?
58
+ self.each_slice(2).map { |(lsb, msb)| [msb, lsb].pack('n*').unpack('N')[0] }
59
+ end
60
+
61
+ # Given an array of 32bit Fixnum, we turn it into an array of 16bit fixnums, doubling the size
62
+ def from_32i
63
+ self.pack('N*').unpack('n*').each_slice(2).map { |arr| arr.reverse }.flatten
64
+ end
65
+
66
+ def pack_to_word
67
+ word = 0
68
+ s = ""
69
+ mask = 0x01
70
+
71
+ self.each do |bit|
72
+ word |= mask if bit > 0
73
+ mask <<= 1
74
+ if mask == 0x100
75
+ mask = 0x01
76
+ s << word.chr
77
+ word = 0
78
+ end
79
+ end
80
+ unless mask == 0x01
81
+ s << word.chr
82
+ else
83
+ s
84
+ end
85
+ end
86
+
87
+ end
88
+
@@ -0,0 +1,54 @@
1
+ # ReadOnly and ReadWrite hash interface for modbus registers and coils
2
+ #
3
+ # Copyright (C) 2010 Kelley Reynolds
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
+ # Given a slave and a type of operation, execute a single or multiple read using hash syntax
16
+ class ReadOnlyProxy
17
+ # Initialize a proxy for a slave and a type of operation
18
+ def initialize(slave, type)
19
+ @slave, @type = slave, type
20
+ end
21
+
22
+ # Read single or multiple values from a modbus slave depending on whether a Fixnum or a Range was given.
23
+ # Note that in the case of multiples, a pluralized version of the method is sent to the slave
24
+ def [](key)
25
+ if key.instance_of?(Fixnum)
26
+ @slave.send("read_#{@type}", key, 1)
27
+ elsif key.instance_of?(Range)
28
+ @slave.send("read_#{@type}s", key.first, key.count)
29
+ else
30
+ raise ProxyException, "Invalid argument, must be integer or range. Was #{key.class}"
31
+ end
32
+ end
33
+ end
34
+
35
+ class ReadWriteProxy < ReadOnlyProxy
36
+ # Write single or multiple values to a modbus slave depending on whether a Fixnum or a Range was given.
37
+ # Note that in the case of multiples, a pluralized version of the method is sent to the slave. Also when
38
+ # writing multiple values, the number of elements must match the number of registers in the range or an exception is raised
39
+ def []=(key, val)
40
+ if key.instance_of?(Fixnum)
41
+ @slave.send("write_#{@type}", key, val)
42
+ elsif key.instance_of?(Range)
43
+ if key.count != val.size
44
+ raise ProxyException, "The size of the range must match the size of the values (#{key.count} != #{val.size})"
45
+ end
46
+
47
+ @slave.send("write_#{@type}s", key.first, val)
48
+ else
49
+ raise ProxyException, "Invalid argument, must be integer or range. Was #{key.class}"
50
+ end
51
+ end
52
+ end
53
+
54
+ end