|
@@ -1,6 +1,8 @@
|
|
|
#!/usr/bin/perl
|
|
#!/usr/bin/perl
|
|
|
use strict;
|
|
use strict;
|
|
|
use warnings;
|
|
use warnings;
|
|
|
|
|
+use POSIX;
|
|
|
|
|
+use File::Touch;
|
|
|
use Math::Random;
|
|
use Math::Random;
|
|
|
use Time::HiRes qw( usleep );
|
|
use Time::HiRes qw( usleep );
|
|
|
|
|
|
|
@@ -12,12 +14,13 @@ use DMX;
|
|
|
|
|
|
|
|
# Prototypes
|
|
# Prototypes
|
|
|
sub red_alert();
|
|
sub red_alert();
|
|
|
-sub red_flash();
|
|
|
|
|
|
|
+sub rave();
|
|
|
|
|
+sub rave_loop();
|
|
|
|
|
|
|
|
# Effects states
|
|
# Effects states
|
|
|
my %EFFECTS = (
|
|
my %EFFECTS = (
|
|
|
'RED_ALERT' => \&red_alert,
|
|
'RED_ALERT' => \&red_alert,
|
|
|
- 'RED_FLASH' => \&red_flash,
|
|
|
|
|
|
|
+ 'RAVE' => \&rave,
|
|
|
);
|
|
);
|
|
|
|
|
|
|
|
# User config
|
|
# User config
|
|
@@ -61,6 +64,7 @@ chomp($TEMP_DIR);
|
|
|
my $DATA_DIR = $TEMP_DIR . 'plexMonitor/';
|
|
my $DATA_DIR = $TEMP_DIR . 'plexMonitor/';
|
|
|
my $OUTPUT_FILE = $DATA_DIR . 'LED';
|
|
my $OUTPUT_FILE = $DATA_DIR . 'LED';
|
|
|
my $STATE_SOCK = $OUTPUT_FILE . '.socket';
|
|
my $STATE_SOCK = $OUTPUT_FILE . '.socket';
|
|
|
|
|
+my $RAVE_FILE = $DATA_DIR . 'RAVE';
|
|
|
my $PUSH_TIMEOUT = 20;
|
|
my $PUSH_TIMEOUT = 20;
|
|
|
my $PULL_TIMEOUT = 60;
|
|
my $PULL_TIMEOUT = 60;
|
|
|
|
|
|
|
@@ -97,6 +101,7 @@ foreach my $key (keys(%DIM)) {
|
|
|
foreach my $key (keys(%EFFECTS)) {
|
|
foreach my $key (keys(%EFFECTS)) {
|
|
|
$VALID{$key} = 1;
|
|
$VALID{$key} = 1;
|
|
|
}
|
|
}
|
|
|
|
|
+$VALID{'STOP'} = 1;
|
|
|
|
|
|
|
|
# Sockets
|
|
# Sockets
|
|
|
DMX::stateSocket($STATE_SOCK);
|
|
DMX::stateSocket($STATE_SOCK);
|
|
@@ -109,8 +114,11 @@ my %exists = ();
|
|
|
my %existsLast = %exists;
|
|
my %existsLast = %exists;
|
|
|
my $pushLast = 0;
|
|
my $pushLast = 0;
|
|
|
my $pullLast = time();
|
|
my $pullLast = time();
|
|
|
|
|
+my $update = 0;
|
|
|
my @COLOR = ();
|
|
my @COLOR = ();
|
|
|
my $colorChange = time();
|
|
my $colorChange = time();
|
|
|
|
|
+my $PID = undef();
|
|
|
|
|
+my $EFFECT = undef();
|
|
|
|
|
|
|
|
# Always force lights out at launch
|
|
# Always force lights out at launch
|
|
|
DMX::dim({ 'channel' => 13, 'value' => 0, 'time' => 0 });
|
|
DMX::dim({ 'channel' => 13, 'value' => 0, 'time' => 0 });
|
|
@@ -124,31 +132,76 @@ while (1) {
|
|
|
$stateLast = $state;
|
|
$stateLast = $state;
|
|
|
%existsLast = %exists;
|
|
%existsLast = %exists;
|
|
|
|
|
|
|
|
- # Set anywhere to force an update this cycle
|
|
|
|
|
- my $forceUpdate = 0;
|
|
|
|
|
-
|
|
|
|
|
# State is calculated; use newState to gather data
|
|
# State is calculated; use newState to gather data
|
|
|
my $newState = $state;
|
|
my $newState = $state;
|
|
|
|
|
|
|
|
|
|
+ # Reduce the select delay when we're processing background effects
|
|
|
|
|
+ # We don't want to hang waiting for event updates
|
|
|
|
|
+ my $delay = $DELAY;
|
|
|
|
|
+ if (defined($PID)) {
|
|
|
|
|
+ $delay = 0.01;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
# Wait for state updates
|
|
# Wait for state updates
|
|
|
- my $cmdState = DMX::readState($DELAY, \%exists, \%VALID);
|
|
|
|
|
|
|
+ my $cmdState = DMX::readState($delay, \%exists, \%VALID);
|
|
|
if (defined($cmdState)) {
|
|
if (defined($cmdState)) {
|
|
|
$newState = $cmdState;
|
|
$newState = $cmdState;
|
|
|
$pullLast = time();
|
|
$pullLast = time();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ # Handle "STOP" commands
|
|
|
|
|
+ if ($newState eq 'STOP') {
|
|
|
|
|
+ if (defined($PID)) {
|
|
|
|
|
+ if ($DEBUG) {
|
|
|
|
|
+ print STDERR "Ending background processing\n";
|
|
|
|
|
+ }
|
|
|
|
|
+ kill(SIGTERM, $PID);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $newState = $state;
|
|
|
|
|
+ $update = 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Reap zombie children
|
|
|
|
|
+ if (defined($PID)) {
|
|
|
|
|
+ my $kid = waitpid($PID, WNOHANG);
|
|
|
|
|
+ if ($kid > 0) {
|
|
|
|
|
+ if ($DEBUG) {
|
|
|
|
|
+ print STDERR 'Reaped child: ' . $PID . "\n";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $PID = undef();
|
|
|
|
|
+ $EFFECT = undef();
|
|
|
|
|
+
|
|
|
|
|
+ if (-e $RAVE_FILE) {
|
|
|
|
|
+ unlink($RAVE_FILE);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ $update = 1;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Continue special processing
|
|
|
|
|
+ if (defined($EFFECT)) {
|
|
|
|
|
+ $EFFECT->();
|
|
|
|
|
+ next;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
# Special handling for effects states
|
|
# Special handling for effects states
|
|
|
if (defined($EFFECTS{$newState})) {
|
|
if (defined($EFFECTS{$newState})) {
|
|
|
|
|
|
|
|
# Dispatch the handler
|
|
# Dispatch the handler
|
|
|
- $EFFECTS{$newState}->();
|
|
|
|
|
|
|
+ # Optionally skip the rest of this loop
|
|
|
|
|
+ if($EFFECTS{$newState}->()) {
|
|
|
|
|
+ next;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
# Force an update back to the original state
|
|
# Force an update back to the original state
|
|
|
$newState = $stateLast;
|
|
$newState = $stateLast;
|
|
|
%exists = %existsLast;
|
|
%exists = %existsLast;
|
|
|
@COLOR = ();
|
|
@COLOR = ();
|
|
|
$colorChange = time() + $COLOR_TIME_MIN;
|
|
$colorChange = time() + $COLOR_TIME_MIN;
|
|
|
- $forceUpdate = 1;
|
|
|
|
|
|
|
+ $update = 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# Calculate the new state
|
|
# Calculate the new state
|
|
@@ -182,17 +235,11 @@ while (1) {
|
|
|
my @vals = random_normal($numChans, $max, $max * $COLOR_VAR{$state});
|
|
my @vals = random_normal($numChans, $max, $max * $COLOR_VAR{$state});
|
|
|
foreach my $data (@{ $DIM{$state} }) {
|
|
foreach my $data (@{ $DIM{$state} }) {
|
|
|
my $color = pop(@vals);
|
|
my $color = pop(@vals);
|
|
|
- $color = int($color);
|
|
|
|
|
- if ($color < 0) {
|
|
|
|
|
- $color = 0;
|
|
|
|
|
- } elsif ($color > 255) {
|
|
|
|
|
- $color = 255;
|
|
|
|
|
- }
|
|
|
|
|
push(@COLOR, { 'channel' => $data->{'channel'}, 'value' => $color, 'time' => $time });
|
|
push(@COLOR, { 'channel' => $data->{'channel'}, 'value' => $color, 'time' => $time });
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# Update
|
|
# Update
|
|
|
- $forceUpdate = 1;
|
|
|
|
|
|
|
+ $update = 1;
|
|
|
$colorChange = time();
|
|
$colorChange = time();
|
|
|
if ($DEBUG) {
|
|
if ($DEBUG) {
|
|
|
print STDERR "New color\n";
|
|
print STDERR "New color\n";
|
|
@@ -201,7 +248,7 @@ while (1) {
|
|
|
|
|
|
|
|
# Force updates on a periodic basis
|
|
# Force updates on a periodic basis
|
|
|
if (time() - $pushLast > $PUSH_TIMEOUT) {
|
|
if (time() - $pushLast > $PUSH_TIMEOUT) {
|
|
|
- $forceUpdate = 1;
|
|
|
|
|
|
|
+ $update = 1;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# Die if we don't see regular updates
|
|
# Die if we don't see regular updates
|
|
@@ -211,20 +258,18 @@ while (1) {
|
|
|
|
|
|
|
|
# Force updates on any state change
|
|
# Force updates on any state change
|
|
|
if ($stateLast ne $state) {
|
|
if ($stateLast ne $state) {
|
|
|
- $forceUpdate = 1;
|
|
|
|
|
|
|
+ if ($DEBUG) {
|
|
|
|
|
+ print STDERR 'State change: ' . $stateLast . ' => ' . $state . "\n";
|
|
|
|
|
+ }
|
|
|
|
|
+ $update = 1;
|
|
|
|
|
|
|
|
# Reset the color change sequence, so we always spend 1 cycle at white
|
|
# Reset the color change sequence, so we always spend 1 cycle at white
|
|
|
@COLOR = ();
|
|
@COLOR = ();
|
|
|
$colorChange = time() + $COLOR_TIME_MIN;
|
|
$colorChange = time() + $COLOR_TIME_MIN;
|
|
|
-
|
|
|
|
|
- # Debug
|
|
|
|
|
- if ($DEBUG) {
|
|
|
|
|
- print STDERR 'State change: ' . $stateLast . ' => ' . $state . "\n";
|
|
|
|
|
- }
|
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
# Update the lighting
|
|
# Update the lighting
|
|
|
- if ($forceUpdate) {
|
|
|
|
|
|
|
+ if ($update) {
|
|
|
|
|
|
|
|
# Select a data set (color or standard)
|
|
# Select a data set (color or standard)
|
|
|
my @data_set = ();
|
|
my @data_set = ();
|
|
@@ -241,6 +286,15 @@ while (1) {
|
|
|
|
|
|
|
|
# Update the push time
|
|
# Update the push time
|
|
|
$pushLast = time();
|
|
$pushLast = time();
|
|
|
|
|
+
|
|
|
|
|
+ # Clear the update flag
|
|
|
|
|
+ $update = 0;
|
|
|
|
|
+
|
|
|
|
|
+ # Clear the RAVE file, just in case
|
|
|
|
|
+ if (-e $RAVE_FILE) {
|
|
|
|
|
+ unlink($RAVE_FILE);
|
|
|
|
|
+ die("Cleared orphan RAVE file\n");
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -249,7 +303,11 @@ while (1) {
|
|
|
# These are blocking, so be careful
|
|
# These are blocking, so be careful
|
|
|
# ======================================
|
|
# ======================================
|
|
|
sub red_alert() {
|
|
sub red_alert() {
|
|
|
- my $ramp = 500;
|
|
|
|
|
|
|
+ if ($DEBUG) {
|
|
|
|
|
+ print STDERR "red_alert()\n";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ my $ramp = 450;
|
|
|
my @sound = ('afplay', '/mnt/media/Sounds/DMX/Red Alert.mp3');
|
|
my @sound = ('afplay', '/mnt/media/Sounds/DMX/Red Alert.mp3');
|
|
|
my $sleep = $ramp;
|
|
my $sleep = $ramp;
|
|
|
|
|
|
|
@@ -280,6 +338,63 @@ sub red_alert() {
|
|
|
usleep($sleep * 1000);
|
|
usleep($sleep * 1000);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-sub red_flash() {
|
|
|
|
|
|
|
+sub rave() {
|
|
|
|
|
+ if ($DEBUG) {
|
|
|
|
|
+ print STDERR "rave()\n";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ my @sound = ('afplay', '/mnt/media/Sounds/DMX/Rave.mp3');
|
|
|
|
|
+
|
|
|
|
|
+ # Play the sound in a child (i.e. in the background)
|
|
|
|
|
+ $PID = fork();
|
|
|
|
|
+ if (defined($PID) && $PID == 0) {
|
|
|
|
|
+ exec(@sound)
|
|
|
|
|
+ or die('Unable to play sound: ' . join(' ', @sound) . "\n");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Setup our loop handler
|
|
|
|
|
+ $EFFECT = \&rave_loop;
|
|
|
|
|
+
|
|
|
|
|
+ # Initiate the RAVE state
|
|
|
|
|
+ touch($RAVE_FILE);
|
|
|
|
|
+
|
|
|
|
|
+ # Do not pass GO, do not collect $200
|
|
|
|
|
+ return 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+sub rave_loop() {
|
|
|
|
|
+ if ($DEBUG) {
|
|
|
|
|
+ print STDERR "rave_loop()\n";
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ my $max_dur = 500;
|
|
|
|
|
+ my $max_val = 255;
|
|
|
|
|
+ my %channels = (
|
|
|
|
|
+ 1 => 1,
|
|
|
|
|
+ 2 => 1,
|
|
|
|
|
+ 4 => 1,
|
|
|
|
|
+ 5 => 1,
|
|
|
|
|
+ 6 => 1,
|
|
|
|
|
+ 7 => 1,
|
|
|
|
|
+ 8 => 1,
|
|
|
|
|
+ 9 => 1,
|
|
|
|
|
+ 13 => 1,
|
|
|
|
|
+ 14 => 1,
|
|
|
|
|
+ 15 => 1,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ # Random data for each channel
|
|
|
|
|
+ my @data_set = ();
|
|
|
|
|
+ foreach my $chan (keys(%channels)) {
|
|
|
|
|
+ my $val = int(rand($max_val));
|
|
|
|
|
+ my $dur = int(rand($max_dur));
|
|
|
|
|
+ push(@data_set, { 'channel' => $chan, 'value' => $val, 'time' => $dur });
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ # Push the data set
|
|
|
|
|
+ DMX::applyDataset(\@data_set, 'RAVE', $OUTPUT_FILE);
|
|
|
|
|
+ $pushLast = time();
|
|
|
|
|
|
|
|
|
|
+ # Wait just a bit, to prevent seizures
|
|
|
|
|
+ usleep($max_dur * 1000);
|
|
|
}
|
|
}
|