|
@@ -1,8 +1,8 @@
|
|
|
#!/usr/bin/perl
|
|
#!/usr/bin/perl
|
|
|
use strict;
|
|
use strict;
|
|
|
use warnings;
|
|
use warnings;
|
|
|
-use IO::Socket;
|
|
|
|
|
-use Time::HiRes qw( usleep );
|
|
|
|
|
|
|
+use IO::Select;
|
|
|
|
|
+use IO::Socket::UNIX;
|
|
|
use File::Temp qw( tempfile );
|
|
use File::Temp qw( tempfile );
|
|
|
|
|
|
|
|
# Prototypes
|
|
# Prototypes
|
|
@@ -23,8 +23,12 @@ my $SOCK_TIMEOUT = 5;
|
|
|
my $TEMP_DIR = `getconf DARWIN_USER_TEMP_DIR`;
|
|
my $TEMP_DIR = `getconf DARWIN_USER_TEMP_DIR`;
|
|
|
chomp($TEMP_DIR);
|
|
chomp($TEMP_DIR);
|
|
|
my $DATA_DIR = $TEMP_DIR . 'plexMonitor/';
|
|
my $DATA_DIR = $TEMP_DIR . 'plexMonitor/';
|
|
|
-my $CMD_FILE = $DATA_DIR . 'DMX.socket';
|
|
|
|
|
|
|
+my $DMX_SOCK = $DATA_DIR . 'DMX.socket';
|
|
|
|
|
+my $SUB_SOCK = $DATA_DIR . 'STATE.socket';
|
|
|
|
|
+my $STATE_SOCK = $DATA_DIR . 'ROPE.socket';
|
|
|
|
|
+my $MAX_CMD_LEN = 1024;
|
|
|
my $PUSH_TIMEOUT = 20;
|
|
my $PUSH_TIMEOUT = 20;
|
|
|
|
|
+my $PULL_TIMEOUT = $PUSH_TIMEOUT * 3;
|
|
|
|
|
|
|
|
# Debug
|
|
# Debug
|
|
|
my $DEBUG = 0;
|
|
my $DEBUG = 0;
|
|
@@ -35,28 +39,49 @@ if ($ENV{'DEBUG'}) {
|
|
|
# Command-line arguments
|
|
# Command-line arguments
|
|
|
my ($DELAY) = @ARGV;
|
|
my ($DELAY) = @ARGV;
|
|
|
if (!$DELAY) {
|
|
if (!$DELAY) {
|
|
|
- $DELAY = 0.5;
|
|
|
|
|
|
|
+ $DELAY = $PULL_TIMEOUT / 2;
|
|
|
}
|
|
}
|
|
|
-$DELAY *= 1000000; # Microseconds;
|
|
|
|
|
|
|
|
|
|
# Sanity check
|
|
# Sanity check
|
|
|
-if (!-d $DATA_DIR || !-S $CMD_FILE) {
|
|
|
|
|
|
|
+if (!-d $DATA_DIR || !-S $DMX_SOCK || !-S $SUB_SOCK) {
|
|
|
die("Bad config\n");
|
|
die("Bad config\n");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-# Socket init
|
|
|
|
|
-my $sock = IO::Socket::UNIX->new(
|
|
|
|
|
- 'Peer' => $CMD_FILE,
|
|
|
|
|
|
|
+# State socket init
|
|
|
|
|
+if (-e $STATE_SOCK) {
|
|
|
|
|
+ unlink($STATE_SOCK);
|
|
|
|
|
+}
|
|
|
|
|
+my $state_fh = IO::Socket::UNIX->new(
|
|
|
|
|
+ 'Local' => $STATE_SOCK,
|
|
|
|
|
+ 'Type' => SOCK_DGRAM
|
|
|
|
|
+) or die('Unable to open socket: ' . $STATE_SOCK . ": ${@}\n");
|
|
|
|
|
+my $select = IO::Select->new($state_fh)
|
|
|
|
|
+ or die('Unable to select socket: ' . $STATE_SOCK . ": ${!}\n");
|
|
|
|
|
+
|
|
|
|
|
+# DMX socket init
|
|
|
|
|
+my $dmx_fh = IO::Socket::UNIX->new(
|
|
|
|
|
+ 'Peer' => $DMX_SOCK,
|
|
|
'Type' => SOCK_DGRAM,
|
|
'Type' => SOCK_DGRAM,
|
|
|
'Timeout' => $SOCK_TIMEOUT
|
|
'Timeout' => $SOCK_TIMEOUT
|
|
|
-) or die('Unable to open socket: ' . $CMD_FILE . ": ${@}\n");
|
|
|
|
|
|
|
+) or die('Unable to open socket: ' . $DMX_SOCK . ": ${@}\n");
|
|
|
|
|
+
|
|
|
|
|
+# Subscribe to state updates
|
|
|
|
|
+my $sub_fh = IO::Socket::UNIX->new(
|
|
|
|
|
+ 'Peer' => $SUB_SOCK,
|
|
|
|
|
+ 'Type' => SOCK_DGRAM,
|
|
|
|
|
+ 'Timeout' => $SOCK_TIMEOUT
|
|
|
|
|
+) or die('Unable to open socket: ' . $SUB_SOCK . ": ${@}\n");
|
|
|
|
|
+$sub_fh->send($STATE_SOCK)
|
|
|
|
|
+ or die('Unable to subscribe: ' . $! . "\n");
|
|
|
|
|
+shutdown($sub_fh, 2);
|
|
|
|
|
+undef($sub_fh);
|
|
|
|
|
|
|
|
# State
|
|
# State
|
|
|
my $state = 'INIT';
|
|
my $state = 'INIT';
|
|
|
my $stateLast = $state;
|
|
my $stateLast = $state;
|
|
|
-my $fan = 0;
|
|
|
|
|
-my $projector = 0;
|
|
|
|
|
|
|
+my %exists = ();
|
|
|
my $pushLast = 0;
|
|
my $pushLast = 0;
|
|
|
|
|
+my $pullLast = time();
|
|
|
|
|
|
|
|
# Always force lights out at launch
|
|
# Always force lights out at launch
|
|
|
dim({ 'channel' => 11, 'value' => 0, 'time' => 0 });
|
|
dim({ 'channel' => 11, 'value' => 0, 'time' => 0 });
|
|
@@ -67,47 +92,52 @@ while (1) {
|
|
|
# Set anywhere to force an update this cycle
|
|
# Set anywhere to force an update this cycle
|
|
|
my $forceUpdate = 0;
|
|
my $forceUpdate = 0;
|
|
|
|
|
|
|
|
- # Monitor the PROJECTOR file for state
|
|
|
|
|
- {
|
|
|
|
|
- $projector = 0;
|
|
|
|
|
- my $fh;
|
|
|
|
|
- open($fh, $DATA_DIR . 'PROJECTOR')
|
|
|
|
|
- or die("Unable to open PROJECTOR\n");
|
|
|
|
|
- my $text = <$fh>;
|
|
|
|
|
- close($fh);
|
|
|
|
|
- if ($text =~ /1/) {
|
|
|
|
|
- $projector = 1;
|
|
|
|
|
- }
|
|
|
|
|
- if ($DEBUG) {
|
|
|
|
|
- print STDERR 'Projector: ' . $projector . "\n";
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ # State is calculated; use newState to gather data
|
|
|
|
|
+ my $newState = $state;
|
|
|
|
|
|
|
|
- # Monitor the FAN_CMD file for presence
|
|
|
|
|
- {
|
|
|
|
|
|
|
+ # Wait for state updates
|
|
|
|
|
+ my @ready_clients = $select->can_read($DELAY);
|
|
|
|
|
+ foreach my $fh (@ready_clients) {
|
|
|
|
|
|
|
|
- # Clear the flag when the projector is off
|
|
|
|
|
- if ($fan && !$projector) {
|
|
|
|
|
- unlink($DATA_DIR . 'FAN_CMD');
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ # Grab the inbound text
|
|
|
|
|
+ my $text = undef();
|
|
|
|
|
+ $fh->recv($text, $MAX_CMD_LEN);
|
|
|
|
|
|
|
|
- $fan = 0;
|
|
|
|
|
- if (-e $DATA_DIR . 'FAN_CMD') {
|
|
|
|
|
- $fan = 1;
|
|
|
|
|
|
|
+ # Parse the string
|
|
|
|
|
+ %exists = ();
|
|
|
|
|
+ my ($cmdState, $exists_text) = $text =~ /^(\w+)\s+\(([^\)]+)\)/;
|
|
|
|
|
+ if (!defined($cmdState) || !defined($exists_text)) {
|
|
|
|
|
+ print STDERR 'State parse error: ' . $text . "\n";
|
|
|
|
|
+ next;
|
|
|
|
|
+ }
|
|
|
|
|
+ foreach my $exists (split(/\s*,\s*/, $exists_text)) {
|
|
|
|
|
+ my ($name, $value) = $exists =~ /(\w+)\:(0|1)/;
|
|
|
|
|
+ if (!defined($name) || !defined($value)) {
|
|
|
|
|
+ print STDERR 'State parse error (exists): ' . $text . "\n";
|
|
|
|
|
+ next;
|
|
|
|
|
+ }
|
|
|
|
|
+ $exists{$name} = $value;
|
|
|
}
|
|
}
|
|
|
if ($DEBUG) {
|
|
if ($DEBUG) {
|
|
|
- print STDERR 'Fan: ' . $fan . "\n";
|
|
|
|
|
|
|
+ my @exists_tmp = ();
|
|
|
|
|
+ foreach my $key (keys(%exists)) {
|
|
|
|
|
+ push(@exists_tmp, $key . ':' . $exists{$key});
|
|
|
|
|
+ }
|
|
|
|
|
+ print STDERR 'Got state: ' . $cmdState . ' (' . join(', ', @exists_tmp) . ")\n";
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ # We don't really care about the master state for the fan
|
|
|
|
|
+ # But we have to parse the exists records, so we might as well grab it
|
|
|
|
|
+
|
|
|
|
|
+ # Propogate the most recent command state
|
|
|
|
|
+ $newState = $cmdState;
|
|
|
|
|
+ $pullLast = time();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# Calculate the new state
|
|
# Calculate the new state
|
|
|
$stateLast = $state;
|
|
$stateLast = $state;
|
|
|
- if ($fan) {
|
|
|
|
|
- if ($projector) {
|
|
|
|
|
- $state = 'ON';
|
|
|
|
|
- } else {
|
|
|
|
|
- $state = 'OFF';
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if ($exists{'FAN_CMD'}) {
|
|
|
|
|
+ $state = 'ON';
|
|
|
} else {
|
|
} else {
|
|
|
$state = 'OFF';
|
|
$state = 'OFF';
|
|
|
}
|
|
}
|
|
@@ -120,6 +150,11 @@ while (1) {
|
|
|
$forceUpdate = 1;
|
|
$forceUpdate = 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ # Die if we don't see regular updates
|
|
|
|
|
+ if (time() - $pullLast > $PULL_TIMEOUT) {
|
|
|
|
|
+ die('No update on state socket in past ' . $PULL_TIMEOUT . " seconds. Exiting...\n");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
# Update the fan state
|
|
# Update the fan state
|
|
|
if ($forceUpdate || $stateLast ne $state) {
|
|
if ($forceUpdate || $stateLast ne $state) {
|
|
|
if ($DEBUG) {
|
|
if ($DEBUG) {
|
|
@@ -145,9 +180,6 @@ while (1) {
|
|
|
# Update the push time
|
|
# Update the push time
|
|
|
$pushLast = time();
|
|
$pushLast = time();
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- # Wait and loop
|
|
|
|
|
- usleep($DELAY);
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# Send the command
|
|
# Send the command
|
|
@@ -161,6 +193,6 @@ sub dim($) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
my $cmd = join(':', $args->{'channel'}, $args->{'time'}, $args->{'value'}, $args->{'delay'});
|
|
my $cmd = join(':', $args->{'channel'}, $args->{'time'}, $args->{'value'}, $args->{'delay'});
|
|
|
- $sock->send($cmd)
|
|
|
|
|
- or die('Unable to write command to socket: ' . $CMD_FILE . ': ' . $cmd . ": ${!}\n");
|
|
|
|
|
|
|
+ $dmx_fh->send($cmd)
|
|
|
|
|
+ or die('Unable to write command to socket: ' . $DMX_SOCK . ': ' . $cmd . ": ${!}\n");
|
|
|
}
|
|
}
|