La séptima vida

...o el gato así lo espera/teme

AnyEvent tcp_server

Having an AnyEvent-based Modbus server has its own appeal.The idea is that you could have other activities going on in the same process while your program is not working with requests, thus making interprocess communication unnecessary.

I can think of some examples:

A Modbus gateway for RS232-based devices.
RS232 is painful with most PLCs. You could, for example, request a weight measurement to a RS232 scale and then wait for the value already formatted into nice 16-bit registers, without building strings or parsing the result in the PLC.
A MQTT-Modbus gateway.
Imagine that you wanted the PLC to publish process data via MQTT, or that you wanted to make remote data available to a machine.
Publishing information to a web server.
A PLC might want to send information to a HTTP server to be logged or processed.

However, I must confess that I find asynchronous programming in general quite challenging, and quite interesting. So, this is a first attempt at writing a simple echo server. You connect to it with the telnet program and close the connection by typing bye. There is a sample session just below the program.

#! /usr/bin/env perl

use AnyEvent;
use AnyEvent::Socket;
use AnyEvent::Handle;
use v5.14;
use strict;
use warnings;

# Condition varialble. The program will loop until its signal is
# received.
my $cv = AnyEvent->condvar;

# See AnyEvent::Socket. This is the server implementation.
# tcp_server binds to a host and a port, and will execute the callback
# routine for every connection accepted.
tcp_server '127.0.0.1', 23876, sub {
    my ($fh, $host, $port) = @_;
    # If the file handle is not defined, there is a problem
    $cv->send("Error: $!") unless defined $fh;

    # The connection file handle is fed to AnyEvent::Handle, which
    # handles the complexity of reading and writing queues
    my $handle; $handle = AnyEvent::Handle->new(
        fh        => $fh,
        keepalive => 1,
        on_read   => sub {
            # This callback is executed every time that there is
            # new data to read.
            my $handle = shift;

            # We push into the read queue a routine that processes
            # a line at a time.
            $handle->push_read( line => sub {
                my $msg = $_[1];
                say "I got [$msg]";
                if ($msg eq 'bye') {
                    # We want out!
                    $handle->push_write("Bye!\n");
                    # Once the write queue is empty (data is gone)
                    # we terminate the program
                    $handle->on_drain(sub {
                        $handle->destroy;
                        $cv->send('Client said bye');
                    });
                } else {
                    # Echo back the message received (which is not bye)
                    $handle->push_write("ECHO: [$msg]\n");
                }
            });
        },
        # Executes if client closes its connection
        on_eof   => sub {
            $handle->destroy;
            $cv->send('Client disconnected');
        },
        # Well... in case it happens
        on_error => sub {
            my ($handle, $fatal, $msg) = @_;
            $handle->destroy;
            $cv->send($msg);
        },
    );

};

# This is a signal handler. If you type ctrl-C, the server stops
my $exit = AnyEvent->signal(
    signal => 'INT',
    cb     => sub {
        $cv->send('OK');
    }
);

# And we loop until the condvar receives a message!
my $msg = $cv->recv;
say "Exit: $msg" if $msg;

As for the sample session, this is the client terminal:

julio@julio-lap$ telnet localhost 23876
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hola, Crayola!
ECHO: [Hola, Crayola!]
Adiós, Supermán
ECHO: [Adiós, Supermán]
bye
Bye!
Connection closed by foreign host.

As for the server side:

julio@julio-lap$ perl my_server.pl 
I got [Hola, Crayola!]
I got [Adiós, Supermán]
I got [bye]
Exit: Client said bye

So... I shall try with a Modbus server!