Raspberry Pi Pico W: асинхронный веб-сервер (MicroPython)
В этом руководстве вы узнаете, как создать базовый асинхронный локальный веб-сервер на Raspberry Pi Pico W, программируемом на MicroPython, с использованием модуля asyncio. Благодаря асинхронному подходу Raspberry Pi Pico W может обрабатывать несколько клиентов одновременно, а также выполнять другие задачи, ожидая подключения клиентов.
Мы создадим веб-страницу для включения и выключения светодиода, а также для получения случайных значений, генерируемых Pico. В это же время Pico будет непрерывно мигать другим светодиодом и выполнять задачи в цикле, демонстрируя асинхронную природу программы. Этот пример можно легко модифицировать или расширить для управления несколькими выходами и получения данных с датчиков вместо случайных чисел.
Этот проект аналогичен проекту базового веб-сервера, но использует асинхронное программирование.
Хотите узнать больше об асинхронном программировании? Начните с этого руководства: Raspberry Pi Pico: асинхронное программирование – запуск нескольких задач (MicroPython)
Это руководство совместимо только с Raspberry Pi Pico W, который поставляется с поддержкой Wi-Fi. На протяжении всего руководства, когда мы упоминаем Raspberry Pi Pico, мы имеем в виду Raspberry Pi Pico W.
Впервые работаете с Raspberry Pi Pico? Начните здесь: Начало работы с Raspberry Pi Pico
Необходимые условия
Прежде чем продолжить, убедитесь, что вы выполнили следующие условия.
Прошивка MicroPython
Для выполнения этого руководства вам необходимо установить прошивку MicroPython на плату Raspberry Pi Pico. Также вам потребуется IDE для написания и загрузки кода на плату.
Рекомендуемой IDE для Raspberry Pi Pico с MicroPython является Thonny IDE. Следуйте следующему руководству, чтобы узнать, как установить Thonny IDE, прошить прошивку MicroPython и загрузить код на плату.
Базовые концепции веб-сервера
Если вы не знакомы с базовыми концепциями веб-сервера, мы рекомендуем сначала ознакомиться с этим руководством:
Обзор проекта асинхронного веб-сервера Raspberry Pi Pico
Давайте кратко рассмотрим проект, который мы будем создавать, чтобы было проще понять код в дальнейшем.
Этот веб-сервер работает точно так же, как в этом проекте, но он является асинхронным и неблокирующим, позволяя вам обрабатывать несколько клиентов одновременно и выполнять другие задачи, ожидая клиентских запросов.
Вот что делает наш пример:
Создает веб-сервер, который обслуживает веб-страницу с:
двумя кнопками для включения и выключения светодиода (GPIO 19)
разделом для отображения случайного значения (можно заменить показаниями датчика)
Запускает параллельную задачу мигания светодиода (GPIO 20)
Также выполняет другие действия в основном цикле, продолжая прослушивать клиентские запросы.
Модуль asyncio MicroPython
Для создания асинхронного веб-сервера мы воспользуемся модулем asyncio MicroPython. Этот модуль уже включен в прошивку MicroPython по умолчанию, поэтому вам не нужно устанавливать дополнительные модули.
Преимущества асинхронного программирования
Модуль asyncio MicroPython позволяет запускать несколько задач параллельно, создавая иллюзию многозадачности и избегая блокировки кода на длительных операциях.
Например, ваша программа может ожидать ответа от сервера и при этом выполнять другие задачи, такие как проверка нажатия кнопки или мигание светодиодом одновременно. Асинхронное программирование может быть очень полезно в случае настройки Raspberry Pi Pico в качестве веб-сервера, поскольку оно позволяет обрабатывать несколько клиентов одновременно, сохраняя при этом возможность выполнять другие задачи в цикле.
Модуль asyncio MicroPython — это легковесный фреймворк асинхронного ввода-вывода, вдохновленный модулем asyncio Python. Вы можете ознакомиться со всеми подробностями об этом модуле MicroPython по следующей ссылке: документация модуля asyncio MicroPython.
Сборка схемы
Для тестирования этого проекта подключите два светодиода к Raspberry Pi Pico. Один светодиод подключен к GPIO 19, а другой — к GPIO 20. Вы можете использовать схему ниже в качестве справки.
Необходимые компоненты
Raspberry Pi Pico W |
2x светодиода |
2x резистора 220 Ом |
Макетная плата |
Соединительные провода |
Вы можете использовать любые другие GPIO, при условии что вы соответствующим образом измените код — ознакомьтесь с распиновкой Raspberry Pi Pico.
Асинхронный веб-сервер Raspberry Pi Pico – код MicroPython
Создайте новый файл в Thonny IDE и скопируйте следующий код.
# Import necessary modules
import network
import asyncio
import socket
import time
import random
from machine import Pin
# Wi-Fi credentials
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
# Create several LEDs
led_blink = Pin(20, Pin.OUT)
led_control = Pin(19, Pin.OUT)
# Initialize variables
state = "OFF"
random_value = 0
# HTML template for the webpage
def webpage(random_value, state):
html = f"""
<!DOCTYPE html>
<html>
<head>
<title>Pico Web Server</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<h1>Raspberry Pi Pico Web Server</h1>
<h2>Led Control</h2>
<form action="./lighton">
<input type="submit" value="Light on" />
</form>
<br>
<form action="./lightoff">
<input type="submit" value="Light off" />
</form>
<p>LED state: {state}</p>
<h2>Fetch New Value</h2>
<form action="./value">
<input type="submit" value="Fetch value" />
</form>
<p>Fetched value: {random_value}</p>
</body>
</html>
"""
return str(html)
# Init Wi-Fi Interface
def init_wifi(ssid, password):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
# Connect to your network
wlan.connect(ssid, password)
# Wait for Wi-Fi connection
connection_timeout = 10
while connection_timeout > 0:
print(wlan.status())
if wlan.status() >= 3:
break
connection_timeout -= 1
print('Waiting for Wi-Fi connection...')
time.sleep(1)
# Check if connection is successful
if wlan.status() != 3:
print('Failed to connect to Wi-Fi')
return False
else:
print('Connection successful!')
network_info = wlan.ifconfig()
print('IP address:', network_info[0])
return True
# Asynchronous functio to handle client's requests
async def handle_client(reader, writer):
global state
print("Client connected")
request_line = await reader.readline()
print('Request:', request_line)
# Skip HTTP request headers
while await reader.readline() != b"\r\n":
pass
request = str(request_line, 'utf-8').split()[1]
print('Request:', request)
# Process the request and update variables
if request == '/lighton?':
print('LED on')
led_control.value(1)
state = 'ON'
elif request == '/lightoff?':
print('LED off')
led_control.value(0)
state = 'OFF'
elif request == '/value?':
global random_value
random_value = random.randint(0, 20)
# Generate HTML response
response = webpage(random_value, state)
# Send the HTTP response and close the connection
writer.write('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
writer.write(response)
await writer.drain()
await writer.wait_closed()
print('Client Disconnected')
async def blink_led():
while True:
led_blink.toggle() # Toggle LED state
await asyncio.sleep(0.5) # Blink interval
async def main():
if not init_wifi(ssid, password):
print('Exiting program.')
return
# Start the server and run the event loop
print('Setting up server')
server = asyncio.start_server(handle_client, "0.0.0.0", 80)
asyncio.create_task(server)
asyncio.create_task(blink_led())
while True:
# Add other tasks that you might need to do in the loop
await asyncio.sleep(5)
print('This message will be printed every 5 seconds')
# Create an Event Loop
loop = asyncio.get_event_loop()
# Create a task to run the main function
loop.create_task(main())
try:
# Run the event loop indefinitely
loop.run_forever()
except Exception as e:
print('Error occurred: ', e)
except KeyboardInterrupt:
print('Program Interrupted by the user')
Просмотреть исходный код на GitHub
Этот код создает асинхронный веб-сервер. Перед запуском кода обязательно укажите ваши сетевые учетные данные.
# Wi-Fi credentials
ssid = 'REPLACE_WITH_YOUR_SSID'
password = 'REPLACE_WITH_YOUR_PASSWORD'
Как работает код
Этот код аналогичен коду из предыдущего проекта. В этой статье мы рассмотрим только те части кода, которые относятся к асинхронному веб-серверу. Поэтому, если вы ещё не знакомы с базовым сокет-сервером, прочитайте объяснение кода этого проекта.
Импорт модуля asyncio
Сначала нам нужно импортировать модуль asyncio, чтобы использовать его функциональные возможности для асинхронного программирования.
import asyncio
Асинхронные функции
Функции handle_client() и blink_led() определены как асинхронные функции (определяются ключевыми словами async def), что позволяет им работать параллельно и кооперативно с другими корутинами.
Главная функция main() также определена как асинхронная функция для организации запуска сервера и других задач.
Цикл событий asyncio.get_event_loop() создается для управления и выполнения асинхронных задач.
loop = asyncio.get_event_loop()
Главная функция main() регистрируется как задача в цикле событий с помощью loop.create_task(main()), что позволяет планировать и выполнять её асинхронно.
loop.create_task(main())
Настройка асинхронного сервера
Вместо использования традиционного сокет-программирования, функция asyncio asyncio.start_server() используется для создания асинхронного TCP-сервера.
server = asyncio.start_server(handle_client, "0.0.0.0", 80)
Корутина handle_client() передается как функция обратного вызова для обработки входящих клиентских подключений.
По сути, предыдущая строка настраивает сервер, который прослушивает входящие подключения на указанном хосте и порту. Она возвращает объект, представляющий задачу сервера (server).
Когда сервер принимает новое клиентское подключение, он вызывает функцию handle_client (или любую другую корутину, указанную в качестве обработчика клиента).
Подключение клиента
Когда клиент подключается к серверу, asyncio.start_server() создает пару потоковых объектов: StreamReader для чтения данных от клиента и StreamWriter для записи данных клиенту. Узнайте больше о потоковых TCP-подключениях с asyncio.
Эти потоковые объекты (reader и writer) передаются в качестве параметров корутине, указанной в качестве обработчика клиента (handle_client в данном случае).
async def handle_client(reader, writer):
Внутри функции handle_client() объекты reader и writer используются для асинхронного чтения данных от клиента и записи данных обратно клиенту соответственно.
Объект reader (StreamReader) предоставляет методы для асинхронного чтения данных из сокетного соединения клиента (await reader.readline()).
print("Client connected")
request_line = await reader.readline()
print('Request:', request_line)
Объект writer (StreamWriter) предоставляет методы для асинхронной записи данных в сокетное соединение клиента (writer.write()).
writer.write('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
writer.write(response)
Объекты reader и writer предоставляются функцией asyncio.start_server() при подключении клиента к серверу. Эти объекты позволяют серверу асинхронно взаимодействовать с клиентом, читая входящие данные и записывая исходящие данные.
Параллельное выполнение задач
Функция asyncio asyncio.create_task() используется для создания задач обработки клиентских подключений и мигания светодиодом параллельно. Это позволяет серверу принимать несколько клиентских подключений и мигать светодиодом одновременно без блокировки.
asyncio.create_task(server)
asyncio.create_task(blink_led())
Кроме того, в функции main() также есть другие асинхронные операции, использующие await asyncio.sleep() — неблокирующую версию time.sleep(). В данном случае мы просто выводим сообщение в оболочку каждые 5 секунд, но вы можете добавить любые другие задачи, которые вам нужны. При необходимости можно добавить больше операций.
while True:
# Add other tasks that you might need to do in the loop
await asyncio.sleep(5)
print('This message will be printed every 5 seconds')
Цикл событий (loop.run_forever()) работает бесконечно, непрерывно обрабатывая задачи и обрабатывая клиентские подключения. Это гарантирует, что сервер остается активным и отзывчивым к входящим запросам.
loop.run_forever()
Подводя итог, мы используем асинхронные операции по всему коду, такие как чтение от клиентов await reader.readline(), отправка ответов writer.write() и переключение светодиода await asyncio.sleep().
Эти операции позволяют серверу выполнять другие задачи, ожидая завершения операций, делая его неблокирующим и более эффективным.
Тестирование кода
Запустите предыдущий код на вашем Raspberry Pi Pico.
После подключения к интернету откройте веб-браузер в той же сети и введите IP-адрес Pico для доступа к веб-серверу.
Вы можете открыть несколько вкладок в веб-браузере или несколько устройств одновременно без каких-либо проблем, при этом по-прежнему имея возможность включать и выключать светодиод и запрашивать новое случайное значение.
Всё это может обрабатываться одновременно с миганием светодиода и выводом сообщений в оболочку с разной частотой.
Теперь вы должны понимать, что вместо мигания светодиодом и вывода сообщений вы можете запускать более сложные асинхронные задачи, и Pico по-прежнему сможет обрабатывать веб-сервер и обслуживать нескольких клиентов.
Загрузка кода на Raspberry Pi Pico
Если вы хотите, чтобы Raspberry Pi Pico запускал веб-сервер без подключения к компьютеру, вам нужно загрузить код как main.py в файловую систему Raspberry Pi Pico. Для этого, после копирования кода в новый файл, перейдите в File > Save as и выберите Raspberry Pi Pico.
Назовите файл main.py и нажмите OK, чтобы сохранить файл на Raspberry Pi Pico. Теперь он будет запускать файл main.py при загрузке без необходимости подключения к компьютеру.
Заключение
В этом руководстве мы показали вам, как создать асинхронный веб-сервер на Raspberry Pi Pico с использованием модуля asyncio MicroPython. Использование асинхронного подхода позволяет Raspberry Pi Pico обрабатывать несколько клиентов одновременно и при этом выполнять другие задачи параллельно.
Мы надеемся, что это руководство было для вас полезным. Если вы только начинаете работать с веб-серверами на Raspberry Pi Pico, мы рекомендуем начать с более простого примера веб-сервера и затем перейти к асинхронному подходу.