class AzimuthController:
    DIRECTION_GPIO = 38
    PULSE_GPIO = 40
    HOME_GPIO = 22
    MICROSTEPS = 400
    GEAR_RATIO = 48
    LEFT_DIRECTION = True
    RIGHT_DIRECTION = not LEFT_DIRECTION
    HOMING_DIRECTION = LEFT_DIRECTION
    BACKLASH_STEPS = 12

    def __init__(self):
        GPIO.setup(self.HOME_GPIO, GPIO.IN, pull_up_down = GPIO.PUD_UP)
        self.__stepper = Stepper(self.MICROSTEPS * self.GEAR_RATIO, self.DIRECTION_GPIO, self.PULSE_GPIO, self.BACKLASH_STEPS)

        steps = 0
        while self.is_home():
            self.__stepper.pulse(self.HOMING_DIRECTION)
            steps += 1
            if (steps > self.total_steps()):
                raise ValueError("Cannot home the azimuth stepper. It is likely not moving or the sensor is broken.")
        while not self.is_home():
            self.__stepper.pulse(self.HOMING_DIRECTION)
            steps += 1
            if (steps > self.total_steps()):
                raise ValueError("Cannot home the azimuth stepper. It is likely not moving or the sensor is broken.")
        self.__stepper.reset_position()

    def is_home(self):
        return not GPIO.input(self.HOME_GPIO)

    def position(self):
        return self.__stepper.position()

    def total_steps(self):
        return self.__stepper.total_steps()

    def move_left(self, steps):
        return self.move(self.LEFT_DIRECTION, steps)

    def move_right(self, steps):
        return self.move(self.RIGHT_DIRECTION, steps)

    def move(self, direction, steps):
        self.__stepper.move(direction, steps)
        return True

    def move_to(self, position, max_steps = sys.maxint):
        if position < 0 or position >= self.total_steps():
            raise ValueError("Invalid position")

        steps_from_left = (position - self.position()) % self.total_steps()
        steps_from_right = (self.position() - position) % self.total_steps()

        if steps_from_left <= steps_from_right:
            self.move(self.LEFT_DIRECTION, min(steps_from_left, max_steps))
            return steps_from_left <= max_steps
        else:
            self.move(self.RIGHT_DIRECTION, min(steps_from_right, max_steps))
            return steps_from_right <= max_steps
class ElevationController:
    DIRECTION_GPIO = 36
    PULSE_GPIO = 32
    # There are ~150 degrees between the two home sensors
    HOME_DOWN_GPIO = 24
    HOME_UP_GPIO = 26
    MICROSTEPS = 400
    GEAR_RATIO = 48
    DOWN_DIRECTION = True
    UP_DIRECTION = not DOWN_DIRECTION
    BACKLASH_STEPS = 12

    def __init__(self):
        GPIO.setup(self.HOME_DOWN_GPIO, GPIO.IN, pull_up_down = GPIO.PUD_UP)
        GPIO.setup(self.HOME_UP_GPIO, GPIO.IN, pull_up_down = GPIO.PUD_UP)
        self.__stepper = Stepper(self.MICROSTEPS * self.GEAR_RATIO, self.DIRECTION_GPIO, self.PULSE_GPIO, self.BACKLASH_STEPS)

        steps = 0
        while self.is_home_down():
            self.__stepper.pulse(self.UP_DIRECTION)
            steps += 1
            if (steps > self.__stepper.total_steps()):
                raise ValueError("Cannot home the elevation stepper. It is likely not moving or the sensor is broken.")
        while not self.is_home_down():
            self.__stepper.pulse(self.DOWN_DIRECTION)
            steps += 1
            if (steps > self.__stepper.total_steps()):
                raise ValueError("Cannot home the elevation stepper. It is likely not moving or the sensor is broken.")

        steps = 0
        self.__amplitude = 0
        while not self.is_home_up():
            self.__stepper.pulse(self.UP_DIRECTION)
            steps += 1
            self.__amplitude += 1
            if (steps > self.__stepper.total_steps()):
                raise ValueError("Cannot home the elevation stepper. It is likely not moving or the sensor is broken.")
        self.__stepper.reset_position()

    def is_home(self):
        return self.is_home_down() or self.is_home_up()

    def is_home_down(self):
        return not GPIO.input(self.HOME_DOWN_GPIO)

    def is_home_up(self):
        return not GPIO.input(self.HOME_UP_GPIO)

    def position(self):
        position = float(self.__stepper.position()) / self.amplitude()
        if position < 0 or position > 1:
            raise ValueError("Invalid position about to be returned: " + position)
        return position

    def amplitude(self):
        return self.__amplitude

    def move_to(self, percentage_amplitude, max_delta = 1.0):
        if percentage_amplitude < 0 or percentage_amplitude > 1:
            raise ValueError("Percentage of amplitude must be between 0 and 1: " + str(percentage_amplitude))
        steps = int((percentage_amplitude - self.position()) * self.amplitude())
        max_steps = int(max_delta * self.amplitude())
        direction = self.UP_DIRECTION if steps < 0 else self.DOWN_DIRECTION
        self.__stepper.move(direction, min(abs(steps), max_steps))
        return abs(steps) <= max_steps