#!/usr/bin/python

# Written by Mark Eichin.  Use for whatever you want...

# "version 0" of the missile launcher driver
# works with a local build of pyusb...
import sys
import os
import time
import struct
sys.path.append("pyusb-0.3.1/build/lib.linux-i686-2.4/")
import usb

# find all of the USB busses
busses = usb.busses()
print busses

# Find one device
for bus in busses:
    for dev in bus.devices:
        if dev.idVendor == 0x1941 and dev.idProduct == 0x8021:
            rdev = dev

dev = rdev
# bugcheck - make sure we did find the DreamCheeky device
assert dev.idVendor == 0x1941
assert dev.idProduct == 0x8021
print dev, hex(dev.idVendor), hex(dev.idProduct)
# we know that there's one config, one interface, and one endpoint
conf = dev.configurations[0]
iface = conf.interfaces[0][0]
endpoint = iface.endpoints[0]

# open it
handle = dev.open()
handle.detachKernelDriver(0) # Should this be iface.interfaceNumber?
# Note that if you don't run this, you'll actually get messages like
# [222572.952000] usb usb1: usbfs: process 20986 (python) did not claim interface 1 before use
# in dmesg...
handle.setConfiguration(conf)
handle.claimInterface(iface)
handle.setAltInterface(iface)
handle.reset()

# based on /mac/USB Missile Launcher NZ Source Package/USB Missile Launcher NZ v1.4e/Classes/USBMissileControl.m
# we have

reqBuffer = [0] * 8
reqType = usb.TYPE_CLASS | usb.RECIP_INTERFACE | usb.ENDPOINT_OUT
# which is 0x21...
# setconfig is 0x9...
# dt_config is 2...

print "PID:", os.getpid()

class Motion:
    UP = 1
    DOWN = 2
    LEFT = 4
    RIGHT = 8
    FIRE = 16
    # legit combos:
    # UP+LEFT DOWN+LEFT UP+RIGHT DOWN+RIGHT
    # bad combos:
    # UP+DOWN
    # LEFT+RIGHT
    # UP+DOWN+LEFT+RIGHT
    # but:
    # SLOW LEFT = LEFT + UP + DOWN
    # SLOW RIGHT = RIGHT + UP + DOWN
    # SLOW UP = UP + LEFT + RIGHT
    # SLOW DOWN = DOWN + LEFT + RIGHT

class LimitSwitch:
    LEFT = 0x400
    RIGHT = 0x800
    DOWN = 0x40
    UP = 0x80
    def value(self, usb_bytes):
        """convert limit switch bytes to values"""
        return struct.unpack("<H", struct.pack("<BB", *usb_bytes))[0]
    def decode_switches(self, value):
        if value & LimitSwitch.LEFT:
            print "LEFT",
        if value & LimitSwitch.RIGHT:
            print "RIGHT",
        if value & LimitSwitch.DOWN:
            print "DOWN",
        if value & LimitSwitch.UP:
            print "UP",
        print
    def filtered_motion(self, motion, value):
        if value & LimitSwitch.LEFT:
            motion = motion & ~Motion.LEFT
            # print "STOPPING LEFT"
        if value & LimitSwitch.RIGHT:
            motion = motion & ~Motion.RIGHT
            # print "STOPPING RIGHT"
        if value & LimitSwitch.DOWN:
            motion = motion & ~Motion.DOWN
            # print "STOPPING DOWN"
        if value & LimitSwitch.UP:
            motion = motion & ~Motion.UP
            # print "STOPPING UP"
        return motion

def request_move(handle, motion):
    """send request for motion"""
    reqType = usb.TYPE_CLASS | usb.RECIP_INTERFACE | usb.ENDPOINT_OUT
    pad = [0] * 7
    handle.controlMsg(reqType, usb.REQ_SET_CONFIGURATION, [motion] + pad, value=usb.DT_CONFIG, index=0)

def check_switches(endpoint):
    """read from the endpoint and see if any switches are set"""
    # return LimitSwitch().value(handle.interruptRead(endpoint.address, 2))
    return LimitSwitch().value(handle.interruptRead(endpoint.address, 8)[:2])

def move_to_limit(handle, endpoint, motion, duration=None):
    """move until we get the limit stop..."""
    request_move(handle, motion)
    started = time.time()
    while motion:
        switches = check_switches(endpoint)
        if switches:
            # print "got switch", LimitSwitch().decode_switches(switches)
            motion = LimitSwitch().filtered_motion(motion, switches)
            request_move(handle, motion)
        if duration:
            if started + duration < time.time():
                break
    request_move(handle, 0)

if __name__ == "__main__":

    move_to_limit(handle, endpoint, Motion.LEFT)
    start = time.time()
    move_to_limit(handle, endpoint, Motion.RIGHT)
    horiz_range = time.time() - start
    print "Horiz Range:", horiz_range
    move_to_limit(handle, endpoint, Motion.LEFT, duration=horiz_range/2)

    move_to_limit(handle, endpoint, Motion.DOWN)
    start = time.time()
    move_to_limit(handle, endpoint, Motion.UP)
    vert_range = time.time() - start
    print "Vert Range:", vert_range
    move_to_limit(handle, endpoint, Motion.DOWN, duration=vert_range/2)

    print "seems like 5 seconds is enough to fire, but we get no feedback..."
    move_to_limit(handle, endpoint, Motion.FIRE, duration=5)

