stumped has asked for the wisdom of the Perl Monks concerning the following question:


I have a home automation system which I am currently monitoring with a script, on a fedora box, which uses Device:SerialPort to read the output and then DBI to write it to a database. The data is hex terminated with a x0A. So I use a while loop with lookfor. When the status changes a status change message is sent, so there are random time intervals, which if there are no status changes can be very long.

The problem

I now want to control the HA system through the perl script. So I need to be able to write data to the serial port without disturbing the monitoring above. The data flows will be 98% incoming lookfor reads with irregular intervals and 2% writing instructions.

I have tried searching for a suitable module or code, but there does not seem to be a clear way to approach the problem with alternatives being some sort of filehandle or a socket (perhaps a pair of sockets?). The examples I can find all seem to be based on win32, so not directly comparable.

Can anyone help with a simple low overhead method of writing a hex string (which I can prepare with pack), given that the script may be in a loop waiting at the lookfor for the next status message?



Replies are listed 'Best First'.
Re: duplex serial read write
by kschwab (Priest) on Nov 14, 2013 at 20:00 UTC

    There is a neat program called ser2net that would allow you to maintain these things as two separate programs. Basically, it would expose the serial port via a tcp socket that program #1 can connect to, and is able to talk bidirectionally just like any socket client.

    The second program...the one that just needs read access to what's coming out of the port could do one of two things:

    • Continuously watch the output of a "tracefile" that ser2net can provide
    • Or, it can also connect (via tcp) to a separate control port, provided by ser2net, send the string "monitor term <port>", and then it will get a streamed copy of everything coming from the serial port.
    It does support binding it's listen ports to only localhost, so it doesn't need to expose all of this to outside hosts. The man page is here. Prebuilt packages are out there for debian, redhat, etc.
Re: duplex serial read write
by BrowserUk (Pope) on Nov 14, 2013 at 18:14 UTC

    Use a count (timeout) on the lookfor(), and each time it times out, check a flag to see if there is anything to be sent.

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.
Re: duplex serial read write
by Synsation (Beadle) on Nov 14, 2013 at 19:20 UTC
    use AnyEvent;
Re: duplex serial read write
by stumped (Novice) on Jul 03, 2014 at 10:02 UTC

    Thanks for all the suggestions. In the end I found this script that uses two non blocking threads and MQTT messages to send and receive info. It does exactly what I needed.

    #!/usr/bin/perl use threads; use threads::shared; use JSON; use WebSphere::MQTT::Client; use Device::SerialPort; use Time::HiRes qw(time); use strict; # Jeelink port and speed my $jeelink_port = '/dev/ttyUSB0'; # mqtt configuration my $mqtt_hostname = 'localhost'; my $mqtt_port = 1883; # this array is the queu commands to be written to the JeeLink my @queue : shared = (); # the thread to subscribes to MQTT messages sub mqtt { my $mqtta = new WebSphere::MQTT::Client( Hostname => $mqtt_hostname, Port => $mqtt_port, clientid => 'rf12_received_mqtt', ); # connect to mqtt server my $res = $mqtta->connect(); die "Failed to connect: $res\n" if ($res); # Subscribe to topic to be forwarded to the JeeLink, multiple topi +cs can be added here $mqtta->subscribe( 'TIME' ); my @res = (); for (;;) { @res = $mqtta->receivePub(); my $obj = from_json($res[1]); my $topic = $res[0]; my $msg = undef; if ( $topic eq 'TIME' ) { # pack the data into binary my $binary = pack("C C C",$obj->{hour},$obj->{minute},$obj +->{second}); # convert binary into a command to the Jeelink $msg = join(',',unpack("C*",$binary)) . ',0s'; } # encodes for other topics can be added here # push the message to be sent to the JeeLink on the queue if ( $msg ) { lock(@queue); push(@queue,$msg); } } } # the thread that talks to the JeeLink sub serial { # creat an MQTT server reference my $mqtt = new WebSphere::MQTT::Client( Hostname => $mqtt_hostname, Port => $mqtt_port, clientid => 'rf12_sender_mqtt', ); # connect to MQTT server my $res = $mqtt->connect(); die "Failed to connect: $res\n" if ($res); # A ping is sent to the MQTT server each five seconds. $ts is used + for keeping track of that my $ts = time(); # Set up the serial port my $port = Device::SerialPort->new($jeelink_port); $port->databits(8); $port->baudrate(57600); $port->parity("none"); $port->stopbits(1); # start endless loop for(;;) { # look for data on the serial port my $recv = $port->lookfor(); if ( $recv ) { # we're only interested in good messages if ( $recv =~ m/^OK / ) { my @words = split(' ',$recv); # create an empty packet my $obj = {}; shift(@words); # shift OK $obj->{id} = shift(@words) & 0x1F; # shift and + maskthe node id my $binary = pack("C*",@words); my $mqtt_topic = undef; # decode a message received from the energy meter if ( $obj->{id} == 0x05 ) { ($obj->{realpower},$obj->{apparentpower},$ +obj->{powerfactor},$obj->{vrms},$obj->{irms},$obj->{netfrequency}) = +unpack("f f f f f f",$binary); $mqtt_topic = 'POWERMETER'; } # decode a message received from a room node elsif ( $obj->{id} == 0x03 ) { ($obj->{light},$obj->{humidity},$obj->{tem +perature}) = unpack("C C s",$binary); $obj->{moved} = $obj->{humidity} & 1; $obj->{humidity} = $obj->{humidity} >> 1; $obj->{temperature} = $obj->{temperature} +& 0x03FF; $mqtt_topic = 'ROOMNODE'; } # send the message as mqtt message encode in JSON + if ( $mqtt_topic ) { my $result = $mqtt->publish(to_json($obj),$mqtt_to +pic); # record the time we sent the last message $ts = time(); } } } # if no message was sent each 5 seconds, just send a status me +ssage to keep us connected to the server if ( time() - $ts > 5 ) { $mqtt->status(); $ts = time(); } # check for new packets on the queue { my $msg = undef; { lock(@queue); if ( scalar(@queue) > 0 ) { $msg = shift(@queue); } } # write the message to the serial port if ( $msg ) { $port->write($msg . "\n"); } } } } # start both threads my $thr1 = threads->new(\&mqtt); my $thr2 = threads->new(\&serial); $thr1->join; $thr2->join;