dmx.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  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 = int(((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. cmds['delay'][i] -= 1
  112. else:
  113. diff = cmds['value'][i] - state[i]
  114. if (cmds['ticks'][i] < 1):
  115. delta = diff
  116. else:
  117. delta = float(diff) / float(cmds['ticks'][i])
  118. if (abs(delta) < min_delta):
  119. delta = diff
  120. state[i] += delta
  121. cmds['ticks'][i] -= ticks
  122. if (DEBUG):
  123. print '(', now, ') Channel:', (i + 1)
  124. print "\tDelay:", cmds['delay'][i], "\tValue:", '%.3f' % state[i], "\tDelta:", '%.3f' % delta, "\tTicks:", cmds['ticks'][i]
  125. # Send all DMX channels
  126. data = array.array('B')
  127. for i in range(state_len):
  128. data.append(int(state[i]))
  129. wrapper.Client().SendDmx(universe, data, DmxSent)
  130. # ====================================
  131. # Main
  132. # ====================================
  133. # Debug
  134. DEBUG = None
  135. if 'DEBUG' in os.environ:
  136. DEBUG = os.environ['DEBUG']
  137. # Pick a universe (default from above, or as specified in the environment)
  138. if 'UNIVERSE' in os.environ:
  139. universe = int(os.environ['UNIVERSE'])
  140. # Pick a tick interval (default from above, or as specified in the environment)
  141. if 'INTERVAL' in os.environ:
  142. interval = int(os.environ['INTERVAL'])
  143. # Pick a socket file ($TMPDIR/plexMonitor/DMX.socket, or as specified in the environment)
  144. cmd_file = None
  145. data_dir = None
  146. if 'SOCKET' in os.environ:
  147. cmd_file = os.environ['SOCKET']
  148. data_dir = os.dirname(cmd_file)
  149. else:
  150. proc = subprocess.Popen(['getconf', 'DARWIN_USER_TEMP_DIR'], stdout=subprocess.PIPE, shell=False)
  151. (tmp_dir, err) = proc.communicate()
  152. tmp_dir = tmp_dir.strip()
  153. data_dir = tmp_dir + 'plexMonitor/'
  154. cmd_file = data_dir + 'DMX.socket'
  155. # Sanity checks
  156. if (not os.path.isdir(data_dir)):
  157. raise Exception('Bad config: ' + data_dir)
  158. # Open the socket
  159. if (os.path.exists(cmd_file)):
  160. os.unlink(cmd_file)
  161. sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
  162. sock.bind(cmd_file)
  163. sock.setblocking(0)
  164. # Start the DMX loop
  165. wrapper = ClientWrapper()
  166. wrapper.AddEvent(interval, SendDMXFrame)
  167. wrapper.Run()