Exemple #1
0
class MoabEnv:
    def __init__(
        self,
        frequency=30,
        debug=False,
        verbose=0,
        derivative_fn=derivative,
        calibration_file="bot.json",
    ):
        self.debug = debug
        self.verbose = verbose
        self.frequency = frequency
        self.derivative_fn = derivative
        self.vel_x = self.derivative_fn(frequency)
        self.vel_y = self.derivative_fn(frequency)
        self.sum_x, self.sum_y = 0, 0

        self.hat = Hat(debug=debug, verbose=verbose)
        self.hat.open()
        self.camera = OpenCVCameraSensor(frequency=frequency)
        self.detector = hsv_detector(debug=debug)

        self.calibration_file = calibration_file
        self.reset_calibration()

    def __enter__(self):
        self.camera.start()
        return self

    def __exit__(self, type, value, traceback):
        self.hat.go_down()
        self.hat.disable_servos()
        self.hat.display_power_symbol("TO WAKE", PowerIcon.POWER)
        self.hat.close()
        self.camera.stop()

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return f"hue: {self.hue}, offsets: {self.servo_offsets}"

    def reset_calibration(self, calibration_file=None):
        # Use default if not defined
        calibration_file = calibration_file or self.calibration_file

        # Get calibration settings
        if os.path.isfile(calibration_file):
            with open(calibration_file, "r") as f:
                calib = json.load(f)
        else:  # Use defaults
            calib = {
                "ball_hue": 44,
                "plate_offsets": (0.0, 0.0),
                "servo_offsets": (0.0, 0.0, 0.0),
            }

        plate_offsets = calib["plate_offsets"]
        self.plate_offsets_pixels = meters_to_pixels(plate_offsets)
        self.servo_offsets = calib["servo_offsets"]
        self.hue = calib["ball_hue"]

        # Set the servo offsets (self.hue & self.plate_offsets_pixels are used in step)
        self.hat.set_servo_offsets(*self.servo_offsets)
        self.camera.x_offset_pixels = self.plate_offsets_pixels[0]
        self.camera.y_offset_pixels = self.plate_offsets_pixels[1]

    def reset(self, text=None, icon=None):
        # Optionally display the controller active text
        if icon and text:
            self.hat.display_string_icon(text, icon)
        elif text:
            self.hat.display_string(text)

        # Reset the derivative of the position
        # Use a high pass filter instead of a numerical derivative for stability.
        # A high pass filtered signal can be thought of as a derivative of a low
        # pass filtered signal: fc*s / (s + fc) = fc*s * 1 / (s + fc)
        # For more info: https://en.wikipedia.org/wiki/Differentiator
        # Or: https://www.youtube.com/user/ControlLectures/
        self.vel_x = self.derivative_fn(self.frequency)
        self.vel_y = self.derivative_fn(self.frequency)
        # Reset the integral of the position
        self.sum_x, self.sum_y = 0, 0

        # Return the state after a step with no motor actions
        return self.step((0, 0))

    def step(self, action) -> Tuple[EnvState, bool, Buttons]:
        plate_x, plate_y = action
        self.hat.set_angles(plate_x, plate_y)

        frame, elapsed_time = self.camera()
        ball_detected, cicle_feature = self.detector(frame, hue=self.hue)
        ball_center, ball_radius = cicle_feature

        x, y = ball_center

        # Update derivate calulation
        vel_x, vel_y = self.vel_x(x), self.vel_y(y)
        # Update the summation (integral calculation)
        self.sum_x += x
        self.sum_y += y

        buttons = self.hat.get_buttons()
        state = EnvState(x, y, vel_x, vel_y, self.sum_x, self.sum_y)

        return state, ball_detected, buttons
Exemple #2
0
class MoabHardware:
    def __init__(
        self,
        frequency=30,
        debug=False,
        verbose=0,
        calibration_file="bot.json",
    ):
        self.debug = debug
        self.verbose = verbose
        self.frequency = frequency

        self.hat = Hat(debug=debug, verbose=verbose)
        self.hat.open()
        self.camera = OpenCVCameraSensor(frequency=frequency)
        self.detector = hsv_detector(debug=debug)

        # Set the calibration
        self.calibration_file = calibration_file
        self.reset_calibration()

    def __enter__(self):
        self.camera.start()
        return self

    def __exit__(self, type, value, traceback):
        self.go_down()
        self.hat.disable_servos()
        self.hat.display_power_symbol("TO WAKE", PowerIcon.POWER)
        self.hat.close()
        self.camera.stop()

    def __repr__(self):
        return self.__str__()

    def __str__(self):
        return f"hue: {self.hue}, servo offsets: {self.servo_offsets}, plate offsets: {self.plate_offsets}"

    def reset_calibration(self, calibration_file=None):
        # Use default if not defined
        calibration_file = calibration_file or self.calibration_file

        # Get calibration settings
        if os.path.isfile(calibration_file):
            with open(calibration_file, "r") as f:
                calib = json.load(f)
        else:  # Use defaults
            calib = {
                "ball_hue": 44,
                "plate_offsets": (0.0, 0.0),
                "servo_offsets": (0.0, 0.0, 0.0),
            }

        self.plate_offsets = calib["plate_offsets"]
        self.servo_offsets = calib["servo_offsets"]
        self.hue = calib["ball_hue"]

    def go_up(self):
        # Set the plate to its hover position, experimentally found to be 150
        self.set_servos(150, 150, 150)
        time.sleep(
            0.200)  # Give enough time for the action before turning off servos

    def go_down(self):
        # Set the plate to its lowest position, experimentally found to be 155
        self.set_servos(155, 155, 155)
        time.sleep(
            0.200)  # Give enough time for the action before turning off servos

    def get_buttons(self):
        # Get the buttons after doing a noop (ensure the buttons are up to date)
        self.hat.noop()
        return self.hat.get_buttons()

    def enable_servos(self):
        self.hat.enable_servos()

    def disable_servos(self):
        self.hat.disable_servos()

    def set_servos(self, s1, s2, s3):
        # Note the indexing offset
        s1 += self.servo_offsets[0]
        s2 += self.servo_offsets[1]
        s3 += self.servo_offsets[2]
        self.hat.set_servos((s1, s2, s3))

    def display(
        self,
        text: Optional[str] = None,
        icon: Optional[str] = None,
        scrolling: bool = False,
    ):
        # Optionally display the controller active text
        if icon and text:
            if scrolling:
                raise ValueError(
                    "Cannot display scrolling screen with an icon")
            self.hat.display_string_icon(text, icon)
        elif text:
            if scrolling:
                self.hat.display_long_string(text)
            else:
                self.hat.display_string(text)

    def set_angles(self, pitch, roll):
        s1, s2, s3 = plate_angles_to_servo_positions(pitch, roll)
        self.set_servos(s1, s2, s3)

    def step(self, pitch, roll) -> Buttons:
        self.set_angles(pitch, roll)
        frame, elapsed_time = self.camera()
        buttons = self.hat.get_buttons()
        ball_detected, (ball_center, ball_radius) = self.detector(frame,
                                                                  hue=self.hue)
        return ball_center, ball_detected, buttons