dmx.py: --zero --start --stop --step ...
authorTero Marttila <terom@paivola.fi>
Fri, 11 Apr 2014 19:51:19 +0300
changeset 71 24f00b561a4a
parent 70 72175852afd2
child 72 2957be8573b9
dmx.py: --zero --start --stop --step ...
bin/dmx.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/bin/dmx.py	Fri Apr 11 19:51:19 2014 +0300
@@ -0,0 +1,147 @@
+#!/usr/bin/env python
+
+import serial
+import time
+
+import logging; log = logging.getLogger('dmx')
+
+class DMXError (Exception) :
+    def __init__ (self, **kwargs) :
+        self.kwargs = kwargs
+
+    def __str__ (self) :
+        return self.__doc__.strip().format(**self.kwargs)
+
+class DMXCommandError (DMXError) :
+    """
+        Command {cmd!r} failed: {out!r}
+    """
+
+class DMXUnknownCommandError (DMXError) :
+    """
+        Unknown command: {cmd!r}
+    """
+
+class DMX (object) :
+    SERIAL = '/dev/arduino'
+    SERIAL_BAUD = 9600
+    SERIAL_TIMEOUT = 1.0
+
+    @classmethod
+    def open (cls, path, baud=SERIAL_BAUD, timeout=SERIAL_TIMEOUT) :
+        return cls(serial.Serial(path, baud, timeout=timeout))
+
+    def __init__ (self, io) :
+        self.io = io
+
+        # XXX: bug
+        self.io.write('\r')
+        self.io.flush()
+        self.io.read(1)
+
+    def __call__ (self, cmd, *args) :
+        out = cmd + ' ' + ' '.join(str(arg) for arg in args) + '\r'
+        
+        log.info("%s", out)
+        
+        self.io.write(out)
+        self.io.flush()
+
+        ret = self.io.read(len(out))
+
+        if '!' in ret :
+            raise DMXCommandError(cmd=out, out=ret)
+
+        elif '?' in ret :
+            raise DMXUnknownCommandError(cmd=cmd)
+
+    def clear (self) :
+        """
+            Set dmx = [ ]
+
+            i.e. start transmitting zero-length DMX packets.
+            For most lights, this seems to be equivalent to losing the DMX signal, and they retain their old state.
+        """
+
+        self('c')
+
+    def zero (self) :
+        """
+            Set dmx = [0, ...]
+
+            Uses the maximum DMX packet length available.
+        """
+
+        self('z')
+
+    def out (self, *values) :
+        """
+            Set dmx = (value, ...)
+        """
+
+        self('o', *values)
+
+    def set (self, start, *values) :
+        """
+            Set dmx[start:] = value
+        """
+
+        self('s', start, *values)
+
+    def fill (self, start, end, *values) :
+        """
+            Set dmx[start:end] to repetitions of (value, ...)
+        """
+
+        self('f', start, end, *values)
+
+    def range (self, start, stop, step, value) :
+        """
+            Set dmx[start:end:step] = value
+        """
+
+        self('r', start, stop, step, value)
+
+import argparse
+
+def main (argv) :
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--serial', default=DMX.SERIAL,
+            help="Path to /dev/tty*")
+    parser.add_argument('--zero', action='store_true',
+            help="Zero output before setting")
+    parser.add_argument('--start', type=int,
+            help="Set from start offset")
+    parser.add_argument('--stop', type=int,
+            help="Set to end offset")
+    parser.add_argument('--step', type=int,
+            help="Step")
+
+    parser.add_argument('channels', nargs='*', type=int,
+            help="Output channel values")
+
+    options = parser.parse_args(argv[1:])
+
+    logging.basicConfig(level=logging.DEBUG)
+
+    dmx = DMX.open(options.serial)
+
+    if options.zero :
+        dmx.zero()
+
+
+    if options.start and options.stop and options.step :
+        dmx.range(options.start, options.stop, options.step, *options.channels)
+
+    elif options.start and options.stop :
+        dmx.fill(options.start, options.stop, *options.channels)
+
+    elif options.start :
+        dmx.set(options.start, *options.channels)
+
+    elif options.channels :
+        dmx.out(*options.channels)
+
+if __name__ == '__main__':
+    import sys
+    sys.exit(main(sys.argv))