qmsk/dmx/control.py
changeset 74 9031dafa39d6
child 83 136e210fce82
equal deleted inserted replaced
73:3c25f32c92fa 74:9031dafa39d6
       
     1 import collections
       
     2 import logging; log = logging.getLogger('qmsk.dmx.control')
       
     3 import serial
       
     4 
       
     5 class DMXError (Exception) :
       
     6     def __init__ (self, **kwargs) :
       
     7         self.kwargs = kwargs
       
     8 
       
     9     def __str__ (self) :
       
    10         return self.__doc__.strip().format(**self.kwargs)
       
    11 
       
    12 class DMXCommandError (DMXError) :
       
    13     """
       
    14         Command {cmd!r} failed: {out!r}
       
    15     """
       
    16 
       
    17 class DMXUnknownCommandError (DMXError) :
       
    18     """
       
    19         Unknown command: {cmd!r}
       
    20     """
       
    21 
       
    22 class DMX (object) :
       
    23     """
       
    24         Arudino-based DMX controller using src/hello-dmx.c over the serial port.
       
    25     """
       
    26 
       
    27     SERIAL = '/dev/arduino'
       
    28     SERIAL_BAUD = 9600
       
    29     SERIAL_TIMEOUT = 1.0
       
    30 
       
    31     @classmethod
       
    32     def open (cls, path, baud=SERIAL_BAUD, timeout=SERIAL_TIMEOUT) :
       
    33         return cls(serial.Serial(path, baud, timeout=timeout))
       
    34 
       
    35     def __init__ (self, io) :
       
    36         self.io = io
       
    37 
       
    38         # XXX: bug
       
    39         self.io.write('\r')
       
    40         self.io.flush()
       
    41         self.io.read(1)
       
    42 
       
    43     def _arg (self, arg) :
       
    44         if isinstance(arg, str) :
       
    45             value, = arg
       
    46             value = ord(value)
       
    47         elif isinstance(arg, int) :
       
    48             value = arg
       
    49         else :
       
    50             raise ValueError(arg)
       
    51 
       
    52         if 0 <= value <= 255 :
       
    53             return str(value)
       
    54         else :
       
    55             raise ValueError(value)
       
    56 
       
    57     def __call__ (self, cmd, *args) :
       
    58         out = cmd + ' ' + ' '.join(self._arg(arg) for arg in args) + '\r'
       
    59         
       
    60         log.info("%s", out)
       
    61         
       
    62         self.io.write(out)
       
    63         self.io.flush()
       
    64 
       
    65         ret = self.io.read(len(out))
       
    66 
       
    67         if '!' in ret :
       
    68             raise DMXCommandError(cmd=out, out=ret)
       
    69 
       
    70         elif '?' in ret :
       
    71             raise DMXUnknownCommandError(cmd=cmd)
       
    72 
       
    73     def clear (self) :
       
    74         """
       
    75             Set dmx = [ ]
       
    76 
       
    77             i.e. start transmitting zero-length DMX packets.
       
    78             For most lights, this seems to be equivalent to losing the DMX signal, and they retain their old state.
       
    79         """
       
    80 
       
    81         self('c')
       
    82 
       
    83     def zero (self) :
       
    84         """
       
    85             Set dmx = [0, ...]
       
    86 
       
    87             Uses the maximum DMX packet length available.
       
    88         """
       
    89 
       
    90         self('z')
       
    91 
       
    92     def out (self, *values) :
       
    93         """
       
    94             Set dmx = (value, ...)
       
    95         """
       
    96 
       
    97         self('o', *values)
       
    98 
       
    99     def set (self, start, *values) :
       
   100         """
       
   101             Set dmx[start:] = value
       
   102         """
       
   103 
       
   104         self('s', start, *values)
       
   105 
       
   106     def fill (self, start, end, *values) :
       
   107         """
       
   108             Set dmx[start:end] to repetitions of (value, ...)
       
   109         """
       
   110 
       
   111         self('f', start, end, *values)
       
   112 
       
   113     def range (self, start, stop, step, value) :
       
   114         """
       
   115             Set dmx[start:end:step] = value
       
   116         """
       
   117 
       
   118         self('r', start, stop, step, value)
       
   119 
       
   120     def __setitem__ (self, index, value) :
       
   121         if isinstance(value, collections.Sequence) :
       
   122             values = tuple(value)
       
   123         else :
       
   124             values = (value, )
       
   125 
       
   126         if isinstance(index, slice) :
       
   127             if index.start and index.stop and index.step :
       
   128                 # XXX: single
       
   129                 self.range(index.start, index.stop, index.step, value)
       
   130 
       
   131             elif index.start and index.stop :
       
   132                 self.fill(index.start, index.stop, *values)
       
   133 
       
   134             elif index.start :
       
   135                 self.set(index.start, *values)
       
   136 
       
   137             else :
       
   138                 raise IndexError("invalid slice: %s" % (index, ))
       
   139         
       
   140         else :
       
   141             # simple set
       
   142             self.set(index, *values)
       
   143