MicroPython: Асинхронное программирование ESP32/ESP8266 – запуск нескольких задач

В этом руководстве мы рассмотрим основы асинхронного программирования на MicroPython для ESP32 и ESP8266 NodeMCU с использованием модуля asyncio. Вы научитесь запускать несколько задач одновременно, создавая иллюзию многозадачности и избегая блокировки кода при выполнении длительных операций.

MicroPython: Асинхронное программирование ESP32/ESP8266 - Запуск нескольких задач

Например, ваша программа может ожидать ответа от сервера и при этом выполнять другие действия, такие как проверка нажатия кнопки или мигание светодиодом.

Асинхронное программирование может быть полезно в проектах, включающих: взаимодействие с базами данных, сетевую коммуникацию (например, при запросе данных с сервера или когда ESP32/ESP8266 работает как веб-сервер), считывание данных с датчиков, вывод информации на экран, получение пользовательского ввода и многое другое.

Предварительные требования

Для работы с этим руководством на вашей плате ESP32 или ESP8266 должна быть установлена прошивка MicroPython. Также вам понадобится IDE для написания и загрузки кода на плату. Мы рекомендуем использовать Thonny IDE или uPyCraft IDE:

Необходимые компоненты

Для выполнения этого руководства вам понадобятся следующие компоненты:

Введение в асинхронное программирование

Асинхронное программирование – это техника, которая позволяет вашей программе запускать потенциально длительную задачу и при этом оставаться отзывчивой к другим событиям, пока эта задача выполняется, вместо того чтобы ожидать её завершения.

Это достигается за счёт выполнения задач в неблокирующем режиме и использования функций обратного вызова для обработки результатов. Таким образом, программа может продолжать выполнять другие задачи, пока ожидает результатов асинхронной задачи. С другой стороны, в синхронном программировании каждая задача должна дождаться завершения предыдущей, прежде чем начать выполнение.

Подводя итог…

  • Асинхронное – это неблокирующая архитектура, при которой выполнение одной задачи не зависит от другой. Задачи могут выполняться одновременно.

  • Синхронное – это блокирующая архитектура, при которой выполнение каждой операции зависит от завершения предыдущей.

Синхронное и асинхронное программирование

Модуль asyncio в MicroPython

MicroPython предоставляет модуль asyncio – облегчённый фреймворк асинхронного ввода-вывода, вдохновлённый модулем asyncio из Python. Подробности об этом модуле MicroPython можно найти по следующей ссылке:

Рассмотрим основные концепции, которые нужно знать для начала использования asyncio в асинхронном программировании:

  • Event Loop (Цикл событий): цикл, который непрерывно проверяет наличие событий (задач или корутин) и выполняет их.

  • Tasks (Задачи): отдельные единицы работы или корутины, которые запланированы для параллельного выполнения внутри цикла событий.

  • Asynchronous Functions (Асинхронные функции): также известные как корутины – это функции, которые могут быть приостановлены и возобновлены без блокировки других операций, что позволяет параллельно выполнять несколько задач.

  • await: ключевое слово, используемое внутри корутин для приостановки выполнения текущей корутины до завершения определённого события или операции, позволяя другим корутинам работать в это время.

Теперь рассмотрим каждую из этих концепций подробнее, а также рабочий процесс и методы модуля asyncio для написания асинхронной программы.

Event Loop (Цикл событий)

Цикл событий является ядром асинхронного программирования. Это цикл, который непрерывно проверяет наличие событий (задач или корутин) и выполняет их. В asyncio вы создаёте цикл событий с помощью asyncio.get_event_loop().

Следующая строка создаёт экземпляр цикла событий с именем loop. Вы можете использовать этот цикл для управления и планирования асинхронных задач.

loop = asyncio.get_event_loop()

Создание задач

В асинхронном программировании задачи представляют единицы работы. Вы создаёте задачи для параллельного выполнения корутин. Корутины – это функции, определённые с ключевым словом async, которые могут быть приостановлены и возобновлены, что позволяет реализовать асинхронное программирование.

Например:

async def blink_led():
    #Код для мигания светодиодом

Затем мы можем использовать loop.create_task() для планирования этой корутины как задачи для выполнения циклом событий.

loop.create_task(blink_led())

Запуск цикла событий

После создания задач вы запускаете цикл событий для их выполнения. Цикл будет непрерывно проверять запланированные задачи и выполнять их. Следующая строка инициализирует цикл событий, как мы видели ранее.

loop = asyncio.get_event_loop()

Затем loop.run_forever() заставляет его работать бесконечно, постоянно проверяя наличие задач для выполнения.

loop.run_forever()

Асинхронные функции – async def

Асинхронная функция определяется с помощью синтаксиса async def. Эти функции, также известные как корутины, могут быть приостановлены с помощью ключевого слова await, позволяя другим корутинам работать в это время. Например:

async def blink_led():
    while True:
        led.toggle()  # Переключение состояния светодиода
        await asyncio.sleep(1)

Здесь blink_led() – это асинхронная функция. Она переключает состояние светодиода и затем делает паузу на 1 секунду с помощью await asyncio.sleep(1). Во время этой паузы другие задачи могут выполняться.

await asyncio.sleep(1)

Асинхронная задержка – asyncio.sleep()

asyncio.sleep() – это корутина, предоставляемая модулем asyncio, которая используется для введения задержки в выполнение корутины на указанную продолжительность без блокировки всего цикла событий. Таким образом, для создания асинхронной функции необходимо заменить все вызовы time.sleep() на asyncio.sleep().

Когда asyncio.sleep() вызывается внутри корутины, она временно приостанавливает выполнение этой корутины, позволяя другим корутинам работать в это время.

Цикл событий продолжает работать, пока корутина приостановлена, проверяя наличие других задач и событий.

По истечении указанного времени (в секундах) приостановленная корутина возобновляет выполнение с того места, где был вызван asyncio.sleep().

await asyncio.sleep() – это неблокирующий способ передать управление другим корутинам в цикле событий без введения фактической задержки. Он эффективно позволяет другим корутинам запуститься немедленно.

await

Ключевое слово await используется внутри корутин для указания точки, в которой корутина может быть временно приостановлена до завершения определённого события.

В следующем примере корутина делает паузу на 1 секунду без блокировки всего цикла событий.

await asyncio.sleep(1)

Подводя итог…

Когда вы запускаете асинхронную программу, цикл событий работает непрерывно, выполняя запланированные задачи одновременно. Каждая корутина получает возможность выполняться по очереди, а операторы await позволяют циклу событий переключаться между задачами, создавая видимость параллелизма.

В результате вы можете выполнять несколько задач одновременно, не дожидаясь завершения каждой из них перед переходом к следующей.

Базовый пример асинхронной программы

Теперь, когда вы знаете основные концепции асинхронного программирования и базовые методы модуля asyncio, создадим простой пример для применения изученных концепций.

Мы создадим две различные корутины. Каждая корутина будет мигать отдельным светодиодом с разной частотой.

  • Зелёный светодиод: GPIO 14 >> мигает каждые две секунды;

  • Синий светодиод: GPIO 12 >> мигает каждые полсекунды.

Для визуальной проверки финального результата подключите два светодиода к ESP32 или ESP8266: один к GPIO 14, другой к GPIO 12.

ESP32

ESP32 асинхронное программирование MicroPython - мигание несколькими светодиодами

ESP8266 NodeMCU

ESP8266 асинхронное программирование MicroPython - мигание несколькими светодиодами

Вот наш пример кода.

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-esp32-esp8266-asynchronous-programming/

import asyncio
from machine import Pin

green_led_pin = 14
green_led = Pin(green_led_pin, Pin.OUT)
blue_led_pin = 12
blue_led = Pin(blue_led_pin, Pin.OUT)

# Определение корутины
async def blink_green_led():
    while True:
        green_led.value(not green_led.value() )
        await asyncio.sleep(2)

# Определение корутины
async def blink_blue_led():
    while True:
        blue_led.value(not blue_led.value())
        await asyncio.sleep(0.5)

# Определение главной функции для запуска цикла событий
async def main():
    # Создание задач для одновременного мигания двух светодиодов
    asyncio.create_task(blink_green_led())
    asyncio.create_task(blink_blue_led())

# Создание и запуск цикла событий
loop = asyncio.get_event_loop()
loop.create_task(main())  # Создание задачи для запуска главной функции
loop.run_forever()  # Запуск цикла событий на неопределённый срок

Исходный код

Как работает код

Рассмотрим код подробнее.

Мы создаём две корутины – по одной для каждого светодиода:

# Определение корутины
async def blink_green_led():
    while True:
        green_led.value(not green_led.value())
        await asyncio.sleep(2)

# Определение корутины
async def blink_blue_led():
    while True:
        blue_led.value(not blue_led.value())
        await asyncio.sleep(0.5)

Внутри каждой корутины мы переключаем состояние светодиода в цикле и ожидаем (асинхронное ожидание) в течение определённого интервала await asyncio.sleep(0.5).

Мы создаём ещё одну корутину main(), которая служит центральной точкой для организации и координации выполнения этих задач.

В корутине main() мы создаём задачи для функций blink_green_led() и blink_blue_led() для их одновременного выполнения.

# Определение главной функции для запуска цикла событий
async def main():
    # Создание задач для одновременного мигания двух светодиодов
    asyncio.create_task(blink_green_led())
    asyncio.create_task(blink_blue_led())

Затем мы создаём цикл событий.

loop = asyncio.get_event_loop()

loop.create_task(main()) создаёт задачу для запуска корутины main().

loop.create_task(main())  # Создание задачи для запуска главной функции

Наконец, метод run_forever() запускает цикл событий на неопределённый срок. Цикл событий непрерывно проверяет наличие задач для выполнения, запускает их параллельно и управляет их выполнением.

loop.run_forever()  # Запуск цикла событий на неопределённый срок

Тестирование кода

Запустите приведённый выше код на вашем ESP32 или ESP8266. Результатом будут два мигающих светодиода с разной частотой.

ESP32 ESP8266 асинхронное программирование - мигание несколькими светодиодами - демонстрация ESP32 ESP8266 асинхронное программирование - мигание несколькими светодиодами - демонстрация ESP32 ESP8266 асинхронное программирование - мигание несколькими светодиодами - демонстрация ESP32 ESP8266 асинхронное программирование - мигание несколькими светодиодами - демонстрация

Заключение

Это лишь простой пример, демонстрирующий, как написать асинхронную программу на MicroPython для ESP32 и ESP8266. Теперь вы должны понимать, что вместо мигания светодиодом можно выполнять более сложные задачи, такие как чтение файлов, запрос данных из интернета, обработка веб-сервера с несколькими клиентами и многое другое.

Надеемся, это руководство окажется полезным. Чтобы узнать больше о MicroPython для ESP32/ESP8266, обратите внимание на наши ресурсы: