#!/usr/bin/perl
use strict;
use warnings;

# ABSTRACT: Perl script for subscribing to an MQTT topic
# PODNAME: anyevent-mqtt-sub


use Net::MQTT::Constants;
use AnyEvent::MQTT;
use Getopt::Long;
use Pod::Usage;

my $help;
my $man;
my $verbose = 0;
my $host = '127.0.0.1';
my $port = 1883;
my $retain = 1;
my $qos = MQTT_QOS_AT_MOST_ONCE;
my $count;
my $keep_alive_timer = 120;
my $client_id;
my $code;
GetOptions('help|?' => \$help,
           'man' => \$man,
           'verbose+' => \$verbose,
           'host=s' => \$host,
           'port=i' => \$port,
           'retain!' => \$retain,
           'qos=i' => \$qos,
           'count=i' => \$count,
           'one|1' => sub { $count = 1 },
           'keepalive=i' => \$keep_alive_timer,
           'client_id|client-id|C=s' => \$client_id,
           'code|e=s' => \$code) or pod2usage(2);
pod2usage(1) if ($help);
pod2usage(-exitstatus => 0, -verbose => 2) if $man;
pod2usage(2) unless (@ARGV); # need a topic

my @args;
push @args, client_id => $client_id if (defined $client_id);
my $mqtt =
  AnyEvent::MQTT->new(host => $host, port => $port,
                      keep_alive_timer => $keep_alive_timer,
                      @args,
                      on_error => sub {
                        my ($fatal, $message) = @_;
                        if ($fatal) {
                          die $message, "\n";
                        } else {
                          warn $message, "\n";
                        }
                      });

my $quit = AnyEvent->condvar;
my $cb = \&output;
if (defined $code) {
  $cb = sub {
    my ($topic, $message, $obj) = @_;
    push @_, $quit, $mqtt;
    eval $code; ## no critic
    die $@ if ($@);
  };
}
foreach my $topic (@ARGV) {
  $mqtt->subscribe(topic => $topic, callback => $cb, qos => $qos);
}

$quit->recv();

sub output {
  my ($topic, $message, $obj) = @_;
  return unless ($retain || !$obj->retain); # skip retained messages
  if ($verbose == 0) {
    print $topic, ' ', $message, "\n";
  } else {
    print $obj->string, "\n";
  }
  if (defined $count && --$count == 0) {
    $quit->send;
  }
}

__END__

=pod

=encoding UTF-8

=head1 NAME

anyevent-mqtt-sub - Perl script for subscribing to an MQTT topic

=head1 VERSION

version 1.212810

=head1 SYNOPSIS

  anyevent-mqtt-sub [options] topic1 [topic2] [topic3] ...

=head1 DESCRIPTION

This script subscribes to one or more MQTT topics and prints any
messages that it receives to stdout.

=head1 OPTIONS

=over

=item B<-help>

Print a brief help message.

=item B<-man>

Print the manual page.

=item B<-host A.B.C.D>

The host running the MQTT service.  The default is C<127.0.0.1>.

=item B<-port NNNNN>

The port of the running MQTT service.  The default is 1883.

=item B<-client-id STRING>

The client id to use in the connect message.  The default is
'NetMQTTpm' followed by the process id of the process.  This
should be up to 23 characters and only use the characters
A-Z, a-z, and 0-9 for maximum compatibility.

=item B<-qos N>

The QoS level for the published message.  The default is
0 (C<MQTT_QOS_AT_MOST_ONCE>).

=item B<-verbose>

Include more verbose output.  Without this option the script only
outputs errors and received messages one per line in the form:

  topic message

With one B<-verbose> options, publish messages are printed in a form
of a summary of the header fields and the payload in hex dump and text
form.

With two B<-verbose> options, summaries are printed for all messages
sent and received.

=item B<-keepalive NNN>

The keep alive timer value.  Defaults to 120 seconds.  For simplicity,
it is also currently used as the connection/subscription timeout.

=item B<-count NNN>

Read the specificed number of MQTT messages and then exit.  Default
is 0 - read forever.

=item B<-one> or B<-1>

Short for B<-count 1>.  Read one message and exit.

=item B<--no-retain>

Ignore retained messages.  That is, wait for new messages rather than
processing existing retained messages.

=item B<--code CODE>

Use CODE for callback.  The caller is responsible for ensuring that
the code to be executed is safe - i.e. not "system('rm -rf /');".  The
code will be called with the following arguments in C<@_>:

=over 4

=item The topic of the received message.

=item The message payload.

=item The L<Net::MQTT::Message> object for the message.

=item A L<condvar|AnyEvent/"CONDITION VARIABLES"> that can be emitted
      to quit the subscribe loop.

=item The L<AnyEvent::MQTT> object.

=item An empty hash reference that can be used as a stash.

=back

For example:

  my ($topic, $payload, $message, $quitcv, $mqtt, $stash) = @_;
  print STDERR $topic, ': ', $payload, "\n";
  $quitcv->send if ($stash->{'count'}++ > 10);

If a callback is provided with this option then the builtin callback
is no longer called.

=back

=head1 SEE ALSO

AnyEvent::MQTT(3)

=head1 DISCLAIMER

This is B<not> official IBM code.  I work for IBM but I'm writing this
in my spare time (with permission) for fun.

=head1 AUTHOR

Mark Hindess <soft-cpan@temporalanomaly.com>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2014 by Mark Hindess.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut
