Zachary Kotlarek 13 лет назад
Родитель
Сommit
3ee1c3ea03
4 измененных файлов с 237 добавлено и 5 удалено
  1. 205 0
      dmx/bias.pl
  2. 30 0
      dmx/launchd/com.uberzach.ola.bias.plist
  3. 2 1
      dmx/reset.sh
  4. 0 4
      dmx/rope.pl

+ 205 - 0
dmx/bias.pl

@@ -0,0 +1,205 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use IO::Select;
+use IO::Socket::UNIX;
+use File::Temp qw( tempfile );
+
+# Prototypes
+sub dim($);
+
+# User config
+my %DIM = (
+	'OFF'    => [
+		# Handled by rope.pl
+	],
+	'PLAY'   => [
+		{ 'channel' => 12, 'value' => 255, 'time' => 0    }
+	],
+	'PAUSE'   => [
+		{ 'channel' => 12, 'value' => 0,  'time' => 12000 }
+	],
+	'MOTION'  => [
+		{ 'channel' => 12, 'value' => 0,  'time' => 0    }
+	],
+);
+
+# App config
+my $SOCK_TIMEOUT = 5;
+my $TEMP_DIR     = `getconf DARWIN_USER_TEMP_DIR`;
+chomp($TEMP_DIR);
+my $DATA_DIR     = $TEMP_DIR . 'plexMonitor/';
+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 $PULL_TIMEOUT = $PUSH_TIMEOUT * 3;
+
+# Debug
+my $DEBUG = 0;
+if ($ENV{'DEBUG'}) {
+	$DEBUG = 1;
+}
+
+# Command-line arguments
+my ($DELAY) = @ARGV;
+if (!$DELAY) {
+	$DELAY = $PULL_TIMEOUT / 2;
+}
+
+# Sanity check
+if (!-d $DATA_DIR || !-S $DMX_SOCK || !-S $SUB_SOCK) {
+	die("Bad config\n");
+}
+
+# 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,
+	'Timeout' => $SOCK_TIMEOUT
+) 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
+my $state     = 'INIT';
+my $stateLast = $state;
+my %exists    = ();
+my $pushLast  = 0;
+my $pullLast  = time();
+
+# Always force lights out at launch
+dim({ 'channel' => 12, 'value' => 0, 'time' => 0 });
+
+# Loop forever
+while (1) {
+
+	# Set anywhere to force an update this cycle
+	my $forceUpdate = 0;
+
+	# State is calculated; use newState to gather data
+	my $newState = $state;
+
+	# Wait for state updates
+	my @ready_clients = $select->can_read($DELAY);
+	foreach my $fh (@ready_clients) {
+
+		# Grab the inbound text
+		my $text = undef();
+		$fh->recv($text, $MAX_CMD_LEN);
+
+		# 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) {
+			my @exists_tmp = ();
+			foreach my $key (keys(%exists)) {
+				push(@exists_tmp, $key . ':' . $exists{$key});
+			}
+			print STDERR 'Got state: ' . $cmdState . ' (' . join(', ', @exists_tmp) . ")\n";
+		}
+
+		# Translate INIT to OFF
+		if ($cmdState eq 'INIT') {
+			$cmdState = 'OFF';
+		}
+
+		# Only accept valid states
+		if (!defined($DIM{$cmdState})) {
+			print STDERR 'Invalid state: ' . $cmdState . "\n";
+			next;
+		}
+
+		# Propogate the most recent command state
+		$newState = $cmdState;
+		$pullLast = time();
+	}
+
+	# Accept the new state directly
+	$stateLast = $state;
+	$state = $newState;
+
+	# Force updates on a periodic basis
+	if (time() - $pushLast > $PUSH_TIMEOUT) {
+		$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
+	if ($forceUpdate || $stateLast ne $state) {
+		if ($DEBUG) {
+			print STDERR 'State: ' . $stateLast . ' => ' . $state . "\n";
+			foreach my $data (@{ $DIM{$state} }) {
+				print STDERR "\t" . $data->{'channel'} . ' => ' . $data->{'value'} . ' @ ' . $data->{'time'} . "\n";
+			}
+		}
+
+		# Send the dim command
+		my @values = ();
+		foreach my $data (@{ $DIM{$state} }) {
+			dim($data);
+			push(@values, $data->{'channel'} . ' => ' . $data->{'value'} . ' @ ' . $data->{'time'});
+		}
+
+		# Save the state and value to disk
+		my ($fh, $tmp) = tempfile($DATA_DIR . 'BIAS.XXXXXXXX', 'UNLINK' => 0);
+		print $fh 'State: ' . $state . "\n" . join("\n", @values) . "\n";
+		close($fh);
+		rename($tmp, $DATA_DIR . 'BIAS');
+
+		# Update the push time
+		$pushLast = time();
+	}
+}
+
+# Send the command
+sub dim($) {
+	my ($args) = @_;
+	if (!defined($args->{'delay'})) {
+		$args->{'delay'} = 0;
+	}
+	if (!defined($args->{'channel'}) || !defined($args->{'time'}) || !defined($args->{'value'})) {
+		die('Invalid command for socket: ' . join(', ', keys(%{$args})) . ': ' . join(', ', values(%{$args})) . "\n");
+	}
+
+	my $cmd = join(':', $args->{'channel'}, $args->{'time'}, $args->{'value'}, $args->{'delay'});
+	$dmx_fh->send($cmd)
+	  or die('Unable to write command to socket: ' . $DMX_SOCK . ': ' . $cmd . ": ${!}\n");
+}

+ 30 - 0
dmx/launchd/com.uberzach.ola.bias.plist

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>UserName</key>
+	<string>tv</string>
+	<key>LimitLoadToHosts</key>
+	<array>
+		<string>heady.uberzach.com</string>
+	</array>
+	<key>RunAtLoad</key>
+	<true/>
+	<key>Label</key>
+	<string>com.uberzach.ola.bias</string>
+	<key>KeepAlive</key>
+	<dict>
+		<key>OtherJobEnabled</key>
+		<dict>
+			<key>com.uberzach.ola.dmx</key>
+			<true/>
+			<key>com.uberzach.state</key>
+			<true/>
+		</dict>
+	</dict>
+	<key>ProgramArguments</key>
+	<array>
+		<string>/Users/tv/bin/video/dmx/bias.pl</string>
+	</array>
+</dict>
+</plist>

+ 2 - 1
dmx/reset.sh

@@ -2,9 +2,10 @@
 
 ROPE="`ps -A -o pid=,command= | grep -v grep | grep rope.pl | awk '{print $1}'`"
 LEDS="`ps -A -o pid=,command= | grep -v grep | grep leds.pl | awk '{print $1}'`"
+BIAS="`ps -A -o pid=,command= | grep -v grep | grep bias.pl | awk '{print $1}'`"
 FAN="`ps -A -o pid=,command= | grep -v grep | grep fan.pl | awk '{print $1}'`"
 
-for i in $ROPE $LEDS $FAN; do
+for i in $ROPE $LEDS $FAN $BIAS; do
 	if [ -n "${i}" ]; then
 		kill $i
 	fi

+ 0 - 4
dmx/rope.pl

@@ -23,7 +23,6 @@ my %DIM = (
 		{ 'channel' => 7,  'value' => 32,  'time' => 500   },
 		{ 'channel' => 8,  'value' => 16,  'time' => 500   },
 		{ 'channel' => 9,  'value' => 16,  'time' => 500   },
-		{ 'channel' => 12, 'value' => 255, 'time' => 0     },
 	],
 	'PLAY_HIGH' => [
 		{ 'channel' => 1,  'value' => 255, 'time' => 500   },
@@ -35,7 +34,6 @@ my %DIM = (
 		{ 'channel' => 7,  'value' => 128, 'time' => 500   },
 		{ 'channel' => 8,  'value' => 16,  'time' => 500   },
 		{ 'channel' => 9,  'value' => 96,  'time' => 500   },
-		{ 'channel' => 12, 'value' => 255, 'time' => 0     },
 	],
 	'PAUSE'     => [
 		{ 'channel' => 1,  'value' => 255, 'time' => 1000  },
@@ -47,7 +45,6 @@ my %DIM = (
 		{ 'channel' => 7,  'value' => 192, 'time' => 10000 },
 		{ 'channel' => 8,  'value' => 96,  'time' => 6000, 'delay' => 6000 },
 		{ 'channel' => 9,  'value' => 96,  'time' => 6000, 'delay' => 3000 },
-		{ 'channel' => 12, 'value' => 0,   'time' => 10000 },
 	],
 	'MOTION'    => [
 		{ 'channel' => 1,  'value' => 255, 'time' => 1000  },
@@ -59,7 +56,6 @@ my %DIM = (
 		{ 'channel' => 7,  'value' => 192, 'time' => 1000  },
 		{ 'channel' => 8,  'value' => 96,  'time' => 2000  },
 		{ 'channel' => 9,  'value' => 96,  'time' => 1500  },
-		{ 'channel' => 12, 'value' => 0,   'time' => 0     },
 	],
 );