Device::SerialPort provides a tied filehandle interface that works reasonably well with POE. Version 1.000_002 of the library has some syswrite() problems that have been reported to the author. Please note the workaround in this example.

#!/usr/bin/perl
use warnings;
use strict;
use POE;
use POE::Wheel::ReadWrite;
use POE::Wheel::ReadLine;
use Symbol qw(gensym);
use Device::SerialPort;
use POE::Filter::Line;
POE::Session->create(
  inline_states => {
    _start      => \&setup_device,
    got_port    => \&display_port_data,
    got_console => \&transmit_console_data,
    got_error   => \&handle_errors,
  },
);
POE::Kernel->run();
exit 0;

sub setup_device {
  my ($kernel, $heap) = @_[KERNEL, HEAP];

  # Open a serial port, and tie it to a file handle for POE.
  my $handle = gensym();
  my $port = tie(*$handle, "Device::SerialPort", "/dev/cu.modem");
  die "can't open port: $!" unless $port;
  $port->datatype('raw');
  $port->baudrate(4800);
  $port->databits(8);
  $port->parity("none");
  $port->stopbits(1);
  $port->handshake("rts");
  $port->write_settings();

  # Start interacting with the GPS.
  $heap->{port}       = $port;
  $heap->{port_wheel} = POE::Wheel::ReadWrite->new(
    Handle => $handle,
    Filter => POE::Filter::Line->new(
      InputLiteral  => "\x0D\x0A",    # Received line endings.
      OutputLiteral => "\x0D",        # Sent line endings.
    ),
    InputEvent => "got_port",
    ErrorEvent => "got_error",
  );

  # Start a wheel to interact with the console, and prompt the user.
  $heap->{console} = POE::Wheel::ReadLine->new(InputEvent => "got_console",);
  $heap->{console}->put("Press ^D to stop.");
  $heap->{console}->get("Ready: ");
}

# Port data (lines, separated by CRLF) are displayed on the console.
sub display_port_data {
  my ($heap, $data) = @_[HEAP, ARG0];
  $heap->{console}->put($data);
}

# Console input is sent to the device.
sub transmit_console_data {
  my ($heap, $input) = @_[HEAP, ARG0];
  if (defined $input) {
    $heap->{console}->addhistory($input);
    $heap->{port_wheel}->put($input);
    $heap->{console}->get("Ready: ");

    # Clearing $! after $serial_port_wheel->put() seems to work around
    # an issue in Device::SerialPort 1.000_002.
    $! = 0;
    return;
  }
  $heap->{console}->put("Bye!");
  delete $heap->{port_wheel};
  delete $heap->{console};
}

# Error on the serial port.  Shut down.
sub handle_errors {
  my $heap = $_[HEAP];
  $heap->{console}->put("$_[ARG0] error $_[ARG1]: $_[ARG2]");
  $heap->{console}->put("bye!");
  delete $heap->{console};
  delete $heap->{port_wheel};
}