rope.pl 7.4 KB

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