#!/usr/bin/perl
##
##  asterisk-clock.pl -- Asterisk PBX Accurate Talking Clock
##  Copyright (c) 2007 Chris Walton
##  Copyright (c) 2008 Ralf S. Engelschall <rse@engelschall.com>
##  License: GPL (version 2 or higher)
##
##  This script will generate a 1.5s beep at 0, 15, 30, and 45 seconds
##  past each minute. It uses sub-second time delays in order to be as
##  accurate as possible. The time is annoumced in English at 11 seconds
##  before the beep. Warning "blips" play and 2 seconds and 1 second
##  before the beep.
##
##  1st PASS example... assume program starts at 5.5 seconds past the minute:
##  ----------------------------------------------------------------------------
##  0    1    2    3    4    5    6    7    8    9    10   11   12  13   14   15
##                            ->BLIP>BLIP>BLIP>BLIP>BLIP>BLIP>BLI>BLIP>BLIP>BEEP
##  normal operation (loop):
##  ----------------------------------------------------------------------------
##  0    1    2    3    4    5    6    7    8    9   10   11   12   13   14   15
##  BEEP--->SILENCE----->TIME_ANNOUNCEMENT_FOLLOWED_BY_SILENCE----->BLIP>BLIP>BEEP
##

use Time::HiRes qw(usleep gettimeofday);

my $blip    = "!523/20,!0/980";  # 20ms blip followed by 980ms silence.
my $beep    = "!415/1500";       # 1.5s beep.
my $silence = "!0/500";          # 0.5s silence.

#   turn off output buffering to stop dead lock.
$| = 1;

sub status {
    #   AGI function status is avaialbe on STDIN
    my $cmd = shift;
    my $status = <STDIN>;
    if ($status !~ m/^200 result=0/) {
        print STDERR "$cmd returned with bad status ($status)\n";
        exit(-1);
    }
}

#   STDIN will initially contain assorted channel information. Discard it all.
while (<STDIN>) {
    chomp;
    last unless ($_);
}

#   Answer the channel
print "ANSWER\n";
status("Exec Answer");

while (1) {
    my ($seconds, $microseconds) = gettimeofday();

    #   sleep until the next second rolls around.
    usleep(1000000 - $microseconds);

    #   calculate number of seconds left before the beep.
    my $time_til_beep = 14 - ($seconds % 15);

    if ($time_til_beep < 11 ) {
        #   Insufficient time to announce the time; just play blips.
        #   This should only occur on first pass.
        print "EXEC PlayTones ", ("$blip," x $time_til_beep), $beep, "\n";
        status("Exec PlayTones");

        #   Sleep while the blips and beep are playing.
        usleep($time_til_beep * 1000000 + 1500000);
    } else {
        #   We have sufficient time to announce what the time will be at the beep.
        my $time_at_beep = $seconds + $time_til_beep + 1;

        #   Sleep until 11 seconds before the beep and then announce the time.
        usleep(($time_til_beep - 11) * 1000000);
        print qq{STREAM FILE at-tone-time-exactly ""\n};
        status("Stream File");
        print qq{SAY DATETIME $time_at_beep "#" "IM'vm-and'S'seconds'\n};
        status("Say DateTime");

        #   We need to get the time again because we don't know how long
        #   the announcement took to play.
        ($seconds, $microseconds) = gettimeofday();

        #   Now sleep until 2.5 seconds before the beep.
        my $delay = 12500000 - $microseconds - (($seconds % 15) * 1000000);
        next if ($delay < 0); # abort if annoucment took too long to play.
        usleep($delay);

        #   Now play two warning blips followed by the beep.
        #   Preceeding the blips with .5s silence ensures the 1st blip is heard.
        print "EXEC PlayTones $silence,$blip,$blip,$beep\n";
        status("Exec PlayTones");

        #   Sleep while the blips and beep are playing.
        usleep(4000000);
    }
}

