Olympe Expectation objects and the Olympe eDSL#

Before introducing more advanced feature, it seems important to take a moment to have a better understanding of the Olympe specific usage of Python operators to compose “Expectation” objects inside the drone() functor.

Olympe Expectation objects#

First, let’s explain what is an “Expectation” object. “Expectation” objects are a special kind of “Future”-like objects from the Python stdlib. People more familiar with Javascript might want to compare “Expectation” classes with the “Promise” class from the standard.

Olympe creates “Expectation” objects whenever a message object is “called”. For example, takeoff_expectation = TakeOff() creates a takeoff_expectation object from the ardrone3.Piloting.TakeOff command message we’ve been using in the previous example.

Simply creating an expectation object has no side effect, you must pass the expectation object to a drone object to “schedule” it. Continuing with our previous example drone(takeoff_expectation) will “schedule” the takeoff_expectation. Here “scheduling” this expectation actually means sending the TakeOff message to the drone and wait for the TakeOff message default expectations (FlyingStateChanged(state="takingoff")). Let us pause on that. This means that an expectation objects: - has a potential side effect when it is “scheduled” by a drone object (here we send the TakeOff message) - may have “sub-expectation(s)” (here the FlyingStateChanged(state="takingoff") event message)

For convenience, the drone() functor returns the expectation object it has received in parameter. This enables the possibility to create and schedule an expectation object in one expression, for example: takeoff_expectation = drone(TakeOff()).

Olympe Expectation eDSL#

Now that we know that one “Expectation” object can be comprised of other expectation objects, like this is the case of the takeoff_expectation in the previous paragraph, we might want to compose expectation objects ourselves.

Olympe supports the composition of expectation objects with 3 Python binary operators: | (“OR”), & (“AND”), and >> (“AND THEN”). This feature has been briefly introduced in the Moving around - Waiting for a 'hovering' flying state previous example where the >> “and then” operator is used to wait for the “hovering” flying state after a moveBy command.

expectation_object = drone(
    moveBy(10, 0, 0, 0)
    >> FlyingStateChanged(state="hovering", _timeout=5)
)

This specific syntax that makes use of the Python operator overloading feature is what is called an “embedded Domain Specific Language” and we might refer to it as the Olympe “Expectation eDSL”.

Here, the drone() functor accepts more than just one command message expectation. The drone() functor takes an expression that may be a combination of command and event messages to process. This expression actually results in the creation of a compound expectation object. The “>>” operator is used to combine two expressions with an “and then” semantic. This example could be read as “Take off and then wait a maximum of 5 seconds for the ‘hovering’ flying state”).

You can choose to schedule an Olympe eDSL expression with or without waiting for the end of its execution, just call .wait() on the expectation object to block the current thread until the expectation object is done (i.e. successful or timedout).

When a compound expectation fails (or times out) you might want to understand what went wrong. To that end, you can use the .explain() that returns a string representation of the compound expectation. The .explain() method highlights in green the part of the expectation that was successful and in red the part of the compound expectation that has failed.

Programmatic eDSL construct#

You also have the ability to construct a compound expectation object programmatically before scheduling it, for example:

expectation_object = (
    moveBy(10, 0, 0, 0)
    >> FlyingStateChanged(state="hovering", _timeout=5)
)
for i in range(3):
    expectation_object = expectation_object
        >> moveBy(10, 0, 0, 0)
        >> FlyingStateChanged(state="hovering", _timeout=5)
    )
drone(expectation_object).wait(60)
assert expectation_object.success()

Each expectation part of a compound expectation may be given a specific _timeout value in seconds that is independent of the global compound expectation timeout value that may be specified later to the .wait() method. When .wait() is called without a timeout value, this method blocks the current thread indefinitely until the expectation succeeds or until a blocking sub-expectation has timedout. In the example above, if any of the FlyingStateChanged expectations times out after 5 seconds, the call to drone(expectation_object).wait(60) returns and expectation_object.success() would return False. Likewise, if the drone takes more than 60 seconds to complete this moveBy, the expectation_object compound expectation times out and expectation_object.success() returns False even if no individual FlyingStateChanged expectation has timedout.

Olympe eDSL operators semantic#

To conclude this quick tour of the expectation eDSL, let’s focus on the specific semantic of each of the supported operator.

The >> “and then” operator is probably the most useful of them. When an “and then” compound expectation is scheduled, the left hand side expectation is scheduled and awaited. When the left hand side expectation is satisfied the right-hand side expectation is scheduled and awaited. If the left-hand side expectation times out, the left-hand side is never scheduled nor awaited and the compound expectation times out. The compound “and then” expectation is successful when the right-hand side is successful.

The & “and” operator schedules and awaits both the left-hand side and right-hand side expectations objects simultaneously. The compound “and” expectation is successful when both the left-hand side and the right hand side expectation are successful without any specific order requirement. The compound expectation times out if the left-hand side or the right-hand side of the expectation times out.

The | “or” operator schedules and awaits both the left-hand side and right-hand side expectations objects simultaneously. The compound “or” expectation is successful if one of the left-hand side and the right hand side expectation is successful (or both). The compound expectation times out if both the left-hand side and the right-hand side of the expectation times out.

You should now understand the basics of Olympe and should be able to write your own scripts. The rest of this guide will walk you through the most advanced (nevertheless important) features of Olympe.