Commit a62c7516 authored by Renaud Pacalet's avatar Renaud Pacalet
Browse files

Init

parents
Copyright (C) 2014 Renaud Pacalet <renaud@pacalet.fr>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#!/usr/bin/env perl
package MSG;
use strict;
use warnings;
use Carp;
my %DEFAULTS = (
"id" => 0,
"parameters" => [],
);
sub new {
my $usage = "Usage: MSG->new(...)\n msg->new(...)\n";
my $proto = shift or croak("$usage");
my $class = ref($proto) || $proto;
my $self = {};
if(!ref($proto)) {
$proto = \%DEFAULTS;
}
my $id = scalar(@_) ? shift : $proto->{"id"};
croak("MSG->new(...): Invalid id: $id\n") if($id !~ /^\d+$/ || $id > 65535);
$self->{"id"} = $id;
my @parameters = @_;
$self->{"parameters"} = \@parameters;
bless $self, $class;
return $self;
}
sub print {
my $usage = "Usage: MSG->print(msg)\n msg->print(msg)\n";
my $self = shift or croak("$usage");
printf("id: %d\n", $self->{"id"});
printf("parameters:\n");
my $i = 1;
for my $parameter ( @{$self->{"parameters"}} ) {
printf(" %3d: 0x%08x\n", $i, $parameter);
$i += 1;
}
}
# Message to network string
sub msg2nstr {
my $usage = "Usage: MSG->msg2nstr()\n msg->msg2nstr()\n";
my $self = shift or croak("$usage");
return pack('nC/l>*', $self->{"id"}, @{$self->{"parameters"}});
}
# Network string to message
sub nstr2msg {
my $usage = "Usage: MSG->nstr2msg(str)\n";
my $class = shift or croak("$usage");
croak("$usage") if(ref($class));
my $str = shift or croak("$usage");
my ( $id, @params ) = unpack('nC/l>', $str);
my $msg = MSG->new($id, @params);
return $msg;
}
1;
COPYRIGHT
---------
See Copyright Notice in LICENSE. Comments and suggestions of improvements are
welcome. Please report bugs to renaud@pacalet.fr.
DESCRIPTION
-----------
pegp (Perl-Ethernet-Perl-Gnuplot) is a very basic example that shows how an
acquisition device can send acquired samples to a visualization device through
UDP packets. This application is largely inspired by the driveGnuPlots.pl perl
script by Thanassis Tsiodras (thanks Thanassis) with several improvements and
some important differences.
----
MSG.pm is a perl module that handles messages. The messages comprise a 16-bits
identifier ID, an 8-bits number of values N and N 32-bits integer values. The
module uses pack to convert messages between perl hashes to network strings.
----
server.pl plays the role of the acquisition device. It can be used to generate
several streams of data samples (sines of various frequencies) and send them
through a UDP socket. Each UDP packet contains a 16-bits stream index and a
single 32-bits sample value.
Usage: ./server.pl [OPTION]...
Send sine samples to a UDP socket. Samples are in the MSG form where the id
field is a stream index and the parameters field is a 32 bits integer encoding
the sine value: parameters = int32_t(sine(X)*2^30). The --offset=OFFSET option
specifies the X increment between two consecutive sine samples.
--address=ADDRESS destination address (default 127.0.0.1)
--port=PORT destination UDP port (default 56780)
--rate=RATE number of samples per second (default 50)
--duration=DURATION total duration in seconds, 0 for never ending (default 1)
--index=INDEX index of stream to send (default ARRAY(0x1f00d80))
--offset=OFFSET X increment between two consecutive sine(X) (default ARRAY(0x1f1c860))
--end send a sample with id 0xffff at the end (if duration != 0)
--help print this help
ADDRESS is a IP address or a hostname. PORT, RATE and INDEX are natural integers.
DURATION and OFFSET are reals. Several --index=INDEX options can be specified;
one stream is generated for each specified stream index (if a stream index is
specified several times only the last one is taken into account). Several
--offset=OFFSET options can thus also be specified. They are associated
one-to-one to the different streams in their respective order of appearence. The
last specified --offset=OFFSET option, if any, overrides the ARRAY(0x1f1c860)
default value. If more options are specified than the number of streams, the
extraneous are ignored. The --address=ADDRESS, --port=PORT, --rate=RATE and
--duration=DURATION options are common to all streams.
Exit status:
0 if OK,
1 if minor problems (e.g., invalid options),
2 if serious trouble (e.g., cannot open socket).
----
client.pl plays the role of the visualization device. It receives data samples
in UDP packets, selects some of them based on their stream indices and
continuously plots the selected streams in different Gnuplot windows.
Usage: ./client.pl [OPTION]...
Listen to a UDP socket for data samples and plot them. Samples are in the MSG
form where the id field is a stream index and the parameter field is a 32 bits
integer value to plot.
--port=PORT UDP port to listen to (default 56780)
--index=INDEX index of stream to plot (default 0)
--samples=SAMPLES gnuplot window width in samples (default 250)
--title=TITLE gnuplot window title (default '')
--ymin=YMIN gnuplot minimum of vertical axis (default -2147483648)
--ymax=YMAX gnuplot maximum of vertical axis (default 2147483647)
--threshold=THRESHOLD vertical position of threshold line (default 0)
--geometry=GEOMETRY position on gnuplot window (default 640x480+0+0)
--persist lets plot window survive after main gnuplot program exits
--end interprets any sample with id=0xffff as a request to stop and exit
--help print this help
GEOMETRY format is the X11 one: [<width>{xX}<height>][{+-}<xoffset>{+-}<yoffset>].
PORT, INDEX, and SAMPLES are natural integers. YMIN, YMAX and THRESHOLD are
integers. Several --index=INDEX options can be specified; one plotting window is
created for each specified stream index (if a stream index is specified several
times only the last one is considered). Several --samples=SAMPLES options can
thus also be specified. They are associated one-to-one to the different streams
in their respective order of appearence. The last specified --samples=SAMPLES
option, if any, overrides the 250 default value. If more
options are specified than the number of streams, the extraneous are ignored.
The same holds for the --ymin=YMIN, --ymax=YMAX, --threshold=THRESHOLD,
--title=TITLE and --geometry=GEOMETRY options. The --port=PORT and --persist
options are common to all streams.
Exit status:
0 if OK,
1 if minor problems (e.g., invalid options),
2 if serious trouble (e.g., cannot open socket or pipe to gnuplot).
----
test.sh simply launches the client and the server for testing. Of course, for
testing, the client and the server both run on the same computer but it is very
easy to deploy this example on two different nodes by changing the --address
option of the server.
To run the test:
./test.sh
#!/usr/bin/env perl
use IO::Socket::INET;
use Getopt::Long;
use strict;
use warnings;
use MSG;
$| = 1;
my %DEFAULTS = (
"port" => 56780,
"index" => 0,
"samples" => 250,
"ymin" => -2**31,
"ymax" => 2**31 - 1,
"threshold" => 0,
"title" => "",
"geometry" => "640x480+0+0",
);
my %options = ();
GetOptions(\%options, "port=i", "index=s@", "samples=s@", "title=s@", "ymin=s@", "ymax=s@", "threshold=s@", "geometry=s@", "persist", "end", "help");
my $usage = <<EOS;
Usage: $0 [OPTION]...
Listen to a UDP socket for data samples and plot them. Samples are in the MSG
form where the id field is a stream index and the parameter field is a 32 bits
integer value to plot.
--port=PORT UDP port to listen to (default $DEFAULTS{"port"})
--index=INDEX index of stream to plot (default $DEFAULTS{"index"})
--samples=SAMPLES gnuplot window width in samples (default $DEFAULTS{"samples"})
--title=TITLE gnuplot window title (default '$DEFAULTS{"title"}')
--ymin=YMIN gnuplot minimum of vertical axis (default $DEFAULTS{"ymin"})
--ymax=YMAX gnuplot maximum of vertical axis (default $DEFAULTS{"ymax"})
--threshold=THRESHOLD vertical position of threshold line (default $DEFAULTS{"threshold"})
--geometry=GEOMETRY position on gnuplot window (default $DEFAULTS{"geometry"})
--persist lets plot window survive after main gnuplot program exits
--end interprets any sample with id=0xffff as a request to stop and exit
--help print this help
GEOMETRY format is the X11 one: [<width>{xX}<height>][{+-}<xoffset>{+-}<yoffset>].
PORT, INDEX, and SAMPLES are natural integers. YMIN, YMAX and THRESHOLD are
integers. Several --index=INDEX options can be specified; one plotting window is
created for each specified stream index (if a stream index is specified several
times only the last one is considered). Several --samples=SAMPLES options can
thus also be specified. They are associated one-to-one to the different streams
in their respective order of appearence. The last specified --samples=SAMPLES
option, if any, overrides the $DEFAULTS{"samples"} default value. If more
options are specified than the number of streams, the extraneous are ignored.
The same holds for the --ymin=YMIN, --ymax=YMAX, --threshold=THRESHOLD,
--title=TITLE and --geometry=GEOMETRY options. The --port=PORT and --persist
options are common to all streams.
Exit status:
0 if OK,
1 if minor problems (e.g., invalid options),
2 if serious trouble (e.g., cannot open socket or pipe to gnuplot).
Please report bugs to renaud\@pacalet.fr
EOS
# Parse common options
if(defined($options{"help"})) {
print $usage;
exit 0;
}
my $port = defined($options{"port"}) ? $options{"port"} : $DEFAULTS{"port"};
my $index = defined($options{"index"}) ? $options{"index"} : [ $DEFAULTS{"index"} ];
my $persist = defined($options{"persist"}) ? " --persist " : "";
my $end = defined($options{"end"}) ? 1 : 0;
if($port < 0 || $port > 65535) {
print "Invalid port specification: $port\n";
print $usage;
exit 1;
}
# TRICK
# This allows several values to be specified as --index=5,1,8 instead of giving
# several different --index=5, --index=1, ... options.
# END TRICK
@{$index} = split(/,/, join(',', @{$index}));
# Parse plots (multiple values) options
my @plots = ();
for my $i (@{$index}) {
if($i !~ /^\d+$/ || $i < 0) {
print "Invalid index specification: $i\n";
print $usage;
exit 1;
}
}
for my $k ( "samples", "title", "ymin", "ymax", "threshold", "geometry" ) {
my $tmp = defined($options{"$k"}) ? $options{"$k"} : [ $DEFAULTS{"$k"} ];
@{$tmp} = split(/,/, join(',', @{$tmp}));
my $default = $tmp->[-1];
for my $i (@{$index}) {
$plots[$i]->{"$k"} = @{$tmp} ? shift(@{$tmp}) : $default;
}
}
# Sanity checks, start plots
for my $i (@{$index}) {
my ( $samples, $title, $ymin, $ymax, $threshold, $geometry ) = ( $plots[$i]->{"samples"}, $plots[$i]->{"title"}, $plots[$i]->{"ymin"}, $plots[$i]->{"ymax"}, $plots[$i]->{"threshold"}, $plots[$i]->{"geometry"} );
if($samples !~ /^\d+$/) {
print "Invalid samples specification: " . $samples . "\n";
print $usage;
exit 1;
}
if($ymin !~ /^[+-]?\d+$/) {
print "Invalid ymin specification: " . $ymin . "\n";
print $usage;
exit 1;
}
if($ymax !~ /^[+-]?(\d+\.?|\d*\.\d+)([eE][+-]?\d+)?$/) {
print "Invalid ymax specification: " . $ymax . "\n";
print $usage;
exit 1;
}
if($ymin > $ymax) {
print "Invalid ymin/ymax specification: " . $ymin . " > " . $ymax . "\n";
print $usage;
exit 1;
}
if($threshold !~ /^[+-]?(\d+\.?|\d*\.\d+)([eE][+-]?\d+)?$/) {
print "Invalid threshold specification: " . $threshold . "\n";
print $usage;
exit 1;
}
if($ymin > $threshold || $threshold > $ymax) {
print "Warning: threshold (" . $threshold . ") not in [ymin...ymax] ([" . $ymin . "..." . $ymax . "]) range\n";
}
if($geometry !~ /^(\d+[xX]\d+)?([+-]\d+[+-]\d+)?$/) {
print "Invalid geometry specification: $geometry\n";
print $usage;
exit 1;
}
$geometry = " -geometry " . $geometry;
$plots[$i]->{"geometry"} = $geometry;
my $terminal = "x11";
my $xcounter = 0;
$plots[$i]->{"xcounter"} = $xcounter;
open(my $PIPE, "| gnuplot $geometry $persist");
if(!$PIPE) {
print "Can't initialize gnuplot\n";
exit 2;
}
$plots[$i]->{"pipe"} = $PIPE;
select((select($PIPE), $| = 1)[0]);
print $PIPE "# gnuplot $geometry $persist\n";
print $PIPE "set xtics\n";
print $PIPE "set ytics\n";
print $PIPE "set style data linespoints\n";
print $PIPE "set grid\n";
if($title) {
print $PIPE "set terminal $terminal title '" . $title . "' noraise\n";
$plots[$i]->{"title"} = "title '$title'";
} else {
print $PIPE "set terminal $terminal noraise\n";
$plots[$i]->{"title"} = "not";
}
print $PIPE "set yrange [$ymin:$ymax]\n";
print $PIPE "f(x,a) = a\n";
print $PIPE "set xrange [" . ($xcounter - $samples) . ":" . ($xcounter + 1) . "]\n";
print $PIPE "plot f(x,$threshold) title 'Detection threshold' lt 1\n";
my @buffer = ();
$plots[$i]->{"buffer"} = \@buffer;
}
my $sock = IO::Socket::INET->new(
LocalPort => $port,
Proto => 'udp',
Type => SOCK_DGRAM,
);
if(!$sock) {
print "Could not create socket: $!\n";
exit 2;
}
while(1) {
$sock->recv(my $data, 1024);
my $msg = MSG->nstr2msg($data);
last if($end && $msg->{"id"} == 0xffff);
my $idx = $msg->{"id"};
next if(!$plots[$idx]);
die("Invalid packet\n") if(@{$msg->{"parameters"}} != 1);
my $val = $msg->{"parameters"}->[0];
my $buffer = $plots[$idx]->{"buffer"};
push(@{$buffer}, $val);
my $PIPE = $plots[$idx]->{"pipe"};
my $samples = $plots[$idx]->{"samples"};
my $threshold = $plots[$idx]->{"threshold"};
my $title = $plots[$idx]->{"title"};
my $xcounter = $plots[$idx]->{"xcounter"};
print $PIPE "set xrange [" . ($xcounter - $samples) . ":" . ($xcounter + 1) . "]\n";
print $PIPE "plot f(x,$threshold) title 'Detection threshold' lt 1, \"-\" $title lt 3\n";
my $cnt = 0;
if(@{$buffer}) {
for my $elem (reverse @{$buffer}) {
print $PIPE ($xcounter - $cnt) . " " . $elem . "\n";
$cnt += 1;
}
print $PIPE "e\n";
}
if ($cnt >= $samples) {
shift @{$buffer};
}
$xcounter++;
$plots[$idx]->{"xcounter"}++;
}
for my $i (@{$index}) {
my $PIPE = $plots[$i]->{"pipe"};
print $PIPE "exit;\n";
close $PIPE;
}
$sock->close();
#!/usr/bin/env perl
use IO::Socket::INET;
use Time::HiRes qw/sleep/;
use Getopt::Long;
use strict;
use warnings;
use MSG;
$| = 1;
my %DEFAULTS = (
"address" => "127.0.0.1",
"port" => 56780,
"rate" => 50,
"duration" => 1,
"index" => [ 0 ],
"offset" => [ 0.1 ],
"end" => 0,
);
my %options = ();
GetOptions(\%options, "address=s", "port=i", "rate=i", "duration=f", "index=s@", "offset=s@", "end", "help");
my $usage = <<EOS;
Usage: $0 [OPTION]...
Send sine samples to a UDP socket. Samples are in the MSG form where the id
field is a stream index and the parameters field is a 32 bits integer encoding
the sine value: parameters = int32_t(sine(X)*2^30). The --offset=OFFSET option
specifies the X increment between two consecutive sine samples.
--address=ADDRESS destination address (default $DEFAULTS{"address"})
--port=PORT destination UDP port (default $DEFAULTS{"port"})
--rate=RATE number of samples per second (default $DEFAULTS{"rate"})
--duration=DURATION total duration in seconds, 0 for never ending (default $DEFAULTS{"duration"})
--index=INDEX index of stream to send (default $DEFAULTS{"index"})
--offset=OFFSET X increment between two consecutive sine(X) (default $DEFAULTS{"offset"})
--end send a sample with id 0xffff at the end (if duration != 0)
--help print this help
ADDRESS is a IP address or a hostname. PORT, RATE and INDEX are natural integers.
DURATION and OFFSET are reals. Several --index=INDEX options can be specified;
one stream is generated for each specified stream index (if a stream index is
specified several times only the last one is taken into account). Several
--offset=OFFSET options can thus also be specified. They are associated
one-to-one to the different streams in their respective order of appearence. The
last specified --offset=OFFSET option, if any, overrides the $DEFAULTS{"offset"}
default value. If more options are specified than the number of streams, the
extraneous are ignored. The --address=ADDRESS, --port=PORT, --rate=RATE and
--duration=DURATION options are common to all streams.
Exit status:
0 if OK,
1 if minor problems (e.g., invalid options),
2 if serious trouble (e.g., cannot open socket).
Report bugs to renaud.pacalet\@telecom-paristech.fr
EOS
if(defined($options{"help"})) {
print $usage;
exit 0;
}
my $address = defined($options{"address"}) ? $options{"address"} : $DEFAULTS{"address"};
my $port = defined($options{"port"}) ? $options{"port"} : $DEFAULTS{"port"};
my $rate = defined($options{"rate"}) ? $options{"rate"} : $DEFAULTS{"rate"};
my $duration = defined($options{"duration"}) ? $options{"duration"} : $DEFAULTS{"duration"};
my $index = defined($options{"index"}) ? $options{"index"} : $DEFAULTS{"index"};
my $offset = defined($options{"offset"}) ? $options{"offset"} : $DEFAULTS{"offset"};
my $end = defined($options{"end"}) ? $options{"end"} : $DEFAULTS{"end"};
if($port < 0 || $port > 65535) {
print "Invalid port specification: $port\n";
print $usage;
exit 1;
}
if($rate <= 0) {
print "Invalid rate specification: $rate\n";
print $usage;
exit 1;
}
my $period = 1.0 / $rate;
if($duration < 0) {
print "Invalid duration specification: $duration\n";
print $usage;
exit 1;
}
# TRICK
# This allows several values to be specified as --index=5,1,8 instead of giving
# several different --index=5, --index=1, ... options.
# END TRICK
@{$index} = split(/,/, join(',', @{$index}));
@{$offset} = split(/,/, join(',', @{$offset}));
# TRICK
# $offset is a reference to the array of the given --offset=OFFSET options or to
# the $DEFAULTS{"offset"} one cell array. It is naturally indexed (0, 1, 2...)
# @offset is *another* array of the offsets that will actually be used. It is
# indexed by the stream indices (5, 1, 8...).
# END TRICK
my @offset = ();
my @x = ();
for my $i (@{$index}) {
if($i !~ /^\d+$/ || $i < 0) {
print "Invalid index specification: $i\n";
print $usage;
exit 1;
}
$offset[$i] = @{$offset} ? shift(@{$offset}) : $offset->[-1];
if($offset[$i] !~ /^[+-]?(\d+\.?|\d*\.\d+)([eE][+-]?\d+)?$/) {
print "Invalid offset specification: " . $offset[$i] . "\n";
print $usage;
exit 1;
}
$x[$i] = 0;
}
my $sock = IO::Socket::INET->new(
PeerAddr => "$address",
PeerPort => $port,
Proto => 'udp',
Type => SOCK_DGRAM,
);
if(!$sock) {
print "Could not create socket: $!\n";
exit 2;
}
for(my $i = 0; $i < $duration * $rate; $i++) {
for my $i (@{$index}) {
my $val = int(sin($x[$i]) * 2**30);
my $msg = MSG->new($i, $val);
print $sock $msg->msg2nstr;
$x[$i] += $offset[$i];
}
sleep($period);
}
my $msg = MSG->new(0xffff);
print $sock $msg->msg2nstr if($end);
$sock->close();
#!/usr/bin/env bash
./client.pl --port=56780 \
--index=0,1,2 \
--samples=250 \
--ymin=-2000000000 \
--ymax=2000000000 \
--threshold=0 \
--title="Stream #1","Stream #2","Stream #3" \
--geometry=640x480+0+0,640x480+640+0,640x480+1280+0 \
--end &
sleep 1
./server.pl --address=127.0.0.1 \
--port=56780 \
--rate=50 \
--duration=10 \
--index=0,1,2 \
--offset=0.05,0.1,0.2 \
--end
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment