Add initial support for DMX lighting
This commit is contained in:
210
dmx.py
Normal file
210
dmx.py
Normal file
@@ -0,0 +1,210 @@
|
||||
# dmx.py
|
||||
|
||||
from ola.ClientWrapper import ClientWrapper
|
||||
from ola.DMXConstants import (DMX_MIN_SLOT_VALUE, DMX_MAX_SLOT_VALUE, DMX_UNIVERSE_SIZE)
|
||||
from array import array
|
||||
import time
|
||||
|
||||
DMX_DATA_SIZE = DMX_UNIVERSE_SIZE
|
||||
|
||||
class CompatibleArray(array):
|
||||
def tostring(self):
|
||||
return self.tobytes()
|
||||
|
||||
class DMX:
|
||||
def __init__(self, universe, client_wrapper, dmx_data_size=DMX_UNIVERSE_SIZE):
|
||||
dmx_data_size = min(dmx_data_size, DMX_UNIVERSE_SIZE)
|
||||
self._universe = universe
|
||||
self._data = CompatibleArray('B', [DMX_MIN_SLOT_VALUE] * dmx_data_size)
|
||||
self._wrapper = client_wrapper
|
||||
self._client = client_wrapper.Client()
|
||||
|
||||
def set_channel(self, channel, value):
|
||||
self._data[channel] = value
|
||||
|
||||
def submit(self):
|
||||
self._client.SendDmx(self._universe, self._data)
|
||||
|
||||
class FixtureType:
|
||||
def __init__(self, channels):
|
||||
self._channels = channels
|
||||
|
||||
def max_channels(self):
|
||||
return max([channel.get('channel', 0), channel.get('fine-channel', 0)] for channel in self._channels)
|
||||
|
||||
def is_channel(self, channel):
|
||||
return channel in self._channels
|
||||
|
||||
def can_fade(self, channel):
|
||||
return self._channels[channel]['fade']
|
||||
|
||||
def parse_value(self, value):
|
||||
name = None
|
||||
if isinstance(value, str):
|
||||
values = value.split('/')
|
||||
if len(values) > 2:
|
||||
raise ValueError('{} is not a valid value'.format(value))
|
||||
elif len(values) > 1:
|
||||
(name, number) = values
|
||||
else:
|
||||
number = values[0]
|
||||
else:
|
||||
number = value
|
||||
|
||||
return (name, number)
|
||||
|
||||
def channel(self, name):
|
||||
if name not in self._channels:
|
||||
return KeyError('Channel {} not found in fixture type'.format(name))
|
||||
return self._channels[name]
|
||||
|
||||
def range_lookup(self, channel, name, number):
|
||||
channel = self.channel(channel)
|
||||
range = channel['range']
|
||||
if isinstance(range, dict):
|
||||
if name not in range:
|
||||
raise KeyError('Range {} not found'.format(name))
|
||||
(a, b) = range['range']
|
||||
if number < a or number > b:
|
||||
raise ValueError('DMX value out of range')
|
||||
return a + number
|
||||
else:
|
||||
if name is not None:
|
||||
raise KeyError('Range {} not found'.format(name))
|
||||
return number
|
||||
|
||||
def update(self, **kwargs):
|
||||
output = []
|
||||
for channel in kwargs:
|
||||
value = kwargs[channel]
|
||||
(name, number) = self.parse_value(value)
|
||||
dmxvalue = self.range_lookup(channel, name, number)
|
||||
ch = self.channel(channel)
|
||||
if 'fine-channel' in ch and ch['fine-channel'] is not None:
|
||||
output.extend([(ch['channel'], dmxvalue // 256), (ch['fine-channel'], dmxvalue % 256)])
|
||||
else:
|
||||
output.extend([(ch['channel'], dmxvalue)])
|
||||
return output
|
||||
|
||||
class Fixture:
|
||||
def __init__(self, dmx, type):
|
||||
self._dmx = dmx
|
||||
self._type = type
|
||||
self._dmxoutput = None
|
||||
self._values = {}
|
||||
self._target = {}
|
||||
self._velocity = {}
|
||||
self._fadetime = 0
|
||||
self._t = 0
|
||||
|
||||
def get(self, channel):
|
||||
return self._values.get(channel, 0)
|
||||
|
||||
def set(self, channel, value):
|
||||
self._values[channel] = value
|
||||
|
||||
def set_dmx(self, dmx):
|
||||
self._dmxoutput = dmx
|
||||
|
||||
def is_channel(self, name):
|
||||
return self._type.is_channel(name)
|
||||
|
||||
def set_fadetime(self, fadetime):
|
||||
self._fadetime = fadetime
|
||||
|
||||
def update(self, **kwargs):
|
||||
for channel in kwargs:
|
||||
(name, number) = self._type.parse_value(kwargs[channel])
|
||||
self._target[channel] = self._type.range_lookup(channel, name, number)
|
||||
diff = self._target[channel] - self.get(channel)
|
||||
self._velocity[channel] = diff
|
||||
self._t = 0
|
||||
self.tick(0)
|
||||
|
||||
def tick(self, dt):
|
||||
def reached(value, target, velocity):
|
||||
if velocity < 0:
|
||||
return value <= target
|
||||
else:
|
||||
return value >= target
|
||||
|
||||
self._t += dt
|
||||
for channel in self._target.keys():
|
||||
if self._type.can_fade(channel) and self._fadetime > 0 and self._t <= self._fadetime:
|
||||
# print("Fading: t = {}, ft = {}".format(self._t, self._fadetime))
|
||||
value = self.get(channel)
|
||||
value += self._velocity[channel] * dt / self._fadetime
|
||||
if reached(value, self._target[channel], self._velocity[channel]):
|
||||
self._velocity[channel] = 0
|
||||
value = self._target[channel]
|
||||
self.set(channel, value)
|
||||
else:
|
||||
# print("Not fading: t = {}, ft = {}".format(self._t, self._fadetime))
|
||||
self.set(channel, self._target[channel])
|
||||
output = self._type.update(**self._values)
|
||||
output = [(ch-1+self._dmx-1, value) for (ch, value) in output]
|
||||
if self._dmxoutput is None:
|
||||
return
|
||||
for (ch, value) in output:
|
||||
self._dmxoutput.set_channel(ch, int(value))
|
||||
|
||||
class Group:
|
||||
def __init__(self, objects):
|
||||
self._objects = objects
|
||||
|
||||
def is_channel(self, name):
|
||||
return any([x.is_channel(name) for x in self._objects])
|
||||
|
||||
def set_fadetime(self, fadetime):
|
||||
for object in self._objects:
|
||||
object.set_fadetime(fadetime)
|
||||
|
||||
def update(self, **kwargs):
|
||||
for object in self._objects:
|
||||
object.update(**kwargs)
|
||||
|
||||
class Scene:
|
||||
def __init__(self, objects):
|
||||
self._objects = objects
|
||||
|
||||
def set_fadetime(self, fadetime):
|
||||
for object in self._objects:
|
||||
(obj, settings) = self._objects[object]
|
||||
obj.set_fadetime(fadetime)
|
||||
|
||||
def activate(self, fadetime=0):
|
||||
self.set_fadetime(fadetime)
|
||||
for object in self._objects:
|
||||
(obj, settings) = self._objects[object]
|
||||
obj.update(**settings)
|
||||
|
||||
|
||||
class FixtureOutput:
|
||||
def __init__(self, fixtures, dmx):
|
||||
self._fixtures = fixtures
|
||||
self._dmx = dmx
|
||||
for fixture in self._fixtures:
|
||||
fixture.set_dmx(dmx)
|
||||
|
||||
def tick(self, dt):
|
||||
for fixture in self._fixtures:
|
||||
fixture.tick(dt)
|
||||
self._dmx.submit()
|
||||
|
||||
|
||||
def main():
|
||||
wrapper = ClientWrapper()
|
||||
universe = 0
|
||||
dmx = DMX(universe, wrapper, DMX_DATA_SIZE)
|
||||
value = 0
|
||||
while True:
|
||||
dmx.set_channel(0, 255)
|
||||
dmx.set_channel(1, int((value * 3) % 256))
|
||||
dmx.set_channel(2, int((value * 4) % 256))
|
||||
dmx.set_channel(3, int((value * 5) % 256))
|
||||
dmx.submit()
|
||||
value += 0.1
|
||||
time.sleep(0.1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user