dmx.py 5.3 KB

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