Device-Modbus

 view release on metacpan or  search on metacpan

lib/Device/Modbus.pm  view on Meta::CPAN

    'Write Multiple Coils'          => 0x0F,
    'Write Multiple Registers'      => 0x10,
    'Read/Write Multiple Registers' => 0x17,
);

our %function_for = reverse %code_for;

#### Helper methods

# Receives an array reference of bit values and builds an array
# of 8-bit numbers. Each number starts with the lower address
# in the LSB.
# Returns the quantity of bits packed and a reference to the array
# of 8-bit numbers
sub flatten_bit_values {
    my ($self, $values) = @_;

    # Values must be either 1 or 0
    my @values = map { $_ ? 1 : 0 } @{$values};

    # Turn the values array into an array of binary numbers
    my @values_binary;
    while (@values) {
        push @values_binary, pack 'b*', join '', splice @values, 0, 8;
    }
    return \@values_binary;
}

# Receives a quantity of bits and an array of 8-bit numbers.
# The numbers are exploded into an array of bit values.
# The numbers start with the lower address in the LSB,
# and the first number contains the lower address.
# Returns an array of ones and zeros.
sub explode_bit_values {
    my ($self, @values) = @_;
    @values = map {split //, unpack 'b8', pack 'v', $_} @values;
    return @values;
}

1;

lib/Device/Modbus.pm  view on Meta::CPAN

 }

 my $server = Device::Modbus::TCP::Server->new(
     log_level => 4,
     log_file  => 'logfile'
 );

 my $unit = My::Unit->new(id => 3);
 $server->add_server_unit($unit);

 $server->start;

=head1 DESCRIPTION

Modbus is an industrial communication protocol. It is implemented by many industrial devices such as servo motors, temperature controllers, force monitors, and Programmable Logic Controllers. Device::Modbus is a set of Perl modules that should allow ...

=head2 The Modbus data model

With Modbus, a I<client> sends I<requests> to a I<server> device, which returns I<responses>. We'll use the term I<unit> when referring a device capable of processing Modbus requests. Most of the time, a unit is one physical device, but a physical de...

Data within a unit is accessible through the following addressable tables:

lib/Device/Modbus.pm  view on Meta::CPAN

To summarize, the Modbus data model breaks a I<unit> into I<zones> in which data is addressable.

Now, Modbus uses Protocol Data Units. The PDUs are the basic blocks defined by the protocol; they are the binary messages that flow between clients and servers. PDUs encapsulate a request from a client or a response from a server. They are independen...

PDUs are further encapsulated into Application Data Units, which add a header and (in the RTU case) a footer with further information as needed for the communication layer. Device::Modbus handles the RTU and the TCP variants of the protocol through L...

Finally, clients produce requests which are sent to servers, and they receive requests in return. This distribution provides the tools to build both servers and clients.

=head2 Request generalities

Let's talk now about requests. Requests have basically four parameters. The first is implicit in the request code itself, sice each kind of request is directed at a particular zone. The other parameters are the starting address, the number of registe...

For example, we have Read Coil, Write Single Coil and Write Multiple Coils, which refer to the Discrete Outputs zone. As for addresses, a read request for multiple registers that starts at address 1 and demands 5 registers will fetch regiesters 1, 2,...

=head1 GUIDE TO THE DOCUMENTATION

The main protocol is described in the following documents:

=over

=item L<Device::Modbus::Client>

=item L<Device::Modbus::Server>

lib/Device/Modbus/Request.pm  view on Meta::CPAN

=back

=head4 * Read/Write Multiple Registers

This is function number 0x17. It requires the following arguments:

=over

=item read_address

The address where you want to start reading registers. As usual, it must be a number between 0 and 65535.

=item read_quantity

A number up to 125. This is the number of registers that will be read.

=item write_address

The address where you want to start writing register values. Again, it must be a number between 0 and 65535.

=item values

You can enter up to 121 values in an array reference. Each value will be coded in a 16-bit word, so they must be between 0 and 65535.

=back

=head2 pdu

This method returns the binary representation of the request. Before sending it to a server (or to a slave, which amounts to the same), you must wrap the PDU within a protocol header and footer. See the ADU methods of the clients for this.

lib/Device/Modbus/Server.pm  view on Meta::CPAN

 }
 
 my $server = Device::Modbus::TCP::Server->new(
     log_level => 4,
     log_file  => 'logfile'
 );
 
 my $unit = My::Unit->new(id => 3);
 $server->add_server_unit($unit);
 
 $server->start;


=head1 DESCRIPTION

This document describes functionalities common to both Modbus RTU and Modbus TCP servers. Constructors are documented in L<Device::Modbus::RTU::Server> and L<Device::Modbus::TCP::Server>.

First, we will briefly describe the data model inherent to the protocol. This is the base for building the functionalities that the server will expose. Then, these functionalities need to be attached to the server, which must finally be started.

=head1 THE MODBUS DATA MODEL

The Modbus protocol communicates a client with a I<unit> in a server. A unit may offer functionalities in one to four different zones of different types:

=over

=item * Discrete inputs

=item * Discrete outputs (or coils)

lib/Device/Modbus/Server.pm  view on Meta::CPAN

=item * Holding registers

=back

Client requests are sent to a particular server unit, and they specify the data zone they are directed to, the address which will be affected, and the number of data points they refer to. Write requests include also the transmitted values.

=head1 DEFINING A UNIT

In Device::Modbus, a unit is represented by an object which inherits from Device::Modbus::Unit. Each object maps requests to exposed functions. To execute a function, a request must match its address zone, its set of valid addresses, and the quantity...

To define a unit, you must start with a class that inherits from Device::Modbus::Unit. This class must implement a method called C<init_unit>, which is responsible of defining the mapping to the exposed class methods.

Requests may either I<get> data from the server or they may I<put> data into the server. C<get> and C<put> are the methods used to define the mapping to the functionality exposed by the server. Both methods receive the same arguments: a zone, an addr...

I think the best explanation is an example:

 package My::Unit;
 use parent 'Device::Modbus::Unit';
 
 sub init_unit {
     my $unit = shift;

lib/Device/Modbus/Server.pm  view on Meta::CPAN

     return @values;
 }


Note that routines which handle reading requests must return the exact number of requested registers or bits. Values are returned as arrays, not as array references. Register values must be numbers between 0 and 65536; bits are simply true and false ...

=head1 SERVER METHODS

Aside from your unit class, you must instantiate a server. Server construction methods depend on the communication channel that you will be using. L<Device::Modbus::RTU::Server> communicates via the serial port; L<Device::Modbus::TCP::Server> uses TC...

Once your unit class is defined, it must be instantiated and added to a server. Then, the server must be started. From the synopsis:

 my $unit = My::Unit->new(id => 3);
 $server->add_server_unit($unit);
 
 $server->start;

In this example, the unit object is added to the server as unit number three. You can add any number of units to a server.

And that is all it takes.

=head1 SEE ALSO

This module is part of the L<Device::Modbus> distribution. Server constructors are documented in L<Device::Modbus::RTU::Server> and L<Device::Modbus::TCP::Server>.

I have written some examples in my blog, L<http://7mavida.com/tag/Device::Modbus>.

t/lib/Test/Server.pm  view on Meta::CPAN


use parent 'Device::Modbus::Server';
use strict;
use warnings;


sub log_level {
    return 2;
}

sub start {
    print STDERR "# Required by Device::Modbus::Server\n";
}

my %level_str = (
    0 => 'ERROR',
    1 => 'WARNING',
    2 => 'NOTICE',
    3 => 'INFO',
    4 => 'DEBUG',
);

t/server.t  view on Meta::CPAN

    use_ok 'Device::Modbus::ADU';
    use_ok 'TestServer';
}

{
    my $server = TestServer->new();
    ok $server->DOES('Device::Modbus::Server'),
        'The server object plays Device::Modbus::Server';

    is_deeply $server->units, {},
        'Units are saved in a hash reference which starts empty';

    eval { $server->init_server; };
    like $@, qr{Server must be initialized},
        'Initialization method must be subclassed';
}

{
    package My::Unit;
    use Test::More;
    our @ISA = ('Device::Modbus::Unit');

t/units.t  view on Meta::CPAN


    my $unit = Device::Modbus::Unit->new(id => 3);

    isa_ok $unit, 'Device::Modbus::Unit';
    is  $unit->id, 3,
        'Unit created successfully and ID is correct';

    is ref $unit->routes->{'holding_registers:read'}, 'ARRAY',
        'Addresses will be stored in a hash of array refs';
    is scalar @{$unit->routes->{'holding_registers:read'}}, 0,
        'The arrays start empty';

    #                Zone           addr  qty    method
    #           ------------------- ----- --- -----------------
    $unit->get('holding_registers', '1-5', 5,  sub { return 6 });

    is scalar @{$unit->routes->{'holding_registers:read'}}, 1,
        'Added an address to the holding_registers:read array';

    my $match = $unit->route('holding_registers', 'read', 3, 5);



( run in 0.314 second using v1.01-cache-2.11-cpan-fd5d4e115d8 )