#!/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 $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,
           '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 $mqtt =
  AnyEvent::MQTT->new(host => $host, port => $port,
                      keep_alive_timer => $keep_alive_timer,
                      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.142230

=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<-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
