Example #1
0
    def test_feedback_function(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS - 0.1

        # Enter JIBE_ONLY mode from UNKNOWN
        hc = HeadingController(mock_speed, ControlModes.UNKNOWN.value)
        self.assertEqual(hc.getControlModeID(), ControlModes.JIBE_ONLY.value)

        self.assertEqual(
            hc.get_feed_back_gain(-1.2),
            sailbot_constants.KP / (1 + sailbot_constants.CP * abs(-1.2)),
        )
Example #2
0
    def test_reachedHeading_exitTackable(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS + 0.1

        # Enter TACKABLE from UNKNOWN
        hc = HeadingController(mock_speed, ControlModes.UNKNOWN.value)
        mock_heading_error = -0.5 * sailbot_constants.MIN_HEADING_ERROR_FOR_SWITCH

        # A switch occurs
        self.assertTrue(
            hc.switchControlMode(heading_error=mock_heading_error,
                                 boat_speed=mock_speed))

        # Should not have changed modes
        self.assertEqual(hc.getControlModeID(), ControlModes.TACKABLE.value)
Example #3
0
    def test_noExit_tackableMode(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS + 0.1
        mock_heading_error = 2 * sailbot_constants.MIN_HEADING_ERROR_FOR_SWITCH

        # Enter TACKABLE mode from UNKNOWN
        hc = HeadingController(mock_speed, ControlModes.UNKNOWN.value)

        # Adjust parameters
        mock_speed *= 2
        mock_heading_error = 1.5 * sailbot_constants.MIN_HEADING_ERROR_FOR_SWITCH

        # No switch
        self.assertFalse(
            hc.switchControlMode(heading_error=mock_heading_error,
                                 boat_speed=mock_speed))
        self.assertEqual(hc.getControlModeID(), ControlModes.TACKABLE.value)
Example #4
0
    def test_slowSpeed_exitTackable(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS + 0.1
        mock_heading_error = 2 * sailbot_constants.MIN_HEADING_ERROR_FOR_SWITCH

        # Enter TACKABLE from UNKNOWN
        hc = HeadingController(mock_speed, ControlModes.UNKNOWN.value)

        # Make speed low
        mock_speed -= 0.2

        self.assertTrue(
            hc.switchControlMode(heading_error=mock_heading_error,
                                 boat_speed=mock_speed))

        # Should not be in TACKABLE or UNKNOWN
        self.assertNotEqual(hc.getControlModeID(), ControlModes.TACKABLE.value)
        self.assertNotEqual(hc.getControlModeID(), ControlModes.UNKNOWN.value)
Example #5
0
    def test_tack_error_function(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS + 0.1

        # Enter TACKABLE mode from UNKNOWN
        hc = HeadingController(mock_speed, ControlModes.UNKNOWN.value)
        self.assertEqual(hc.getControlModeID(), ControlModes.TACKABLE.value)

        mock_current_heading = math.pi
        mock_desired_heading = math.pi / 2
        mock_wind_angle = math.pi / 4

        # Error function in heading controller should be the same as tack controller error functions
        self.assertAlmostEqual(
            hc.get_heading_error(mock_current_heading, mock_desired_heading,
                                 mock_wind_angle),
            TackController.get_heading_error_tackable(mock_desired_heading,
                                                      mock_current_heading))
Example #6
0
    def test_tack_to_jibe(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS + 0.1
        mock_heading_error = 2 * sailbot_constants.MIN_HEADING_ERROR_FOR_SWITCH

        # Enter TACKABLE from UNKNOWN
        hc = HeadingController(mock_speed, ControlModes.UNKNOWN.value)
        self.assertEqual(hc.getControlModeID(), ControlModes.TACKABLE.value)

        # Adjust parameters to switch to JIBE_ONLY
        mock_speed = 0.5 * sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS

        # A switch should occur
        self.assertTrue(
            hc.switchControlMode(heading_error=mock_heading_error,
                                 boat_speed=mock_speed))

        # Should now be in JIBE_ONLY mode
        self.assertEqual(hc.getControlModeID(), ControlModes.JIBE_ONLY.value)
Example #7
0
    def test_fixed_low_power(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS - 0.1
        mock_heading_error = 2 * sailbot_constants.MIN_HEADING_ERROR_FOR_SWITCH
        fixedMode = ControlModes.LOW_POWER.value

        # Enter LOWER_POWER from UNKNOWN
        hc = HeadingController(mock_speed,
                               ControlModes.UNKNOWN.value,
                               fixedControlMode=fixedMode)
        self.assertEqual(hc.getControlModeID(), ControlModes.LOW_POWER.value)

        # A switch should NOT occur
        self.assertFalse(
            hc.switchControlMode(heading_error=mock_heading_error,
                                 boat_speed=mock_speed))

        # Should now be in LOW_POWER mode
        self.assertEqual(hc.getControlModeID(), ControlModes.LOW_POWER.value)
        self.assertTrue(hc.controlModeIsFixed)
Example #8
0
    def test_jibe_error_function(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS - 0.1

        # Enter JIBE_ONLY mode from UNKNOWN
        hc = HeadingController(mock_speed, ControlModes.UNKNOWN.value)
        self.assertEqual(hc.getControlModeID(), ControlModes.JIBE_ONLY.value)

        mock_current_heading = -math.pi
        mock_desired_heading = math.pi / 2
        mock_wind_angle = math.pi / 4

        jibe_direction = JibeOnlyRudderController.get_jibe_controller_direction(
            mock_current_heading, mock_desired_heading, mock_wind_angle)

        # Error function in heading controller should be the same as jibe controller error functions
        self.assertAlmostEqual(
            hc.get_heading_error(mock_current_heading, mock_desired_heading,
                                 mock_wind_angle),
            JibeOnlyRudderController.get_jibe_controller_error(
                mock_current_heading, mock_desired_heading, jibe_direction))
Example #9
0
    def test_tack_disabled_low_power(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS + 0.1
        mock_heading_error = 2 * sailbot_constants.MIN_HEADING_ERROR_FOR_SWITCH

        # Enter TACKABLE from UNKNOWN
        hc = HeadingController(mock_speed, ControlModes.UNKNOWN.value, True)
        self.assertEqual(hc.getControlModeID(), ControlModes.TACKABLE.value)

        # Adjust parameters to switch to LOW POWER
        low_battery = 0
        low_wind = 1

        # A switch should NOT occur
        self.assertFalse(
            hc.switchControlMode(heading_error=mock_heading_error,
                                 boat_speed=mock_speed,
                                 low_battery_level=low_battery,
                                 low_wind=low_wind))

        # Should now be in TACKABLE mode and not in LOW_POWER mode
        self.assertEqual(hc.getControlModeID(), ControlModes.TACKABLE.value)
Example #10
0
    def test_switch_from_low_power(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS - 0.1

        # Can be anything
        mock_heading_error = 2 * sailbot_constants.MIN_HEADING_ERROR_FOR_SWITCH

        # Enter JIBE_ONLY from UNKNOWN
        hc = HeadingController(mock_speed, ControlModes.UNKNOWN.value)
        self.assertEqual(hc.getControlModeID(), ControlModes.JIBE_ONLY.value)

        # Adjust parameters to switch to LOW POWER
        # Only one or both of these need to be 1
        low_battery = 1
        low_wind = 1

        # A switch should occur
        self.assertTrue(
            hc.switchControlMode(heading_error=mock_heading_error,
                                 boat_speed=mock_speed,
                                 low_battery_level=low_battery,
                                 low_wind=low_wind))

        # Adjust parameters to switch to TACKABLE
        mock_speed = 2 * sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS
        mock_heading_error = 0.5 * sailbot_constants.MIN_HEADING_ERROR_FOR_SWITCH

        # A switch should occur
        self.assertTrue(
            hc.switchControlMode(heading_error=mock_heading_error,
                                 boat_speed=mock_speed))

        # Should now be in TACKABLE mode
        self.assertEqual(hc.getControlModeID(), ControlModes.TACKABLE.value)
Example #11
0
    def test_fixed_tack_mode(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS + 0.1
        mock_heading_error = 2 * sailbot_constants.MIN_HEADING_ERROR_FOR_SWITCH
        fixedMode = ControlModes.TACKABLE.value

        # Enter TACKABLE from UNKNOWN
        hc = HeadingController(mock_speed,
                               ControlModes.UNKNOWN.value,
                               fixedControlMode=fixedMode)
        self.assertEqual(hc.getControlModeID(), ControlModes.TACKABLE.value)

        # Adjust parameters to switch to JIBE_ONLY
        mock_speed = 0.5 * sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS

        # A switch should NOT occur
        self.assertFalse(
            hc.switchControlMode(heading_error=mock_heading_error,
                                 boat_speed=mock_speed))

        # Should still be in TACKABLE mode
        self.assertEqual(hc.getControlModeID(), ControlModes.TACKABLE.value)
        self.assertTrue(hc.controlModeIsFixed)
Example #12
0
    def test_feedback_function(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS - 0.1

        # Enter JIBE_ONLY mode from UNKNOWN
        hc = HeadingController(mock_speed, ControlModes.UNKNOWN.value)
        self.assertEqual(hc.getControlModeID(), ControlModes.JIBE_ONLY.value)

        heading_error = -1.2

        # Generate List of evenly Spaced floats from -4pi to 4pi
        testFloatList = []
        for i in range(0, (int)(8 * math.pi / 0.01)):
            testFloatList.append(-4 * math.pi + 0.01)

        self.assertEqual(
            hc.get_feed_back_gain(-1.2, 0),
            sailbot_constants.KP /
            (1 + sailbot_constants.CP * abs(heading_error)),
        )

        self.assertAlmostEqual(
            hc.get_feed_back_gain(-1.2, math.pi),
            sailbot_constants.MAX_ABS_RUDDER_ANGLE_RAD_IRONS /
            (abs(heading_error) + 0.01),
        )

        # Test Symmetry of function for all possible cases
        for windAngle in testFloatList:
            for headingError in testFloatList:
                self.assertAlmostEqual(
                    hc.get_feed_back_gain(headingError, windAngle),
                    hc.get_feed_back_gain(-headingError, windAngle),
                )

        # Ensure function is positive and works for all possible cases
        for windAngle in testFloatList:
            for headingError in testFloatList:
                self.assertGreaterEqual(
                    hc.get_feed_back_gain(headingError, windAngle),
                    0,
                )
Example #13
0
import sailbot_constants
import rospy
import threading
from sailbot_msg.msg import actuation_angle, heading, Sensors
import math

lock = threading.Lock()
# define global variables for the needed topics
headingSetPointRad = None
headingMeasureRad = None
apparentWindAngleRad = None
groundspeedKnots = None
rudderAngleRad = 0
sailAngle = 0
jibAngle = 0
controller = HeadingController(boat_speed=0)

rudder_winch_actuation_angle_pub = rospy.Publisher(
    "/rudder_winch_actuation_angle", actuation_angle, queue_size=1)


def sensorsCallBack(sensors_msg_instance):
    lock.acquire()

    global headingMeasureRad, apparentWindAngleRad, groundspeedKnots
    headingMeasureRad = sensors_msg_instance.gps_can_true_heading_degrees * math.pi / 180
    apparentWindAngleRad = sensors_msg_instance.wind_sensor_1_angle_degrees * math.pi / 180
    groundspeedKnots = sensors_msg_instance.gps_can_groundspeed_knots

    publishRudderWinchAngle()
    lock.release()
Example #14
0
    def test_initialize_jibeMode(self):
        mock_speed = sailbot_constants.SPEED_THRESHOLD_FOR_JIBING_KNOTS - 0.1

        hc = HeadingController(mock_speed, ControlModes.UNKNOWN.value)
        self.assertEqual(hc.getControlModeID(), ControlModes.JIBE_ONLY.value)
Example #15
0
 def test_badControlModeInput_ValueError(self):
     with self.assertRaises(ValueError):
         HeadingController(0.1, -1)
Example #16
0
lock = threading.Lock()

# Global variables for configuring controller (should be command line arguments ideally)
DISABLE_LOW_POWER = False
"""
No Fixed State: None
Jibe: 1
Tack: 2
Low Power: 3
"""
FIXED_CTRL_STATE = None
WINCH_QUANTIZATION_PARAMETER = 4

# Boat control logic
controller = HeadingController(boat_speed=0,
                               disableLowPower=DISABLE_LOW_POWER,
                               fixedControlMode=FIXED_CTRL_STATE,
                               winchQuantParam=WINCH_QUANTIZATION_PARAMETER)

# Control node publisher
rudder_winch_actuation_angle_pub = rospy.Publisher(
    "/rudder_winch_actuation_angle", actuation_angle, queue_size=1)


def windSensorCallBack(windsensor_msg_instance):
    global apparentWindAngleRad
    apparentWindAngleDegrees = windsensor_msg_instance.measuredBearingDegrees
    apparentWindAngleRad = apparentWindAngleDegrees * sailbot_constants.DEGREES_TO_RADIANS


def gpsCallBack(gps_msg_instance):
    global headingMeasureRad, groundspeedKnots