| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- #!/usr/bin/perl
- use strict;
- use warnings;
- use File::Basename;
- use File::Temp qw( tempfile );
- use IO::Select;
- use IO::Socket::UNIX;
- use Device::SerialPort;
- # Prototypes
- sub sendQuery($$);
- sub clearBuffer($);
- sub collectUntil($$);
- # Device parameters
- my ($DEV, $PORT, $BLUETOOTH, $CRLF, $DELIMITER, %CMDS, $STATUS_ON);
- if (basename($0) =~ /PROJECTOR/i) {
- $DEV = 'Projector';
- $PORT = '/dev/tty.usbserial-A5006xbj';
- $BLUETOOTH = 0;
- $CRLF = "\r\n";
- $DELIMITER = ':';
- %CMDS = (
- 'INIT' => '',
- 'ON' => 'PWR ON',
- 'OFF' => 'PWR OFF',
- 'STATUS' => 'PWR?'
- );
- $STATUS_ON = 'PWR=01';
- } elsif (basename($0) =~ /AMPLIFIER/i) {
- $DEV = 'Amplifier';
- $PORT = '/dev/tty.usbserial-A5006x9u';
- $BLUETOOTH = 0;
- $CRLF = "\r";
- $DELIMITER = "\r";
- %CMDS = (
- 'INIT' => '',
- 'ON' => 'PWON',
- 'OFF' => 'PWSTANDBY',
- 'STATUS' => 'PW?',
- 'VOL+' => 'MVUP',
- 'VOL-' => 'MVDOWN',
- 'MUTE' => 'MUON',
- 'UNMUTE' => 'MUOFF',
- 'TV' => 'SITV',
- 'DVD' => 'SIDVD',
- 'SURROUND' => 'MSDOLBY DIGITAL',
- 'STEREO' => 'MS7CH STEREO',
- 'TV' => 'SITV',
- 'INPUT' => 'SI?',
- );
- $STATUS_ON = 'PWON';
- } elsif (basename($0) =~ /TV/i) {
- $DEV = 'TV';
- $PORT = '/dev/tty.' . $DEV . '-DevB';
- $BLUETOOTH = 1;
- $CRLF = "\r";
- $DELIMITER = "\r";
- %CMDS = (
- 'INIT' => 'RSPW1',
- 'ON' => 'POWR1',
- 'OFF' => 'POWR0',
- 'STATUS' => 'POWR?',
- 'VOL+' => 'MVUP',
- 'VOL-' => 'MVDOWN',
- 'MUTE' => 'MUTE1',
- 'UNMUTE' => 'MUTE2',
- 'TV' => 'IAVD0',
- 'PLEX' => 'IAVD7',
- 'VOL_CHECK' => 'VOLM?',
- 'VOL6' => 'VOLM6',
- 'VOL12' => 'VOLM12',
- 'VOL24' => 'VOLM24',
- 'VOL+' => 'VOLM',
- 'VOL-' => 'VOLM'
- );
- $STATUS_ON = '1';
- } else {
- die("No device specified\n");
- }
- # App config
- my $DELAY_STATUS = 5;
- my $BYTE_TIMEOUT = 50;
- my $SILENCE_TIMEOUT = $BYTE_TIMEOUT * 10;
- my $MAX_CMD_LEN = 1024;
- my $BT_CHECK = $ENV{'HOME'} . '/bin/btcheck';
- my $TEMP_DIR = `getconf DARWIN_USER_TEMP_DIR`;
- chomp($TEMP_DIR);
- my $DATA_DIR = $TEMP_DIR . 'plexMonitor/';
- my $CMD_FILE = $DATA_DIR . uc($DEV) . '.socket';
- # Debug
- my $DEBUG = 0;
- if ($ENV{'DEBUG'}) {
- $DEBUG = 1;
- print STDERR "Debug enabled\n";
- }
- # Command-line arguments
- my ($DELAY) = @ARGV;
- if (!$DELAY) {
- $DELAY = 15;
- }
- # Sanity check
- if (!-r $PORT || !-d $DATA_DIR) {
- die("Bad config\n");
- }
- # Wait for the serial port to become available
- if ($BLUETOOTH) {
- system($BT_CHECK, $DEV);
- if ($? != 0) {
- sleep($DELAY_STATUS);
- die('Bluetooth device "' . $DEV . "\" not available\n");
- }
- }
- # Socket init
- if (-e $CMD_FILE) {
- unlink($CMD_FILE);
- }
- my $sock = IO::Socket::UNIX->new(
- 'Local' => $CMD_FILE,
- 'Type' => SOCK_DGRAM
- ) or die('Unable to open socket: ' . $CMD_FILE . ": ${@}\n");
- if (!-S $CMD_FILE) {
- die('Failed to create socket: ' . $CMD_FILE . "\n");
- }
- my $select = IO::Select->new($sock)
- or die('Unable to select socket: ' . $CMD_FILE . ": ${!}\n");
- # Port init
- my $port = new Device::SerialPort($PORT)
- or die('Unable to open serial connection: ' . $PORT . ": ${!}\n");
- $port->read_const_time($BYTE_TIMEOUT);
- # Init (clear any previous state)
- sendQuery($port, $CMDS{'INIT'});
- # Track the power state
- # Check for new commands
- my $power = -1;
- my $powerLast = $power;
- my $lastStatus = 0;
- while (1) {
- # Calculate our next timeout
- # Hold on select() but not more than $DELAY_STATUS after our last update
- # Plus 1 because we aren't using hi-res time
- my $timeout = ($lastStatus + $DELAY_STATUS + 1) - time();
- if ($timeout < 1) {
- $timeout = 0;
- }
- if ($DEBUG) {
- print STDERR 'Waiting for commands with timeout: ' . $timeout . "\n";
- }
- # Check for queued commands
- my @ready_clients = $select->can_read($timeout);
- foreach my $fh (@ready_clients) {
- # Grab the inbound text
- my $text = undef();
- $fh->recv($text, $MAX_CMD_LEN);
- $text =~ s/^\s+//;
- $text =~ s/\s+$//;
- if ($DEBUG) {
- print STDERR 'Got command: ' . $text . "\n";
- }
- # Only accept valid commands
- my $cmd = undef();
- foreach my $name (keys(%CMDS)) {
- if ($name eq $text) {
- $cmd = $name;
- last;
- }
- }
- # Send command to serial device
- if ($cmd) {
- if ($DEBUG) {
- print STDERR 'Sending command: ' . $cmd . "\n";
- }
- my $result = sendQuery($port, $CMDS{$cmd});
- if ($DEBUG && $result) {
- print STDERR "\tGot result: " . $result . "\n";
- }
- }
- }
- # Check the power state, but not too frequently
- if (time() > $lastStatus + $DELAY_STATUS) {
- $lastStatus = time();
- $powerLast = $power;
- $power = 0;
- my $result = sendQuery($port, $CMDS{'STATUS'});
- if ($result && $result eq $STATUS_ON) {
- $power = 1;
- }
- # If something has changed, save the state to disk
- if ($powerLast != $power) {
- if ($DEBUG) {
- print STDERR 'New ' . uc($DEV) . ' power state: ' . $power . "\n";
- }
- my ($fh, $tmp) = tempfile($DATA_DIR . uc($DEV) . '.XXXXXXXX', 'UNLINK' => 0);
- print $fh $power . "\n";
- close($fh);
- rename($tmp, $DATA_DIR . uc($DEV));
- }
- }
- }
- # Cleanup
- undef($select);
- close($sock);
- undef($sock);
- $port->close();
- undef($port);
- exit(0);
- sub sendQuery($$) {
- my ($port, $query) = @_;
- # Read until the queue is clear (i.e. no data available)
- # Since we don't have flow control this always causes one read timeout
- $port->lookclear();
- clearBuffer($port);
- # Send the command
- my $bytes = $port->write($query . $CRLF);
- if ($DEBUG) {
- print STDERR "\tWrote (" . $bytes . '): ' . $query . "\n";
- }
- # Wait for a reply (delimited or timeout)
- return collectUntil($port, $DELIMITER);
- }
- sub clearBuffer($) {
- my ($port) = @_;
- my $byte = 1;
- while (length($byte) > 0) {
- $byte = $port->read(1);
- if ($DEBUG && length($byte)) {
- print STDERR "\tIgnored: " . $byte . "\n";
- }
- }
- }
- sub collectUntil($$) {
- my ($port, $char) = @_;
- if (length($char) != 1) {
- die('Invalid collection delimiter: ' . $char . "\n");
- }
- # This byte-by-byte reading is not efficient, but it's safe
- # Allow reading forever as long as we don't exceed the silence timeout
- my $count = 0;
- my $string = '';
- while ($count < $SILENCE_TIMEOUT / $BYTE_TIMEOUT) {
- my $byte = $port->read(1);
- if (length($byte)) {
- $count = 0;
- $string .= $byte;
- if ($DEBUG) {
- print STDERR "\tRead: " . $byte . "\n";
- }
- if ($byte eq $char) {
- last;
- }
- } else {
- $count++;
- }
- }
- # Return undef if there was no data (as opposed to just a delimiter and/or whitespace)
- if (length($string) < 1) {
- if ($DEBUG) {
- print STDERR "Read: <NO DATA>\n";
- }
- return undef();
- }
- # Translate CRLF and CR to LF
- $string =~ s/\r\n/\n/g;
- $string =~ s/\r/\n/g;
- # Strip the trailing delimiter
- $string =~ s/${char}$//;
- # Strip leading or trailing whitespace
- $string =~ s/^\s+//;
- $string =~ s/\s+$//;
- # Return our clean string
- if ($DEBUG) {
- print STDERR 'Read: ' . $string . "\n";
- }
- return $string;
- }
|