# 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()