In this section, we will be tying together what we just did in the previous two sections: animating our creature and measuring the orientation of our SenseHat. Before we begin, it is important to know a little about how sailboats tend to move. The most common motion of Wonder is rolling. The round shape of the ship’s hull and the fact that ships are much longer than they are wide (the term for a ship’s width is its “beam”) make it much easier for ships to roll. That being said, Wonder is always moving a little bit in every one of these directions: no wave exclusively pushes Wonder up in a pitching motion, it also causes it to roll and yaw a little bit. Additionally, the sails—all different sizes and in different locations—exert their own unique forces on Wonder that are changing constantly. This is why steering Wonder is a constant task of small corrections.

You get to decide how you want the ship’s motion to affect your creature’s movement. In this example, we will combine all three movements to create our own custom movement variable. Let’s assign a percentage value to each motion that represents how much weight it will have in our motion variable. You should pick whatever values you want here:

Roll (refers to the pitch of Wonder): 10%

Pitch (refers to both the roll and yaw of Wonder): 60%

Yaw (refers to both the roll and yaw of Wonder): 30%

With this arrangement, the ship’s roll to port and starboard will be the primary drivers of our animation. We will test this, so you can always change these values based on your testing.

#### Rotation sensors
while True:
    orientation = sense.get_orientation_degrees()
    roll = orientation["roll"]
    pitch = orientation["pitch"]
    yaw = orientation["yaw"]
    movement = roll * 0.1 + pitch * 0.6 + yaw * 0.3
    print(movement)
#### Rotation sensors
while True:
    orientation = sense.get_orientation_degrees()
    roll = orientation["roll"]
    pitch = orientation["pitch"]
    yaw = orientation["yaw"]
    if pitch > 180:
        pitch = pitch - 360
    movement = roll * 0.1 + pitch * 0.6 + yaw * 0.3
    print(movement)

Now we can run this code and move our Raspberry Pi to simulate the motion of Wonder. Our Raspberry Pi is installed such that it leans slightly aft (back towards the stern), which is why both pitch and yaw are engaged during a roll.

What you might notice is that, when rolling from port to starboard, that the value of movement changes abruptly from a number >200 to <100. data-preserve-html-node="true" This is because the “pitch” value is moving between 359° and 0°. To keep these values within a tighter range, we can subtract 360 from the value of pitch if it is greater than 180.

#### Rotation sensors
orientation = sense.get_orientation_degrees()
roll = orientation["roll"]
pitch = orientation["pitch"]
yaw = orientation["yaw"]
if pitch > 180:
    pitch = pitch - 360

# Starting orientation values
starting_position = roll * 0.1 + pitch * 0.6 + yaw * 0.3
movement = starting_position
direction = "starboard"

while True:
    orientation = sense.get_orientation_degrees()
    roll = orientation["roll"]
    pitch = orientation["pitch"]
    yaw = orientation["yaw"]
    if pitch > 180:
        pitch = pitch - 360
    new_movement = roll * 0.1 + pitch * 0.6 + yaw * 0.3

    if new_movement < movement and direction == "starboard":
        sense.flip_h()
        direction = "port"

    if new_movement > movement and direction == "port":
        sense.flip_h()
        direction = "starboard"

    movement = new_movement
    print(movement)

Remember, there are two movements that we can control: location via set_orientation() and direction using flip_h(). Here is where the program will start to get tricky, so take it slowly and make sure you understand the changes we are making to the code rather than just copy/pasting. Only if you understand why the code is written the way it is will you be able to make your own custom edits. Let’s walk through the block of code above:

Prior to entering the loop, we gather our starting_position and our first movement reading. These values are identical. We could have written movement = roll * 0.1 + pitch * 0.6 + yaw * 0.3 and the result would be the same. We also set our direction variable.

As soon as we enter the loop, we grab a new position reading and store it in the variable new_movement. As the loop runs, this will allow us to compare the reading from the previous lap to the current lap of the loop.

The first animation we will create is direction. Since it does not make sense for our whale to swim backwards, we always want the whale facing the direction of its motion. In fact, there may be times when all the whale does is turn around but not move. Or the motion might be so strong that the whale changes direction and moves across two set_orientation() positions very quickly. Either we want to ensure direction is correct before our whale moves.

In our movement formula, the movement value increases when Wonder leans to starboard (i.e. the Raspberry Pi leans to its left). Therefore, we want to make sure the whale is facing starboard whenever movement is increasing. Conversely, we want to flip the whale around and have it face port when movement is decreasing. If the whale is already facing the correct direction, we do not want to flip it. That is why our if statement checks whether movement is increasing/decreasing as well as whether the direction is already correct. Of course, if we flip the whale, we need to update direction to the whale’s new direction. The last thing we do at the end of the loop is update movement to be the value we gathered from this lap, which we will compare to the new_movement value we get in the next lap.

If you run this code, you will notice that the whale almost constantly flips back and forth in almost a strobe light fashion. We don’t want this. This is happening because the values of our orientation sensors are changing by small amounts even when the SenseHat is barely moving, but those amounts are still triggering our if statements. We can add a line of code that says “Hey, if the SenseHat doesn’t move at least by a value of 1, don’t do anything.” The command continue tells the Pi to immediately end this iteration of the loop and start the next lap. We use absolute values because we don’t care if the value changes by +1 or -1, just as long as it’s at least 1.

Much better.

Since you have no idea which way Wonder will be leaning when your code is run on board, it is a good idea to use relative values instead of absolute values (but this is something you can change in your code if you want!). If we were using absolute, we would find the exact value of our SenseHat being perfectly vertical and then animate based on deviations from that value. With relative values, we grab a reading right when the program starts running and then animate based on deviations from that measured value. If you used the absolute value method but Wonder was leaning to port by 5° for the duration of your program, your creature would never move; it would just stay in the left rotation position. This demo will use the relative method. To accomplish this, we will grab a motion sample before we enter our loop that we do not change for the duration of the program.

We also add a variable called direction to track which direction our creature is facing. Our whale is facing the left of the LED screen when the program starts, but the screen of the SenseHat on Wonder will be facing the bow of the ship. In order to look at the LED screen, you will be facing the stern of Wonder, so the whale is actually looking towards the right side of the ship, so we set this to “starboard” (the ship word for “right”, “port” is the ship word for “left”).

if abs(movement-new_movement) < 1:
    continue

Here is our updated code with comment headers:

#### Rotation sensors
orientation = sense.get_orientation_degrees()
roll = orientation["roll"]
pitch = orientation["pitch"]
yaw = orientation["yaw"]
if pitch > 180:
    pitch = pitch - 360

# Starting orientation values
starting_position = roll * 0.1 + pitch * 0.6 + yaw * 0.3
movement = starting_position
direction = "starboard"

while True:
    # Poll the sensors at the start of the loop
    orientation = sense.get_orientation_degrees()
    roll = orientation["roll"]
    pitch = orientation["pitch"]
    yaw = orientation["yaw"]
    if pitch > 180:
        pitch = pitch - 360

    # Get a fresh orientation reading for this lap	
    new_movement = roll * 0.1 + pitch * 0.6 + yaw * 0.3

    # No direction change or movement if sensor difference is too small
    if abs(movement-new_movement) < 1:
        continue

    # Change creature direction to port
    if new_movement < movement and direction == "starboard":
        sense.flip_h()
        direction = "port"

    # Change creature direction to starboard
    if new_movement > movement and direction == "port":
        sense.flip_h()
        direction = "starboard"

    # Store this lap's movement value for comparison on the next iteration of the loop 
    movement = new_movement

    # Merely for observing our sensor data for debugging
    print(movement)

Direction should be working properly at this point. Now onto rotation.