announce.pl 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. #!/usr/bin/perl
  2. use strict;
  3. use warnings;
  4. use Email::MIME;
  5. use Email::Sender::Simple qw(sendmail);
  6. use File::Basename qw(dirname);
  7. use Sys::Hostname;
  8. use POSIX qw(ceil);
  9. # Local modules
  10. use Cwd qw(abs_path);
  11. use lib dirname(abs_path($0));
  12. use DMX;
  13. # App config
  14. my $DATA_DIR = DMX::dataDir();
  15. my $STATE_SOCK = 'ANNOUNCE';
  16. my $OUTPUT_FILE = $DATA_DIR . $STATE_SOCK;
  17. my $PUSH_TIMEOUT = 20;
  18. my $PULL_TIMEOUT = $PUSH_TIMEOUT * 3;
  19. my $DELAY = $PULL_TIMEOUT / 2;
  20. my $ALARM_DELAY = 10;
  21. my $MODE_DELAY = 7.5;
  22. my $PROJ_DELAY = 59;
  23. my $PROJ_FACTOR = 0.95;
  24. # Debug
  25. my $DEBUG = 0;
  26. if ($ENV{'DEBUG'}) {
  27. $DEBUG = 1;
  28. }
  29. # State
  30. my %exists = ();
  31. my %last = ();
  32. my %mtime = ();
  33. my $alarmLast = 0;
  34. my $modeLast = 0;
  35. my $projLast = 0;
  36. my $pullLast = time();
  37. # Sockets
  38. DMX::stateSocket($STATE_SOCK);
  39. DMX::stateSubscribe($STATE_SOCK);
  40. # Loop forever
  41. while (1) {
  42. # Remember the last state, for comparison
  43. %last = %exists;
  44. # Wait for state updates
  45. my $cmdState = DMX::readState($DELAY, \%exists, \%mtime, undef());
  46. # Avoid repeated calls to time()
  47. my $now = time();
  48. # Record only valid states
  49. if (defined($cmdState)) {
  50. $pullLast = $now;
  51. }
  52. # Die if we don't see regular updates
  53. if ($now - $pullLast > $PULL_TIMEOUT) {
  54. die('No update on state socket in past ' . $PULL_TIMEOUT . " seconds. Exiting...\n");
  55. }
  56. # Speak when the theater mode changes
  57. if (exists($exists{'GAME'}) && exists($last{'GAME'}) && $exists{'GAME'} ne $last{'GAME'}) {
  58. my $mode = 'PLEX';
  59. if ($exists{'GAME'}) {
  60. $mode = 'GAME';
  61. }
  62. # Wake the input system when we switch to it
  63. my $wol = $ENV{'HOME'} . '/bin/video/wol/wol-' . lc($mode) . '.sh';
  64. if (-x $wol) {
  65. system($wol);
  66. }
  67. # Allow other checks to opt-out if they are mode-switch related
  68. $modeLast = $now;
  69. DMX::say('Theater mode: ' . lc($mode));
  70. }
  71. # Speak when LIGHTS changes
  72. if (exists($exists{'LIGHTS'}) && exists($last{'LIGHTS'}) && $exists{'LIGHTS'} ne $last{'LIGHTS'}) {
  73. if ($now - $modeLast > $MODE_DELAY) {
  74. my $mode = 'DOWN';
  75. if ($exists{'LIGHTS'}) {
  76. $mode = 'UP';
  77. }
  78. DMX::say('Lights ' . lc($mode));
  79. }
  80. }
  81. # Speak when AMPLIFIER_INPUT changes
  82. if ( exists($exists{'AMPLIFIER_INPUT'})
  83. && exists($last{'AMPLIFIER_INPUT'})
  84. && $exists{'AMPLIFIER_INPUT'} ne $last{'AMPLIFIER_INPUT'})
  85. {
  86. if ($now - $modeLast > $MODE_DELAY) {
  87. DMX::say('Amplifier input: ' . lc($exists{'AMPLIFIER_INPUT'}));
  88. }
  89. }
  90. # Speak when AMPLIFIER_MODE changes
  91. if ( exists($exists{'AMPLIFIER_MODE'})
  92. && exists($last{'AMPLIFIER_MODE'})
  93. && $exists{'AMPLIFIER_MODE'} ne $last{'AMPLIFIER_MODE'})
  94. {
  95. DMX::say('Amplifier mode: ' . lc($exists{'AMPLIFIER_MODE'}));
  96. }
  97. # Speak when PROJECTOR_CTRL_TIMER changes
  98. if (exists($exists{'PROJECTOR_CTRL_TIMER'}) && $exists{'PROJECTOR_CTRL_TIMER'}) {
  99. if ($now - $projLast > $PROJ_DELAY) {
  100. # Determine the unit
  101. my $unit = 'second';
  102. my $timeLeft = $exists{'PROJECTOR_CTRL_TIMER'};
  103. if ($exists{'PROJECTOR_CTRL_TIMER'} > 60) {
  104. $timeLeft = $exists{'PROJECTOR_CTRL_TIMER'} / 60;
  105. $unit = 'minute';
  106. }
  107. # Avoid saying "0" unless we *really* mean it
  108. $timeLeft = ceil($timeLeft);
  109. # Add an "s" as needed
  110. my $plural = 's';
  111. if ($timeLeft == 1) {
  112. $plural = '';
  113. }
  114. $unit .= $plural;
  115. # Speak
  116. DMX::say('Projector shutdown in about ' . $timeLeft . ' ' . $unit);
  117. # Update the delay timer
  118. $projLast = $now;
  119. }
  120. }
  121. # Speak when PROJECTOR_CTRL_POWER changes
  122. if ( exists($exists{'PROJECTOR_CTRL_POWER'})
  123. && exists($last{'PROJECTOR_CTRL_POWER'})
  124. && $exists{'PROJECTOR_CTRL_POWER'} ne $last{'PROJECTOR_CTRL_POWER'})
  125. {
  126. if ($exists{'PROJECTOR_CTRL_POWER'} eq 'OFF' || $exists{'PROJECTOR_CTRL_POWER'} eq 'ON') {
  127. $projLast = 0;
  128. DMX::say('Projector: ' . lc($exists{'PROJECTOR_CTRL_POWER'}));
  129. # Announce lamp life status, if near/past $LAMP_LIFE
  130. if (exists($exists{'PROJECTOR_CTRL_LIFE'}) && $exists{'PROJECTOR_CTRL_LIFE'} > $PROJ_FACTOR) {
  131. my $chance = ($exists{'PROJECTOR_CTRL_LIFE'} - $PROJ_FACTOR) * (1 / (1 - $PROJ_FACTOR));
  132. if (rand(1) <= $chance) {
  133. DMX::say('Projector lamp has run for ' . int($exists{'PROJECTOR_CTRL_LIFE'} * 100) . '% of its estimated life.');
  134. }
  135. }
  136. }
  137. }
  138. # Speak when BRIGHT changes
  139. if (exists($exists{'BRIGHT'}) && exists($last{'BRIGHT'}) && $exists{'BRIGHT'} ne $last{'BRIGHT'}) {
  140. my $mode = 'NOMINAL';
  141. if ($exists{'BRIGHT'}) {
  142. $mode = 'FULL';
  143. }
  144. DMX::say('Lights - ' . lc($mode) . ' power');
  145. }
  146. # Speak when NO_MOTION changes
  147. if (exists($exists{'NO_MOTION'}) && exists($last{'NO_MOTION'}) && $exists{'NO_MOTION'} ne $last{'NO_MOTION'}) {
  148. my $mode = 'ENABLED';
  149. if ($exists{'NO_MOTION'}) {
  150. $mode = 'DISABLED';
  151. }
  152. DMX::say('Motion detectors: ' . lc($mode));
  153. }
  154. # Speak when LOCK changes
  155. if (exists($exists{'LOCK'}) && exists($last{'LOCK'}) && $exists{'LOCK'} ne $last{'LOCK'}) {
  156. my $mode = 'UNLOCKED';
  157. if ($exists{'LOCK'}) {
  158. $mode = 'LOCKED';
  159. }
  160. DMX::say('System ' . lc($mode));
  161. }
  162. # Speak when ALARM_ENABLE changes
  163. if (exists($exists{'ALARM_ENABLE'}) && exists($last{'ALARM_ENABLE'}) && $exists{'ALARM_ENABLE'} ne $last{'ALARM_ENABLE'}) {
  164. my $mode = 'DISABLED';
  165. if ($exists{'ALARM_ENABLE'}) {
  166. $mode = 'ENABLED';
  167. }
  168. DMX::say('Alarm ' . lc($mode));
  169. }
  170. # Email when ALARM is asserted
  171. if (exists($exists{'ALARM'}) && exists($last{'ALARM'}) && $exists{'ALARM'} && !$last{'ALARM'}) {
  172. my @exists_tmp = ();
  173. foreach my $key (sort(keys(%exists))) {
  174. push(@exists_tmp, $key . ":\t" . $exists{$key} . ' @ ' . $mtime{$key});
  175. }
  176. my $message = Email::MIME->create(
  177. header_str => [
  178. To => 'zach@kotlarek.com',
  179. From => $ENV{'USER'} . '@' . Sys::Hostname::hostname(),
  180. Subject => 'Alarm Activated',
  181. ],
  182. attributes => {
  183. encoding => 'quoted-printable',
  184. charset => 'ISO-8859-1',
  185. },
  186. body_str => 'Alarm activated: ' . localtime() . "\n\nState: " . $cmdState . "\n" . join("\n", @exists_tmp) . "\n",
  187. );
  188. sendmail($message);
  189. }
  190. # Ongoing spoken ALARM
  191. if ($exists{'ALARM'}) {
  192. if ($now - $alarmLast > $ALARM_DELAY) {
  193. $alarmLast = $now;
  194. DMX::say('Unauthorized access detected.');
  195. }
  196. }
  197. }