import os, time, termios, fcntl, pprint
from pprint import pprint
from array import array

# these are defined in termios.h, but the termios module doesn't have them.
# These values from OS X.
TIOCSBRK = 0x2000747b
TIOCCBRK = 0x2000747a

class SerialBanger(object):
    def __init__(self, device):
        self.buffer = array('L', [0])
        self.fd = os.open(device, os.O_WRONLY | os.O_NONBLOCK)

    def readStatus(self):
        fcntl.ioctl(self.fd, termios.TIOCMGET, self.buffer)
        return self.status()

    def setStatus(self):
        fcntl.ioctl(self.fd, termios.TIOCMSET, self.buffer)
        return self.status()

    def getFlags(self):
        return self.buffer[0]

    def setFlags(self, value):
        self.buffer[0] = value

    flags = property(getFlags, setFlags)
    del getFlags, setFlags

    def status(self):
        return {
            'Line enable        ': bool(self.flags & termios.TIOCM_LE),
            'Data Terminal Ready': bool(self.flags & termios.TIOCM_DTR),
            'Request to send    ': bool(self.flags & termios.TIOCM_RTS),
            'Secondary transmit ': bool(self.flags & termios.TIOCM_ST),
            'Secondary receive  ': bool(self.flags & termios.TIOCM_SR),
            'Clear to send      ': bool(self.flags & termios.TIOCM_CTS),
            'Carrier detect     ': bool(self.flags & termios.TIOCM_CAR),
            'Ring               ': bool(self.flags & termios.TIOCM_RNG),
            'Data set ready     ': bool(self.flags & termios.TIOCM_DSR),
        }

    def close(self):
        os.close(self.fd)
        self.fd = None

    def setFlags(self, flags):
        self.flags |= flags
        self.setStatus()

    def clearFlags(self, flags):
        self.flags &= ~flags
        self.setStatus()

    def setRTS(self):
        self.setFlags(termios.TIOCM_RTS)

    def clearRTS(self):
        self.clearFlags(termios.TIOCM_RTS)

    def setDTR(self):
        self.setFlags(termios.TIOCM_DTR)

    def clearDTR(self):
        self.clearFlags(termios.TIOCM_DTR)

    def setTXD(self):
        fcntl.ioctl(self.fd, TIOCSBRK, 0)

    def clearTXD(self):
        fcntl.ioctl(self.fd, TIOCCBRK, 0)


print 'opening serial port'
banger = SerialBanger('/dev/tty.KeySerial1')

# TXD = red, CS
# DTR = yellow, CLK
# RTS = orange, SER
# set = low, clear = high

def delay():
    time.sleep(0.006)

banger.setDTR() # clock and data low
banger.setRTS()
delay()
banger.setTXD() # enable CS

try:
    def sendBit(b):
        banger.setDTR() # clock falling

        # set data
        if b:
            banger.clearRTS()
        else:
            banger.setRTS()
        delay()
        banger.clearDTR()       # clock rising
        delay()

    sendBit(1)  # ch3 output MSB
    sendBit(1)
    sendBit(0)
    sendBit(0)
    sendBit(0)
    sendBit(0)
    sendBit(0)
    sendBit(0)  # ch3 output LSB
    sendBit(1)  # ch4 output MSB
    sendBit(1)
    sendBit(0)
    sendBit(0)
    sendBit(0)
    sendBit(0)
    sendBit(0)
    sendBit(0)  # ch4 output LSB
    sendBit(1)  # ch1 output MSB
    sendBit(0)
    sendBit(1)
    sendBit(1)
    sendBit(0)
    sendBit(0)
    sendBit(0)
    sendBit(0)  # ch1 output LSB
    sendBit(1)  # ch2 output MSB
    sendBit(0)
    sendBit(0)
    sendBit(0)
    sendBit(0)
    sendBit(0)
    sendBit(0)
    sendBit(1)  # ch2 output LSB
    sendBit(1)  # ch4 amp gain MSB
    sendBit(1)  # ch4 amp gain LSB
    sendBit(1)  # ch3 amp gain MSB
    sendBit(1)  # ch3 amp gain LSB
    sendBit(0)  # ch2 amp gain MSB
    sendBit(1)  # ch2 amp gain LSB
    sendBit(0)  # ch1 amp gain MSB
    sendBit(1)  # ch1 amp gain LSB

finally:
    banger.setDTR()     # clock falling
    banger.setRTS()     # data 0
    time.sleep(.01)
    banger.clearTXD()   # disable CS
    time.sleep(.01)
    print 'closing'
    banger.close()
    print 'closed'
