rope.pl 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use IO::Socket;
  5. use Time::HiRes qw( usleep );
  6. use File::Temp qw( tempfile );
  7. # Prototypes
  8. sub mtime($);
  9. sub dim($$$);
  10. # Channels:
  11. # 0 => All channels
  12. # 1 => Couch-side hidden
  13. # 2 => Couch-side exposed
  14. # 3 => Stairs
  15. # 4 => <Unused>
  16. # 5 => Bar-side hidden
  17. # 6 => Bar-side bar-shelves
  18. # 7 => Bar-side exposed
  19. # 8 => Bar-side ceiling
  20. # User config
  21. my %DIM = (
  22. 'OFF' => [
  23. { 'channel' => 0, 'value' => 0, 'time' => 60000 }
  24. ],
  25. 'PLAY' => [
  26. { 'channel' => 1, 'value' => 64, 'time' => 500 },
  27. { 'channel' => 2, 'value' => 32, 'time' => 500 },
  28. { 'channel' => 3, 'value' => 80, 'time' => 1500 },
  29. { 'channel' => 5, 'value' => 64, 'time' => 500 },
  30. { 'channel' => 6, 'value' => 36, 'time' => 500 },
  31. { 'channel' => 7, 'value' => 32, 'time' => 500 },
  32. { 'channel' => 8, 'value' => 16, 'time' => 500 },
  33. ],
  34. 'PLAY_HIGH' => [
  35. { 'channel' => 1, 'value' => 255, 'time' => 500 },
  36. { 'channel' => 2, 'value' => 128, 'time' => 500 },
  37. { 'channel' => 3, 'value' => 192, 'time' => 1500 },
  38. { 'channel' => 5, 'value' => 128, 'time' => 500 },
  39. { 'channel' => 6, 'value' => 36, 'time' => 500 },
  40. { 'channel' => 7, 'value' => 128, 'time' => 500 },
  41. { 'channel' => 8, 'value' => 16, 'time' => 500 },
  42. ],
  43. 'PAUSE' => [
  44. { 'channel' => 1, 'value' => 255, 'time' => 1000 },
  45. { 'channel' => 2, 'value' => 192, 'time' => 10000 },
  46. { 'channel' => 3, 'value' => 192, 'time' => 5000 },
  47. { 'channel' => 5, 'value' => 255, 'time' => 1000 },
  48. { 'channel' => 6, 'value' => 104, 'time' => 12000 },
  49. { 'channel' => 7, 'value' => 192, 'time' => 10000 },
  50. { 'channel' => 8, 'value' => 92, 'time' => 16000 },
  51. ],
  52. 'MOTION' => [
  53. { 'channel' => 1, 'value' => 255, 'time' => 1000 },
  54. { 'channel' => 2, 'value' => 192, 'time' => 1000 },
  55. { 'channel' => 3, 'value' => 192, 'time' => 1000 },
  56. { 'channel' => 5, 'value' => 192, 'time' => 1000 },
  57. { 'channel' => 6, 'value' => 104, 'time' => 1000 },
  58. { 'channel' => 7, 'value' => 192, 'time' => 1000 },
  59. { 'channel' => 8, 'value' => 92, 'time' => 1000 },
  60. ],
  61. );
  62. my $TIMEOUT = 180;
  63. # App config
  64. my $SOCK_TIMEOUT = 5;
  65. my $TEMP_DIR = `getconf DARWIN_USER_TEMP_DIR`;
  66. chomp($TEMP_DIR);
  67. my $DATA_DIR = $TEMP_DIR . 'plexMonitor/';
  68. my $CMD_FILE = $DATA_DIR . 'DMX.socket';
  69. # Debug
  70. my $DEBUG = 0;
  71. if ($ENV{'DEBUG'}) {
  72. $DEBUG = 1;
  73. }
  74. # Command-line arguments
  75. my ($DELAY) = @ARGV;
  76. if (!$DELAY) {
  77. $DELAY = 0.5;
  78. }
  79. $DELAY *= 1000000; # Microseconds;
  80. # Sanity check
  81. if (!-d $DATA_DIR || !-S $CMD_FILE) {
  82. die("Bad config\n");
  83. }
  84. # Socket init
  85. my $sock = IO::Socket::UNIX->new(
  86. 'Peer' => $CMD_FILE,
  87. 'Type' => SOCK_DGRAM,
  88. 'Timeout' => $SOCK_TIMEOUT
  89. ) or die('Unable to open socket: ' . $CMD_FILE . ": ${@}\n");
  90. # State
  91. my $state = 'INIT';
  92. my $stateLast = $state;
  93. my $playing = 0;
  94. my $projector = 0;
  95. my $lights = 0;
  96. my $updateLast = 0;
  97. # Always force lights out at launch
  98. dim(0, 0, 0);
  99. # Loop forever
  100. while (1) {
  101. # Monitor the PLAY_STATUS file for changes and state
  102. {
  103. my $mtime = mtime($DATA_DIR . 'PLAY_STATUS');
  104. if ($mtime > $updateLast) {
  105. $updateLast = $mtime;
  106. }
  107. # Grab the PLAY_STATUS value
  108. $playing = 0;
  109. my $fh;
  110. open($fh, $DATA_DIR . 'PLAY_STATUS')
  111. or die("Unable to open PLAY_STATUS\n");
  112. my $text = <$fh>;
  113. close($fh);
  114. if ($text =~ /1/) {
  115. $playing = 1;
  116. }
  117. if ($DEBUG) {
  118. print STDERR 'Playing: ' . $playing . "\n";
  119. }
  120. }
  121. # Monitor the PROJECTOR file for changes and state
  122. {
  123. my $mtime = mtime($DATA_DIR . 'PROJECTOR');
  124. if ($mtime > $updateLast) {
  125. $updateLast = $mtime;
  126. }
  127. # Grab the PROJECTOR value
  128. $projector = 0;
  129. my $fh;
  130. open($fh, $DATA_DIR . 'PROJECTOR')
  131. or die("Unable to open PROJECTOR\n");
  132. my $text = <$fh>;
  133. close($fh);
  134. if ($text =~ /1/) {
  135. $projector = 1;
  136. }
  137. if ($DEBUG) {
  138. print STDERR 'Projector: ' . $projector . "\n";
  139. }
  140. }
  141. # Monitor the LIGHTS file for presence
  142. {
  143. $lights = 0;
  144. if (-e $DATA_DIR . 'LIGHTS') {
  145. $lights = 1;
  146. }
  147. if ($DEBUG) {
  148. print STDERR 'Lights: ' . $lights . "\n";
  149. }
  150. # Clear the override when the projector is off
  151. if ($lights && !$projector) {
  152. unlink($DATA_DIR . 'LIGHTS');
  153. }
  154. }
  155. # Monitor the GUI, PLAYING, and MOTION files for changes only
  156. {
  157. my $mtime = mtime($DATA_DIR . 'PLAYING');
  158. if ($mtime > $updateLast) {
  159. $updateLast = $mtime;
  160. }
  161. $mtime = mtime($DATA_DIR . 'GUI');
  162. if ($mtime > $updateLast) {
  163. $updateLast = $mtime;
  164. }
  165. $mtime = mtime($DATA_DIR . 'MOTION');
  166. if ($mtime > $updateLast) {
  167. $updateLast = $mtime;
  168. }
  169. }
  170. # Calculate the new state
  171. $stateLast = $state;
  172. if ($projector) {
  173. # We are always either playing or paused if the projector is on
  174. if ($playing) {
  175. # Allow an override to higher brightness during playback
  176. if ($lights) {
  177. $state = 'PLAY_HIGH';
  178. } else {
  179. $state = 'PLAY';
  180. }
  181. } else {
  182. $state = 'PAUSE';
  183. }
  184. } else {
  185. # If the projector is off, check the timeouts
  186. my $timeSinceUpdate = time() - $updateLast;
  187. if ($timeSinceUpdate > $TIMEOUT) {
  188. $state = 'OFF';
  189. } elsif ($timeSinceUpdate < $TIMEOUT) {
  190. $state = 'MOTION';
  191. }
  192. }
  193. if ($state eq 'INIT') {
  194. $state = 'OFF';
  195. }
  196. # Update the lighting state
  197. if ($stateLast ne $state) {
  198. if ($DEBUG) {
  199. print STDERR 'State: ' . $stateLast . ' => ' . $state . "\n";
  200. foreach my $data (@{ $DIM{$state} }) {
  201. print STDERR "\t" . $data->{'channel'} . ' => ' . $data->{'value'} . ' @ ' . $data->{'time'} . "\n";
  202. }
  203. }
  204. # Send the dim command
  205. my @values = ();
  206. foreach my $data (@{ $DIM{$state} }) {
  207. dim($data->{'channel'}, $data->{'time'}, $data->{'value'});
  208. push(@values, $data->{'channel'} . ' => ' . $data->{'value'} . ' @ ' . $data->{'time'});
  209. }
  210. # Save the state and value to disk
  211. my ($fh, $tmp) = tempfile($DATA_DIR . 'ROPE.XXXXXXXX', 'UNLINK' => 0);
  212. print $fh 'State: ' . $state . "\n" . join("\n", @values) . "\n";
  213. close($fh);
  214. rename($tmp, $DATA_DIR . 'ROPE');
  215. }
  216. # Wait and loop
  217. usleep($DELAY);
  218. }
  219. sub mtime($) {
  220. my ($file) = @_;
  221. my $mtime = 0;
  222. if (-r $file) {
  223. (undef(), undef(), undef(), undef(), undef(), undef(), undef(), undef(), undef(), $mtime, undef(), undef(), undef()) = stat($file);
  224. }
  225. return $mtime;
  226. }
  227. # Send the command
  228. sub dim($$$) {
  229. my ($channel, $duration, $intensity) = @_;
  230. my $cmd = join(':', $channel, $duration, $intensity);
  231. $sock->send($cmd)
  232. or die('Unable to write command to socket: ' . $CMD_FILE . ': ' . $cmd . ": ${!}\n");
  233. }