#!/usr/bin/perl -w use strict; use lib '/home/troc/perl/poe'; use lib '/home/troc/perl/Async'; # Import POE-y things we'll need. Async is a helper to open serial # ports. use POE::Kernel; use POE::Wheel::ReadWrite; use POE::Filter::Line; use POE::Filter::Stream; use POE::Driver::SysRW; use POE::NFA; use Async; # ASCII constants. sub STX () { "\002" } sub ETX () { "\003" } sub EOT () { "\004" } sub ENQ () { "\005" } sub ACK () { "\006" } sub LF () { "\012" } sub CR () { "\015" } sub NAK () { "\025" } sub ESC () { "\033" } sub RS () { "\036" } sub READY () { ESC . '[p' } sub DISC () { ESC . EOT } # Generate a showable representation of some data which may not # necessarily display properly. sub show { my @stuff = ( map { ( ($_ lt ' ' or $_ gt '~') ? ('<' . unpack('H*', $_) . '>') : $_ ) } split //, shift ); join '', @stuff; } # Send some data, but first show it. sub put { my ($port, $stuff) = @_; print STDERR ">>> ", &show($stuff), "\n"; $port->put( $stuff ); } # Build a TAP packet. This wraps the pager ID and short message in a # VISA protocol packet, with a funky sort of checksum. sub make_packet { my ($pager_id, $message) = @_; my $packet = STX . $pager_id . CR . $message . CR . ETX; my $checksum = substr(unpack('H*', pack('N', unpack('%32C*', $packet))), -3); $checksum =~ tr[a-f][:-?]; $packet .= $checksum . CR; return $packet; } # Get the pager ID and short message from the command line. my $pager_id = shift @ARGV; my $message = "@ARGV"; die "usage: $0 pager-number message" unless ( defined $pager_id and length $pager_id and defined $message and length $message ); # The TAP machine itself, plus some inline code to spawn it. my %tap_machine = ( close_modem => { enter => sub { # sub: close_port delete $_[RUNSTATE]->{Port}; # sub: set_close_settle_delay $_[KERNEL]->delay( port_settled => 1 ); }, port_settled => sub { # default transition $_[MACHINE]->goto_state( 'open_modem', 'enter' ); }, }, dial => { dial_timeout => sub { # default transition $_[MACHINE]->goto_state( 'close_modem', 'enter' ); }, enter => sub { # sub: send_dial_command &put( $_[RUNSTATE]->{Port}, 'ATDT ' . substr($_[RUNSTATE]->{Pager_ID}, 3, 3) . '-6683' . CR ); # sub: set_dial_timeout $_[KERNEL]->delay( dial_timeout => 90 ); }, input => sub { # sub: trace_input print STDERR "<<< ", &show($_[ARG0]), "\n"; # recognizer $_[ARG0] =~ /^CONNECT (\d+)/ and return $_[MACHINE]->goto_state( 'tap_await_id', 'enter' ); # recognizer $_[ARG0] =~ /^NO CARRIER$/ and return $_[MACHINE]->goto_state( 'redial', 'enter' ); }, leave => sub { # sub: clear_dial_timeout $_[KERNEL]->delay( 'dial_timeout' ); }, }, done => { enter => sub { # sub: close_port delete $_[RUNSTATE]->{Port}; # sub: set_close_settle_delay $_[KERNEL]->delay( port_settled => 1 ); }, port_settled => sub { # default transition $_[MACHINE]->goto_state( 'exit', 'enter' ); }, }, exit => { enter => sub { # sub: exit # does nothing }, }, init_1 => { enter => sub { # sub: send_first_init_string &put( $_[RUNSTATE]->{Port}, 'ATZ' . CR ); # sub: set_init_timeout $_[KERNEL]->delay( init_timeout => 5 ); }, init_timeout => sub { # default transition $_[MACHINE]->goto_state( 'close_modem', 'enter' ); }, input => sub { # sub: trace_input print STDERR "<<< ", &show($_[ARG0]), "\n"; # recognizer $_[ARG0] =~ /^OK$/ and return $_[MACHINE]->goto_state( 'init_2', 'enter' ); }, leave => sub { # sub: clear_init_timeout $_[KERNEL]->delay( 'init_timeout' ); }, }, init_2 => { enter => sub { # sub: send_second_init_string &put( $_[RUNSTATE]->{Port}, 'ATE0M0' . CR ); # sub: set_init_timeout $_[KERNEL]->delay( init_timeout => 5 ); }, init_timeout => sub { # default transition $_[MACHINE]->goto_state( 'close_modem', 'enter' ); }, input => sub { # sub: trace_input print STDERR "<<< ", &show($_[ARG0]), "\n"; # recognizer $_[ARG0] =~ /^OK$/ and return $_[MACHINE]->goto_state( 'dial', 'enter' ); }, leave => sub { # sub: clear_init_timeout $_[KERNEL]->delay( 'init_timeout' ); }, }, open_modem => { enter => sub { # sub: open_port $_[RUNSTATE]->{Port} = POE::Wheel::ReadWrite->new ( Handle => Async->new( Device => $_[RUNSTATE]->{Device_Name}, Speed => 38400, Size => 7, Parity => 'e', Stop => 1, Vmin => 1, ), InputFilter => POE::Filter::Line->new( Literal => "\x0D\x0A" ), OutputFilter => POE::Filter::Stream->new(), Driver => POE::Driver::SysRW->new( BlockSize => 1 ), InputState => 'input', ); # sub: set_close_settle_delay $_[KERNEL]->delay( port_settled => 1 ); }, port_settled => sub { # default transition $_[MACHINE]->goto_state( 'init_1', 'enter' ); }, }, redial => { enter => sub { # sub: set_redial_timeout $_[KERNEL]->delay( redial_timeout => 4 ); }, redial_timeout => sub { # default transition $_[MACHINE]->goto_state( 'dial', 'enter' ); }, }, start => { enter => sub { # sub: store_page_info $_[RUNSTATE]->{Device_Name} = $_[ARG0]; $_[RUNSTATE]->{Pager_ID} = $_[ARG1]; $_[RUNSTATE]->{Message} = $_[ARG2]; # default transition $_[MACHINE]->goto_state( 'open_modem', 'enter' ); }, }, tap_await_id => { enter => sub { # sub: set_id_timeout $_[KERNEL]->delay( id_timeout => 2 ); # sub: set_state_timeout $_[KERNEL]->delay( state_timeout => 15 ); }, id_timeout => sub { # sub: send_cr &put( $_[RUNSTATE]->{Port}, CR ); # sub: set_id_timeout $_[KERNEL]->delay( id_timeout => 2 ); }, input => sub { # sub: trace_input print STDERR "<<< ", &show($_[ARG0]), "\n"; # recognizer $_[ARG0] =~ /^ID=$/ and return $_[MACHINE]->goto_state( 'tap_send_page_type', 'enter' ); # recognizer $_[ARG0] =~ /^NO CARRIER$/ and return $_[MACHINE]->goto_state( 'close_modem', 'enter' ); }, leave => sub { # sub: clear_id_timeout $_[KERNEL]->delay( 'id_timeout' ); # sub: clear_state_timeout $_[KERNEL]->delay( 'state_timeout' ); }, state_timeout => sub { # default transition $_[MACHINE]->goto_state( 'close_modem', 'enter' ); }, }, tap_send_page_packet => { enter => sub { # sub: set_state_timeout $_[KERNEL]->delay( state_timeout => 15 ); # sub: send_packet &put( $_[RUNSTATE]->{Port}, &make_packet( $_[RUNSTATE]->{Pager_ID}, $_[RUNSTATE]->{Message} ) ); }, input => sub { # sub: trace_input print STDERR "<<< ", &show($_[ARG0]), "\n"; # recognizer $_[ARG0] =~ /^\006$/ and return $_[MACHINE]->goto_state( 'tap_wait_disconnect', 'enter' ); # recognizer $_[ARG0] =~ /^NO CARRIER$/ and return $_[MACHINE]->goto_state( 'close_modem', 'enter' ); }, leave => sub { # sub: clear_state_timeout $_[KERNEL]->delay( 'state_timeout' ); }, state_timeout => sub { # default transition $_[MACHINE]->goto_state( 'close_modem', 'enter' ); }, }, tap_send_page_type => { enter => sub { # sub: set_state_timeout $_[KERNEL]->delay( state_timeout => 15 ); # sub: send_esc_pg1_cr $_[RUNSTATE]->{Port}->set_input_filter( POE::Filter::Line->new( Literal => CR ) ); &put( $_[RUNSTATE]->{Port}, ESC . 'PG1' . CR ); }, input => sub { # sub: trace_input print STDERR "<<< ", &show($_[ARG0]), "\n"; # recognizer $_[ARG0] =~ /^\e\[p$/ and return $_[MACHINE]->goto_state( 'tap_send_page_packet', 'enter' ); # recognizer $_[ARG0] =~ /^NO CARRIER$/ and return $_[MACHINE]->goto_state( 'close_modem', 'enter' ); }, leave => sub { # sub: clear_state_timeout $_[KERNEL]->delay( 'state_timeout' ); }, state_timeout => sub { # default transition $_[MACHINE]->goto_state( 'close_modem', 'enter' ); }, }, tap_wait_disconnect => { enter => sub { # sub: set_state_timeout $_[KERNEL]->delay( state_timeout => 15 ); # sub: send_eot_cr &put( $_[RUNSTATE]->{Port}, EOT . CR ); }, input => sub { # sub: trace_input print STDERR "<<< ", &show($_[ARG0]), "\n"; # recognizer $_[ARG0] =~ /^\e\004$/ and return $_[MACHINE]->goto_state( 'done', 'enter' ); # recognizer $_[ARG0] =~ /^NO CARRIER$/ and return $_[MACHINE]->goto_state( 'done', 'enter' ); }, leave => sub { # sub: clear_state_timeout $_[KERNEL]->delay( 'state_timeout' ); }, state_timeout => sub { # default transition $_[MACHINE]->goto_state( 'done', 'enter' ); }, }, ); sub spawn_tap_machine { POE::NFA->spawn( inline_states => \%tap_machine )->goto_state( start => enter => @_ ); } # Spawn a TAP machine with a pager ID and a message to send it. Run # POE until the message is sent, and then exit. Takes a device # name... it's possible to spawn several of these at once, each on a # different port, to page people in parallel. &spawn_tap_machine( '/dev/com2', $pager_id, $message ); $poe_kernel->run(); exit 0;