Jelajahi Sumber

Segregate state tracking from light control -- step 1

Zachary Kotlarek 13 tahun lalu
induk
melakukan
18ac57d59c
2 mengubah file dengan 258 tambahan dan 0 penghapusan
  1. 29 0
      dmx/launchd/com.uberzach.state.plist
  2. 229 0
      dmx/state.pl

+ 29 - 0
dmx/launchd/com.uberzach.state.plist

@@ -0,0 +1,29 @@
+<?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.state</string>
+	<key>KeepAlive</key>
+	<dict>
+		<key>OtherJobEnabled</key>
+		<dict>
+			<key>com.uberzach.projector</key>
+			<true/>
+		</dict>
+	</dict>
+	<key>ProgramArguments</key>
+	<array>
+		<string>/Users/tv/bin/video/dmx/state.pl</string>
+		<string>0.25</string>
+	</array>
+</dict>
+</plist>

+ 229 - 0
dmx/state.pl

@@ -0,0 +1,229 @@
+#!/usr/bin/perl
+use strict;
+use warnings;
+use IO::Select;
+use IO::Socket::UNIX;
+use File::Temp qw( tempfile );
+
+# Prototypes
+sub mtime($);
+
+# User config
+my $STATE_TIMEOUT = 180;
+
+# App config
+my $SOCK_TIMEOUT = 5;
+my $TEMP_DIR     = `getconf DARWIN_USER_TEMP_DIR`;
+chomp($TEMP_DIR);
+my $DATA_DIR    = $TEMP_DIR . 'plexMonitor/';
+my $CMD_FILE    = $DATA_DIR . 'STATE.socket';
+my $MAX_CMD_LEN = 4096;
+
+# Debug
+my $DEBUG = 0;
+if ($ENV{'DEBUG'}) {
+	$DEBUG = 1;
+}
+
+# Command-line arguments
+my ($DELAY) = @ARGV;
+if (!$DELAY) {
+	$DELAY = 0.5;
+}
+
+# Sanity check
+if (!-d $DATA_DIR) {
+	die("Bad config\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");
+
+# Subscribers
+my @subscribers = ();
+
+# State
+my $state      = 'INIT';
+my $stateLast  = $state;
+my $playing    = 0;
+my $projector  = 0;
+my $updateLast = 0;
+
+# Loop forever
+while (1) {
+
+	# Check for queued commands
+	my @ready_clients = $select->can_read($DELAY);
+	foreach my $fh (@ready_clients) {
+
+		# Grab the inbound text
+		my $path = undef();
+		$fh->recv($path, $MAX_CMD_LEN);
+		$path =~ s/^\s+//;
+		$path =~ s/\s+$//;
+		if ($DEBUG) {
+			print STDERR 'Got path: ' . $path . "\n";
+		}
+
+		# Only accept valid paths
+		if (!-r $path) {
+			print STDERR 'Invalid socket path: ' . $path . "\n";
+			next;
+		}
+
+		# Open the new socket
+		my $sub = IO::Socket::UNIX->new(
+			'Peer'    => $path,
+			'Type'    => SOCK_DGRAM,
+			'Timeout' => $SOCK_TIMEOUT
+		);
+		if (!$sub) {
+			print STDERR 'Unable to open socket: ' . $path . ": ${@}\n";
+			next;
+		}
+
+		# Add the socket to our subscriber list
+		my %tmp = (
+			'path'   => $path,
+			'socket' => $sub,
+		);
+		push(@subscribers, \%tmp);
+	}
+
+	# Monitor the PLAY_STATUS file for changes and state
+	{
+		my $mtime = mtime($DATA_DIR . 'PLAY_STATUS');
+		if ($mtime > $updateLast) {
+			$updateLast = $mtime;
+		}
+
+		# Grab the PLAY_STATUS value
+		$playing = 0;
+		my $fh;
+		open($fh, $DATA_DIR . 'PLAY_STATUS')
+		  or die("Unable to open PLAY_STATUS\n");
+		my $text = <$fh>;
+		close($fh);
+		if ($text =~ /1/) {
+			$playing = 1;
+		}
+		if ($DEBUG) {
+			print STDERR 'Playing: ' . $playing . "\n";
+		}
+	}
+
+	# Monitor the PROJECTOR file for changes and state
+	{
+		my $mtime = mtime($DATA_DIR . 'PROJECTOR');
+		if ($mtime > $updateLast) {
+			$updateLast = $mtime;
+		}
+
+		# Grab the PROJECTOR value
+		$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";
+		}
+	}
+
+	# Monitor the GUI, PLAYING, and MOTION files for changes only
+	{
+		my $mtime = mtime($DATA_DIR . 'PLAYING');
+		if ($mtime > $updateLast) {
+			$updateLast = $mtime;
+		}
+		$mtime = mtime($DATA_DIR . 'GUI');
+		if ($mtime > $updateLast) {
+			$updateLast = $mtime;
+		}
+		$mtime = mtime($DATA_DIR . 'MOTION');
+		if ($mtime > $updateLast) {
+			$updateLast = $mtime;
+		}
+	}
+
+	# Calculate the new state
+	$stateLast = $state;
+	if ($projector) {
+
+		# We are always either playing or paused if the projector is on
+		if ($playing) {
+			$state = 'PLAY';
+		} else {
+			$state = 'PAUSE';
+		}
+
+	} else {
+
+		# If the projector is off, check the timeouts
+		my $timeSinceUpdate = time() - $updateLast;
+		if ($timeSinceUpdate > $STATE_TIMEOUT) {
+			$state = 'OFF';
+		} elsif ($timeSinceUpdate < $STATE_TIMEOUT) {
+			$state = 'MOTION';
+		}
+	}
+	if ($state eq 'INIT') {
+		$state = 'OFF';
+	}
+
+	# Update the state
+	if ($stateLast ne $state) {
+		if ($DEBUG) {
+			print STDERR 'State: ' . $stateLast . ' => ' . $state . "\n";
+		}
+
+		# Send notifications to all subscribers
+		foreach my $sub (@subscribers) {
+
+			# Drop subscribers that are not available
+			if (!$sub->{'socket'}->send($state)) {
+				print STDERR 'Unable to write state to socket: ' . $sub->{'path'} . ': ' . $state . ": ${!}\n";
+				print STDERR 'Dropping bad socket from subscriber list: ' . $sub->{'path'} . "\n";
+
+				my @new_subscribers = ();
+				foreach my $new (@subscribers) {
+					if ($new->{'socket'} eq $sub->{'socket'}) {
+						next;
+					}
+					push(@new_subscribers, $new);
+				}
+				@subscribers = @new_subscribers;
+			}
+		}
+
+		# Save the state and value to disk
+		my ($fh, $tmp) = tempfile($DATA_DIR . 'STATE.XXXXXXXX', 'UNLINK' => 0);
+		print $fh $state . "\n";
+		close($fh);
+		rename($tmp, $DATA_DIR . 'STATE');
+	}
+}
+
+sub mtime($) {
+	my ($file) = @_;
+	my $mtime = 0;
+	if (-r $file) {
+		(undef(), undef(), undef(), undef(), undef(), undef(), undef(), undef(), undef(), $mtime, undef(), undef(), undef()) = stat($file);
+	}
+	return $mtime;
+}