rmodbus 1.0.0-java
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/NEWS.md +52 -0
- data/README.md +87 -0
- data/Rakefile +31 -0
- data/examples/perfomance_rtu.rb +56 -0
- data/examples/perfomance_tcp.rb +55 -0
- data/examples/use_rtu_via_tcp_modbus.rb +22 -0
- data/examples/use_tcp_modbus.rb +23 -0
- data/lib/rmodbus/client.rb +91 -0
- data/lib/rmodbus/common.rb +50 -0
- data/lib/rmodbus/errors.rb +50 -0
- data/lib/rmodbus/ext.rb +88 -0
- data/lib/rmodbus/proxy.rb +54 -0
- data/lib/rmodbus/rtu.rb +140 -0
- data/lib/rmodbus/rtu_client.rb +41 -0
- data/lib/rmodbus/rtu_server.rb +61 -0
- data/lib/rmodbus/rtu_slave.rb +59 -0
- data/lib/rmodbus/rtu_via_tcp_client.rb +41 -0
- data/lib/rmodbus/rtu_via_tcp_server.rb +50 -0
- data/lib/rmodbus/rtu_via_tcp_slave.rb +58 -0
- data/lib/rmodbus/server.rb +142 -0
- data/lib/rmodbus/slave.rb +268 -0
- data/lib/rmodbus/sp.rb +45 -0
- data/lib/rmodbus/tcp.rb +49 -0
- data/lib/rmodbus/tcp_client.rb +39 -0
- data/lib/rmodbus/tcp_server.rb +61 -0
- data/lib/rmodbus/tcp_slave.rb +64 -0
- data/lib/rmodbus/version.rb +17 -0
- data/lib/rmodbus.rb +35 -0
- data/spec/client_spec.rb +31 -0
- data/spec/exception_spec.rb +116 -0
- data/spec/ext_spec.rb +46 -0
- data/spec/logging_spec.rb +65 -0
- data/spec/proxy_spec.rb +73 -0
- data/spec/read_rtu_response_spec.rb +91 -0
- data/spec/rtu_client_spec.rb +74 -0
- data/spec/rtu_server_spec.rb +29 -0
- data/spec/rtu_via_tcp_client_spec.rb +78 -0
- data/spec/slave_spec.rb +55 -0
- data/spec/tcp_client_spec.rb +81 -0
- data/spec/tcp_server_spec.rb +99 -0
- metadata +165 -0
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
|
data/lib/rmodbus/ext.rb
ADDED
@@ -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
|