dmx.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. #!/opt/local/bin/python2.7
  2. # Append the path to help find our OLA packages
  3. import sys
  4. sys.path.append('/usr/local/lib/python2.7/site-packages')
  5. sys.path.append('/opt/local/lib/python2.7/site-packages')
  6. sys.path.append('/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages')
  7. import os
  8. import time
  9. import array
  10. import select
  11. import socket
  12. import string
  13. import subprocess
  14. from ola.ClientWrapper import ClientWrapper
  15. # ====================================
  16. # Defaults
  17. # ====================================
  18. interval = 10
  19. universe = 0
  20. max_value = 255
  21. max_delay = 300000
  22. max_channels = 512
  23. min_delta = interval / (5 * 1000)
  24. allow_dups = False
  25. max_mesg_len = 1024
  26. DEBUG = False
  27. # ====================================
  28. # Globals
  29. # ====================================
  30. wrapper = None
  31. sock = None
  32. lastTick = time.time()
  33. state_len = 0
  34. state = [ 0 ] * max_channels
  35. cmds = {
  36. 'value' : [ 0 ] * max_channels,
  37. 'ticks' : [ 0 ] * max_channels,
  38. 'delay' : [ 0 ] * max_channels,
  39. 'orig' : [ "" ] * max_channels
  40. }
  41. # ====================================
  42. # Wrapper callback -- exit on errors
  43. # ====================================
  44. def DmxSent(state):
  45. if not state.Succeeded():
  46. print 'Error: %s' % state.message
  47. global wrapper
  48. if wrapper:
  49. wrapper.Stop()
  50. # ====================================
  51. # Main calculation
  52. # ====================================
  53. def SendDMXFrame():
  54. global lastTick
  55. global wrapper
  56. global sock
  57. global cmds
  58. global state
  59. global state_len
  60. # Re-schedule ourselves in interval ms (do this first to keep the timing consistent)
  61. wrapper.AddEvent(interval, SendDMXFrame)
  62. # Avoid repeated calls to time()
  63. now = time.time()
  64. # Adjust our tick count to match reality
  65. ticks = ((now - lastTick) * 1000) / interval
  66. lastTick = now
  67. # Check for new commands
  68. while (True):
  69. try:
  70. cmd = sock.recvfrom(max_mesg_len)[0]
  71. except socket.error, msg:
  72. break
  73. channel, duration, intensity, delay = string.split(cmd, ':')
  74. try:
  75. channel = int(channel)
  76. duration = int(duration)
  77. intensity = int(intensity)
  78. except ValueError:
  79. print 'Invalid command:', cmd
  80. channel = -1
  81. try:
  82. delay = int(delay)
  83. except ValueError:
  84. delay = 0
  85. # Save valid commands
  86. if (
  87. channel >= 0 and channel <= max_channels and
  88. intensity >= 0 and intensity <= max_value and
  89. duration >= 0 and duration <= max_delay and
  90. delay >= 0 and delay < max_delay
  91. ):
  92. if (channel > 0):
  93. if (channel > state_len):
  94. state_len = channel
  95. if (allow_dups or cmd != cmds['orig'][channel - 1]):
  96. cmds['orig'][channel - 1] = cmd
  97. cmds['value'][channel - 1] = intensity
  98. cmds['ticks'][channel - 1] = duration / interval
  99. cmds['delay'][channel - 1] = delay / interval
  100. else:
  101. for i in range(state_len):
  102. if (allow_dups or cmd != cmds['orig'][channel - 1]):
  103. cmds['orig'][i] = cmd
  104. cmds['value'][i] = intensity
  105. cmds['ticks'][i] = duration / interval
  106. cmds['delay'][i] = delay / interval
  107. else:
  108. print 'Invalid command parameters:', channel, duration, intensity, delay
  109. # Update values for each channel
  110. for i in range(state_len):
  111. delta = 0
  112. if (cmds['value'][i] != state[i]):
  113. if (cmds['delay'][i] > 0):
  114. if (cmds['delay'][i] >= ticks):
  115. cmds['delay'][i] -= ticks
  116. else:
  117. cmds['delay'][i] = 0
  118. else:
  119. diff = cmds['value'][i] - state[i]
  120. if (cmds['ticks'][i] < 1):
  121. delta = diff
  122. else:
  123. delta = float(diff) / float(cmds['ticks'][i])
  124. if (abs(delta) < min_delta):
  125. delta = diff
  126. state[i] += delta
  127. cmds['ticks'][i] -= ticks
  128. if (DEBUG):
  129. print '(', now, ') Channel:', (i + 1)
  130. print "\tDelay:", '%.3f' % cmds['delay'][i], "\tValue:", '%.3f' % state[i], \
  131. "\tDelta:", '%.3f' % delta, "\tTicks:", '%.3f' % cmds['ticks'][i]
  132. # Send all DMX channels
  133. data = array.array('B')
  134. for i in range(state_len):
  135. data.append(int(state[i]))
  136. wrapper.Client().SendDmx(universe, data, DmxSent)
  137. # ====================================
  138. # Main
  139. # ====================================
  140. # Debug
  141. DEBUG = None
  142. if 'DEBUG' in os.environ:
  143. DEBUG = os.environ['DEBUG']
  144. # Pick a universe (default from above, or as specified in the environment)
  145. if 'UNIVERSE' in os.environ:
  146. universe = int(os.environ['UNIVERSE'])
  147. # Pick a tick interval (default from above, or as specified in the environment)
  148. if 'INTERVAL' in os.environ:
  149. interval = int(os.environ['INTERVAL'])
  150. # Pick a socket file ($TMPDIR/plexMonitor/DMX.socket, or as specified in the environment)
  151. cmd_file = None
  152. data_dir = None
  153. if 'SOCKET' in os.environ:
  154. cmd_file = os.environ['SOCKET']
  155. data_dir = os.dirname(cmd_file)
  156. else:
  157. proc = subprocess.Popen(['getconf', 'DARWIN_USER_TEMP_DIR'], stdout=subprocess.PIPE, shell=False)
  158. (tmp_dir, err) = proc.communicate()
  159. tmp_dir = tmp_dir.strip()
  160. data_dir = tmp_dir + 'plexMonitor/'
  161. cmd_file = data_dir + 'DMX.socket'
  162. # Sanity checks
  163. if (not os.path.isdir(data_dir)):
  164. raise Exception('Bad config: ' + data_dir)
  165. # Open the socket
  166. if (os.path.exists(cmd_file)):
  167. os.unlink(cmd_file)
  168. sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
  169. sock.bind(cmd_file)
  170. sock.setblocking(0)
  171. if (DEBUG):
  172. print 'Opened socket: %s' % cmd_file
  173. # Start the DMX loop
  174. wrapper = ClientWrapper()
  175. wrapper.AddEvent(interval, SendDMXFrame)
  176. wrapper.Run()