There are many situations where you'll want an event in your code to continue for an amount of time. Often, this is accomplished using time.sleep() as in the following code:

# SPDX-FileCopyrightText: 2017 Kattni Rembor for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import time

from adafruit_circuitplayground.express import cpx

while True:
    cpx.pixels[0] = (abs(cpx.pixels[0][0] - 255), 0, 0)
    time.sleep(0.5)

Here, the first NeoPixel turns on for 0.5 seconds, and then turns off for 0.5 seconds before repeating indefinitely. The usage of time.sleep(0.5) in this code basically says: turn the LED on and wait in that state for half a second, then turn it off and wait in that state for half a second. In many situations, this usage of time works great. However, during time.sleep(), the code is essentially paused. Therefore, the board cannot accept any other inputs or perform any other functions for that period of time. This type of code is referred to as being blocking. In the case of the code above, this is sufficient as the code is not attempting to do anything else during that time.

Waiting Without Blocking

However, for this project, we want to continue processing inputs, so instead of sleeping for 0.5 seconds, we'll process other inputs for 0.5 seconds and change the led when that time expires. To accomplish this, we're going to use time.monotonic(). Where time.sleep() expects an amount of time be provided, time.monotonic() tells us what time it is now, so we can see whether our 0.5 seconds has passed yet. So, we no longer supply an amount of time. Instead, we assign time.monotonic() to two different variables at two different points in the code, and then compare the results.

At any given point in time, time.monotonic() is equal to the number seconds since your board was last power-cycled. (The soft-reboot that occurs with the auto-reload when you save changes to your CircuitPython code, or enter and exit the REPL, does not start it over.) When it is called, it returns a number with a decimal, which is called a float. If, for example, you assign time.monotonic() to a variable, and then call it again to assign into a different variable, each variable is equal to the number of seconds that time.monotonic() was equal to at the time the variables were assigned. You can then subtract the first variable from the second to obtain the amount of time that passed.

time.monotonic() example

Let's take a look at an example. You can type the following into the REPL to follow along.

First we import the time module, then we print the current time.monotonic(). This is to give you an idea of what is going on in the background. The next two lines assign x = time.monotonic() and y = time.monotonic() so we have two variables, and points in time, to compare. Then we print(y - x). This gives us the amount of time, in seconds, that passed between assigning time.monotonic() to x and y. We print time.monotonic() again to give you a general idea of the difference. Remember, the two numbers resulting from printing the current time are not exactly the same difference from each other as the two variables due to the amount of time it took to assign the variables and print the results.

Non-Blocking Blink

But, how does this allow us to blink our NeoPixel? The result of the comparison is a period of time. So, if we use that period of time to determine when the state of the LED should change, we can successfully blink the LED in the same way we did in the first program. Let's find out what that looks like!

# SPDX-FileCopyrightText: 2018 Kattni Rembor for Adafruit Industries
#
# SPDX-License-Identifier: MIT

import time

from adafruit_circuitplayground.express import cpx

blink_speed = 0.5

cpx.pixels[0] = (0, 0, 0)

initial_time = time.monotonic()
while True:
    current_time = time.monotonic()
    if current_time - initial_time > blink_speed:
        initial_time = current_time
        cpx.pixels[0] = (abs(cpx.pixels[0][0] - 255), 0, 0)

This does exactly the same thing as before! It's exactly what we wanted. Now, let's break it down.

Before the loop begins, we create a blink_speed variable and set it to 0.5. This allows for easier configuration of the blink speed later if you wanted to alter it. Next, we set the initial state of the LED to be (0, 0, 0), or off. Then, we call time.monotonic() for the first time by setting initial_time = time.monotonic(). This applies once when the program begins, before it enters the loop.

Once the code enters the loop, we set current_time = time.monotonic(). We call it a second time to compare to the first, to see if enough time has passed. Then we say if current_time minus initial_time is greater than blink_speed, do two things: set initial_time to now be equal to current_time and cycle the NeoPixel to the next state. Setting initial_time = current_time means it starts the time period over again. Essentially, every time the difference reaches 0.5 seconds, it cycles the state and starts again, repeating indefinitely.

Why would we do it this way? It seems way more complicated! We do it this way because this allows us to do other things while the NeoPixel is blinking. Instead of pausing the code to leave the LED in a red or off state, the code continues to run. The code for the Spoka lamp allows you to change speed and brightness without halting the rainbow animation, and this is how we accomplish that!

This guide was first published on Feb 20, 2018. It was last updated on Mar 18, 2024.

This page (Passing Time) was last updated on Mar 18, 2024.

Text editor powered by tinymce.