Веб-камера — глаза робота: пишу веб-приложение на FastApi для управления DIY-проектом. Часть 1

Эта статья открывает цикл публикаций о создании open-source веб-приложения для стриминга видео с веб-камеры и управления роботом. Приложение позволит транслировать видео с камеры в реальном времени и отправлять команды управления роботом через интерфейс. Думаю, статья будет интересна веб-программистам, интересующимся работой с видеостримингом и FastAPI, а также робототехникам и энтузиастам DIY-проектов.
Идея проекта возникла из моего интереса к робототехнике и веб-программированию. Ранее в статье DIY-проект: гусеничная платформа с ИК-управлением на Arduino я создал гусеничную платформу на базе Iscra mini, управляемую ИК-пультом, и захотел развить эту платформу.
В качестве камеры я планирую использовать экшн-камеру, которая может работать как веб-камера. Если она окажется несовместимой с Linux, её можно будет заменить обычной веб-камерой. Основная цель проекта — создать гибкое решение, которое будет полезным для разных DIY-проектов.
Стриминг видео с веб-камеры
Я начал эксперименты со стримингом видео, используя универсальные UVC-драйверы в Linux Debian. UVC (USB Video Class) — это стандарт, разработанный USB Implementers Forum (USB-IF), который определяет, как веб-камеры, цифровые видеокамеры или другие видеоустройства должны передавать видеопоток через USB. Универсальные драйверы добавят удобства определения в системе разных веб-камер.
Проще говоря, это программное обеспечение в операционной системе (в моём случае в Linux), которое обеспечивает взаимодействие между ядром и UVC-совместимыми устройствами. В Linux этот драйвер называется uvcvideo и встроен в ядро как часть подсистемы Video4Linux2 (V4L2).
Для стриминга видео с камеры я планирую использовать программу MJPG-Streamer — готовое решение для трансляции видео по HTTP. Само веб-приложение будет подключаться к нему по URL. Использование этой программы сократит время на разработку, экономя его на механизме трансляции видеопотока.
Теперь я установлю MJPG-Streamer и все необходимые зависимости на целевое устройство. Сейчас я тестирую на ПК с Debian, однако этот процесс почти идентичен установке для Raspbian или Arbian, которые работают на платах Raspberry Pi и Orange Pi (в будущем я буду использовать такие платы для постройки робота в статьях DIY).
Шаг 1: Обновление системы
Обновляю список пакетов и устанавливаю их:
sudo apt-get update
sudo apt-get upgrade -y
Эти команды нужны для предотвращения проблем с совместимостью пакетов.
Шаг 2: Установка зависимостей
Теперь устанавливаю зависимости:
sudo apt-get install -y build-essential cmake libjpeg-dev libv4l-dev ffmpeg
Разбор команды:
build-essential
включает gcc и make, необходимые для сборки программ:gcc
— компилятор для языков C/C++;make
— инструмент для автоматизации сборки программ;
libjpeg-dev
— библиотека для обработки JPEG;libv4l-dev
— библиотека для работы с Video4Linux2 (V4L2), которая используется для обработки видео с веб-камер.Cmake
— это кроссплатформенная система управления сборкой, которая автоматически генерирует файлы проектов и Makefile для упрощения процесса компиляции программ;ffmpeg
— инструмент для записи, конвертации, обработки и потоковой передачи аудио- и видеофайлов, поддерживающий множество форматов и кодеков.
Эти зависимости помогут правильно собрать программу для видеострима MJPG-Streamer и обрабатывать видео с веб-камеры.
Для дополнительного тестирования камеры я установлю утилиту v4l-utils:
sudo apt-get install -y v4l-utils
Утилита v4l-utils позволит мне узнать информацию о режимах камеры для её настройки и проверки.
Шаг 3: Установка MJPG-Streamer
Программа MJPG-Streamer позволяет получать видеопоток с веб-камеры, обращаясь к нему через URL-адрес. Я выбрал её, так как это простое готовое решение для видеострима.
Устанавливаю программу для видеострима:
git clone https://github.com/jacksonliam/mjpg-streamer.git
cd mjpg-streamer/mjpg-streamer-experimental
make && sudo make install
export LD_LIBRARY_PATH=/usr/local/lib
Разбор команд:
git clone
https://github.com/jacksonliam/mjpg-streamer.git
— клонирование репозитория MJPG-Streamer;cd mjpg-streamer/mjpg-streamer-experimental
— переход в директорию для сборки;make && sudo make install
— компиляция и установка;export LDLIBRARYPATH=/usr/local/lib
— задаёт переменную окружения для зависимостей.
Шаг 4: Проверка устройства
Теперь мне нужно понять, как обращаться к веб-камере для работы с ней. Для этого я подключаю веб-камеру к устройству через USB. Устройством может быть ПК, ноутбук или плата типа Orange pi.
Проверяю наличие подключенных видеоустройств:
ls /dev/video*
У меня два виртуальных устройства, связанных с моей веб-камерой:
/dev/video0 /dev/video1
Далее я хочу проверить, какие форматы, разрешения экрана и количество fps доступны для моей камеры.
Проверяю видеоформаты:
v4l2-ctl --list-formats-ext
Результат команды (пример):
ioctl: VIDIOC_ENUM_FMT
Type: Video Capture
[0]: 'MJPG' (Motion-JPEG, compressed)
Size: Discrete 1280x720
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 800x600
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 640x480
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 320x240
Interval: Discrete 0.033s (30.000 fps)
[1]: 'YUYV' (YUYV 4:2:2)
Size: Discrete 1280x720
Interval: Discrete 0.100s (10.000 fps)
Size: Discrete 800x600
Interval: Discrete 0.050s (20.000 fps)
Size: Discrete 640x480
Interval: Discrete 0.033s (30.000 fps)
Size: Discrete 320x240
Interval: Discrete 0.033s (30.000 fps)
Разбор информации:
Формат MJPG (Motion-JPEG, сжатый) — обеспечивает более высокую частоту кадров (30 fps) для всех разрешений;
Формат YUYV — передаёт данные в «сыром» виде, без использования алгоритмов сжатия (видео занимает больше места) и имеет ограниченную частоту кадров для высоких разрешений;
Discrete 1280×720, Interval: Discrete 0.033s (30.000 fps) — пример разрешения экрана и интервала, который соответствует количеству кадров в секунду (указан в скобках) для данного разрешения.
Теперь, зная характеристики камеры, мне проще подобрать оптимальный режим работы для видеострима.
Проверяю получение изображения с веб-камеры:
ffplay -f v4l2 -video_size 1280x720 -i /dev/video0

Разбор команды:
-f v4l2
— указывает формат захвата видео (в данном случае Video4Linux2);-video_size 1280x720
— задаёт разрешение экрана (например, 1280×720);-i /dev/video0
— указывает устройство, с которого будет захватываться видео.
Из информации в терминале видно, что для формата YUYV разрешение экрана соответствует 10 fps. Это подтверждается данными команды v4l2-ctl --list-formats-ext
для указанного разрешения. Также появилось изображение с камеры, на котором видна моя гусеничная DIY-платформа. Изображение обновляется в реальном времени. Камера работает корректно, поэтому можно приступать к проверке видеострима.
Шаг 5: Запуск видеострима в MJPG-Streamer
Запускаю видеострим:
mjpg_streamer -i "input_uvc.so -d /dev/video0 -r 640x480 -f 15 -q 80" -o "output_http.so -p 8093 -w /usr/local/share/mjpg-streamer/www" &
Разбор команды:
nput_
uvc.so
— модуль для обработки UVC-видео;-d /dev/video0
— указывает устройство камеры;-r 640x480 и -f 15
— задают разрешение и частоту кадров;-q 80
— качество JPEG;output_
http.so
— модуль для трансляции через HTTP;-p 8093
— порт для HTTP-сервера;&
— запускает процесс в фоне.
Видеострим запущен, осталось проверить его корректную работу.
Шаг 6: Проверка видеопотока
Открываю в браузере URL: http://localhost:8093/? action=stream:

На экране отображается изображение с камеры с частотой 15 fps, при которой видеопоток работает приемлемо. Для слабых устройств (например, Orange Pi Zero H2+) 15 fps достаточно. Более точные настройки для видео подберу на конкретном устройстве в ходе проверки. На мощных устройствах я могу установить до 30 fps для своей веб-камеры.
Стек технологий
Я подобрал следующий стек технологий для веб-приложения:
Python 3.12.0 — язык программирования, выбранный для разработки;
MJPG-Streamer — инструмент для стриминга видео через HTTP;
FastAPI — высокопроизводительный веб-фреймворк с поддержкой асинхронности и Websocket;
Uvicorn — лёгкий и быстрый сервер ASGI для Python, предназначенный для запуска современных веб-приложений и поддерживающий асинхронные операции;
Poetry — инструмент для управления зависимостями и сборки проектов на Python;
Pyenv — утилита для управления версиями Python, которая позволяет устанавливать, переключать и администрировать несколько версий Python на одной системе.
Перед началом написания кода я установил всё перечисленное:
Ссылка на установку Python;
Ссылка на документацию с процессом установки Poetry;
Ссылка на документацию с процессом установки Pyenv.
Виртуальное окружение и установка библиотек
Я создал виртуальное окружение следующими командами:
pyenv install 3.12.0
pyenv virtualenv 3.12.0 web_robot_control
pyenv versions
source ~/.bash_profile
pyenv shell web_robot_control
Разбор команд:
pyenv install 3.12.0
— установка версии Python 3.12.0 с использованием Pyenv;pyenv virtualenv 3.12.0 web_robot_control
— создание виртуального окружения с указанной версией Python и названием;pyenv versions
— отображение списка доступных виртуальных окружений и установленных версий Python;source ~/.bash_profile
— активация новых настроек для использования Pyenv;pyenv shell web_robot_control
— активация виртуального окружения в терминале.
Теперь у проекта есть виртуальная среда, в которую будут установлены все библиотеки и зависимости.
Устанавливаю Poetry в активированное виртуальное окружение web_robot_control:
pip install poetry
Устанавливаю фреймворк FastApi:
poetry add "fastapi[all]"
При установке fastapi[all]
автоматически включаются uvicorn
и другие важные библиотеки. Теперь, когда установлены все библиотеки и зависимости для них, я могу приступать к работе над самим веб-приложением.
Структура проекта
Структура проекта — это логическая организация файлов и папок внутри проекта, которая помогает упростить разработку, поддержку и расширение приложения. Poetry умеет создавать новый проект с готовой стартовой структурой, что очень удобно.
Создаю новый проект:
poetry new web-robot-control
Команда создала проект со следующей структурой:
web-robot-control
├── pyproject.toml
├── README.md
├── src
│ └── web_robot_control
│ └── init.py
└── tests
└── init.py
Разбор структуры проекта:
pyproject.toml — файл, который описывает конфигурацию проекта. В нем можно указать зависимости, инструменты для сборки и настройки (используется в
poetry
,pip
,setuptools
);README.md — текстовый файл с описанием проекта. В нем обычно содержатся инструкции по установке, использованию и дополнительной информации;
src/ — папка с исходным кодом приложения:
web_robot_control/ — основная директория кода проекта;
init.py — файл, который делает эту директорию пакетом Python. Он может быть пустым или содержать код для инициализации пакета.
tests/ — папка с тестами для проверки работоспособности кода.
Содержимое pyproject.toml:
[project]
name = "web-robot-control"
version = "0.1.0"
description = "Web-robot-control - open source веб-приложение для управлением роботом и трансляции видео с веб-камеры."
authors = [
{name = "Arduinum628",email = "message.chaos628@gmail.com"}
]
license = {text = "MIT"}
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi[all] (>=0.115.12,<0.116.0)"
]
[tool.poetry]
packages = [{include = "web_robot_control", from = "src"}]
[build-system]
requires = ["poetry-core>=2.0.0,<3.0.0"]
build-backend = "poetry.core.masonry.api"
Разбор содержимого:
name = «web-robot-control» — название проекта;
version = »0.1.0» — текущая версия проекта;
description = «Web-robot-control — open source веб-приложение для управлением роботом и трансляции видео с веб-камеры.» — краткое описание проекта;
authors = [{name = «Arduinum628», email = «message.chaos628@gmail.com»}] — имя автора и его электронный адрес;
license = {text = «MIT»} — тип лицензии, в данном случае MIT (открытое ПО);
readme = «README.md» — путь к README-файлу проекта;
requires-python = »>=3.12» — требуемая версия Python (не ниже 3.12);
dependencies = [«fastapi[all] (>=0.115.12, <0.116.0)" — список зависимостей, включая FastAPI;
packages = [{include = «name_project_packet», from = «src»}] — указание пакетов проекта и их пути;
requires = [«poetry-core>=2.0.0, <3.0.0"] — зависимости, необходимые для сборки проекта;
build-backend = «poetry.core.masonry.api» — механизм сборки для создания пакета.
Теперь у проекта есть настроенный конфигурационный файл. Подробнее о файле pyproject.toml
можно узнать из документации Poetry.
Написание кода и документации веб-приложения
Теперь, когда настроено виртуальное окружение, установлены все зависимости и создана структура проекта, я перехожу к написанию кода и документации.
Документация
Перед началом разработки важно установить правила оформления веток и коммитов. Поскольку это open-source проект, другие разработчики, которые захотят дополнить приложение, должны иметь чёткие рекомендации по работе с ветками и коммитами, а также описание проекта. Это помогает организовать процесс разработки, избежать хаоса и понимать разработчикам и пользователям для чего приложение написано. Документация хранится в файле README.md, который уже был ранее создан командой poetry new web-robot-control
. Осталось его наполнить содержимым.
Содержимое README.md:
# Web-robot-control
**Web-robot-control** - open source веб-приложение для управлением роботом и трансляции видео с веб-камеры.
## Запуск приложения
**Запуск для локальной разработки (бекенд)**: `poetry run uvicorn web_robot_control.main:app --host server_ip --port port_number`
**Todo:** создать Python-функцию для запуска веб-приложения и добавить её в скрипты Poetry
Как оформлять ветки и коммиты
Пример ветки `user_name/name_task`
- **user_name** (имя пользователя);
- **name_task** (название задачи).
Пример коммита `refactor: renaming a variable`
- **feat:** (новая функционал кода, БЕЗ учёта функционала для сборок);
- **devops:** (функционал для сборки, - добавление, удаление и исправление);
- **fix:** (исправление ошибок функционального кода);
- **docs:** (изменения в документации);
- **style:** (форматирование, отсутствующие точки с запятой и т.п., без изменения производственного кода);
- **refactor:** (рефакторинг производственного кода, например, переименование переменной);
- **test:** (добавление недостающих тестов, рефакторинг тестов; без изменения производственного кода);
- **chore:** (обновление рутинных задач и т. д.; без изменения производственного кода).
Оформление основано на https://www.conventionalcommits.org/en/v1.0.0/
Правила оформления веток и коммитов основаны на спецификации Conventional Commits. Это помогает структурировать изменения, чтобы они были понятными, единообразными и легко воспринимаемыми.
Кроме того, я добавил описание проекта, чтобы ясно объяснить, что это за приложение и для каких целей оно создано. Также включён пример команды для локального запуска приложения:
poetry run uvicorn web_robot_control.main:app --host server_ip --port port_number
.env.example
Я создал файл .env.example, который служит для описания переменных окружения.
STREAM_URL="url-адрес видеопотока"
В реальном .env файле я добавлю url-адрес видеопотока:
STREAM_URL=http://localhost:8093/?action=stream
settings.py
Я создал файл settings.py для хранения настроек проекта, прочитанных из файла .env, и валидации типов переменных окружения. Для этих целей используется библиотека pydantic_settings. Она предоставляет готовые классы для валидации, чтения .env файла и других задач.
Содержимое settings.py:
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Класс для данных конфига"""
model_config = SettingsConfigDict(
env_file = '.env',
env_file_encoding='utf-8',
extra='ignore'
)
stream_url: str
settings = Settings()
Пояснения к коду settings.py:
class Settings(BaseSettings)
— класс, который сохраняет переменные окружения, загруженные из .env файла, для настройки проекта;model_config = SettingsConfigDict(...)
— переменная для задания конфигурации модели:env_file='.env'
— указывает, что настройки загружаются из файла .env;env_file_encoding='utf-8'
— задаёт кодировку файла .env;extra='ignore'
— игнорирует переменные, которые не описаны в классе;stream_url: str
— переменная для хранения URL-видеострима;settings = Settings()
— создание экземпляра класса с настройками.
views.py
Теперь я приступаю к созданию самого веб-приложения, начиная с файла views.py. Этот файл обрабатывает запросы и отправляет ответы клиенту.
FastAPI поддерживает WebSocket, который я использую для получения команд с фронтенда в реальном времени. Это решение не мешает видеостримингу. Более подробно ознакомиться с WebSocket можно узнать на странице документации.
Содержимое views.py:
from fastapi import APIRouter, WebSocket
from fastapi.requests import Request
from fastapi.responses import HTMLResponse, Response
from fastapi.templating import Jinja2Templates
from starlette.websockets import WebSocketDisconnect
import httpx
from web_robot_control.settings import settings
# Создаем объект роутера
router = APIRouter()
# Создаём объект для рендеринга html-шаблонов
templates = Jinja2Templates(directory='static')
@router.get('/', response_class=HTMLResponse)
async def index(request: Request) -> Response:
"""
Асинхронная функция для получения главной страницы приложения.
"""
return templates.TemplateResponse(
request=request,
name='index.html',
context={'title': 'Web-robot-control - Главная', 'name_robot': 'Bot1'}
)
@router.get('/config')
async def get_config() -> dict:
"""Aсинхронная функция для получения stream_url."""
return {'stream_url': settings.stream_url}
@router.websocket('/ws')
async def websocket_endpoint(websocket: WebSocket) -> None:
# Установка содединения по веб-сокету
await websocket.accept()
try:
async with httpx.AsyncClient() as client:
while True:
# Получение команды от клиента (с веб-сокета)
command = await websocket.receive_text()
print(f'Получена команда: {command}')
# Todo: здесь будет логика валидации команд
# Todo: здесь будет логика обработки команды
except WebSocketDisconnect:
print('WebSocket отключен') # Todo: для вывода ошибок будет настроен logger
# Todo: вместо Exception будут добавлена ловля других ошибок
# (после того как функция будет полностью дописана)
except Exception as err:
err_text = f'Ошибка: {str(err)}'
await websocket.send_text(err_text)
print(err_text)
Импорты и инициализация объектов:
from fastapi import APIRouter, WebSocket
— модули для работы с маршрутами HTTP и WebSocket;from fastapi.responses import HTMLResponse
— класс для возврата HTML-контента в HTTP-ответе;from starlette.websockets import WebSocketDisconnect
— исключение для обработки отключения WebSocket соединения;import httpx
— библиотека для выполнения HTTP-запросов асинхронно;from web_robot_control.settings import settings
— настройки приложения (например, переменные из .env);router = APIRouter()
— объект роутера;templates = Jinja2Templates(directory='static')
— объект для рендеринга HTML-шаблонов.
index () — функция для отображения главной страницы:
@router.get('/', response_class=HTMLResponse)
— связывает функцию с URL '/' для GET-запроса и задаёт тип ответа HTMLResponse;return templates.TemplateResponse(...)
— возвращает HTML-шаблон с контекстом:request=request — передаёт запрос в шаблонизатор;
name='index.html' — указывает имя HTML-шаблона;
context={'title': …, 'name_robot': …} — передаёт данные для использования в шаблоне.
get_config () — функция для получения настроек:
@router.get('/config')
— связывает функцию с URL /config для GET-запроса;return {'stream_url':
settings.stream
_url}
— возвращает словарь с URL видеострима в виде JSON-ответа.
websocket_endpoint () — функция для обработки команд через WebSocket:
@router.websocket('/ws')
— связывает функцию с маршрутом /ws для обработки WebSocket-запросов;await websocket.accept()
— устанавливает соединение между сервером и клиентом;async with httpx.AsyncClient() as client:
— создаёт асинхронного HTTP-клиента (пока заготовка);command = await websocket.receive_text()
— получает команду от клиента через WebSocket;print(f'Получена команда: {command}')
— вывод команды в консоль (для тестирования);except WebSocketDisconnect:
— ловит разрыв соединения и выводит сообщение;except Exception as err:
— ловит общие ошибки и отправляет их клиенту через WebSocket.
Теперь у бекенда есть возможность получать команды от клиента (фронтенда), передавать данные конфига на фронтенд через WebSocket и получать html главной страницы.
main.py
Файл main.py — это главный файл приложения, который отвечает за его создание и запуск. В этом файле подключаются маршруты, статические файлы и другие компоненты приложения.
Содержимое main.py:
from fastapi import FastAPI
from starlette.staticfiles import StaticFiles
from web_robot_control.views import router
# создаем экземпляр FastAPI
app = FastAPI()
# подключаем статические файлы
app.mount('/static', StaticFiles(directory='static'), name='static')
# подключаем роутер
app.include_router(router)
Импорты:
from fastapi import FastAPI
— импорт класса FastAPI, который используется для создания основного приложения;from starlette.staticfiles import StaticFiles
— импорт класса StaticFiles, который отвечает за обслуживание статических файлов (например, CSS, JavaScript, изображения);from web_robot_control.views import router
— импорт маршрутизатора router, в котором определены обработчики запросов (созданные ранее в views.py).
Приложение в FastApi:
app = FastAPI()
— создание экземпляра основного приложения;app.mount('/static', StaticFiles(directory='static'), name='static')
:'/static' — URL, по которому будут доступны статические файлы;
StaticFiles (directory='static') — указывает путь к папке с статическими файлами;
name='static' — имя маршрута, позволяющее ссылаться на него в других частях приложения;
app.include_router(router)
— подключение маршрутизатора, который добавляет маршруты из views.py в приложение.
Бекендная часть готова, теперь осталось написать фронтенд для веб-приложения.
index.html
Я создаю простой фронтенд, который будет выполнять роль клиента. Сначала я разработал файл index.html, предназначенный для отображения кнопок управления и видеострима. Я буду использовать Bootstrap для ускорения и упрощения разработки фронтенда. Bootstrap — это популярный фреймворк для создания адаптивных веб-интерфейсов, который включает готовые стили, компоненты и JavaScript-инструменты для ускорения разработки.
Содержимое index.html:
{{ title }}
Управление роботом {{ name_robot }}
Главные особенности index.html:
— шаблонное выражение для динамического указания заголовка страницы (его передали в context на бэкенде);{{ title }} href="
https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css
" rel="stylesheet">
— подключение CSS-файла из CDN Bootstrap для применения стилей для приложения;— подключение локального JavaScript-файла (command.js), который управляет командами (например, отправка команд WebSocket’у).
— Шаблонное выражение для вывода имени робота и красивое оформление заголовка (выравнивание по центру).Управление роботом {{ name_robot }}
— Элемент для отображения видеопотока от робота, где src будет динамически обновляться через JavaScript;— cекция с кнопками управления роботом (вперёд, назад, влево, вправо).
style.css
Я добавил пользовательские стили, чтобы слегка кастомизировать стандартные стили Bootstrap.
Содержимое style.css:
/* Задает общий фон страницы /
body {
background-color: #f8f9fa;
}
/ Создает тень для карточки, добавляя глубину и визуальную привлекательность /
.card {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/ Устанавливает черный цвет фона для элементов с классами card-command и card-body /
.card-command, .card-body {
background-color: black;
}
/ Устанавливает стили для блока card-command /
.card-command {
width: 100%; / Задает ширину в 100% /
border-top-left-radius: 0; / Убирает закругление верхнего левого угла /
border-top-right-radius: 0; / Убирает закругление верхнего правого угла /
}
/ Определяет стили для горизонтальной линии между элементами /
.line {
background-color: #ffc107; / Задает желтый цвет линии /
height: 6px; / Высота линии /
width: 100%; / Линия занимает всю ширину блока /
}
/ Определяет максимальную ширину изображения и делает его адаптивным /
img {
max-width: 100%; / Ограничивает ширину изображения, чтобы оно не выходило за пределы контейнера /
height: auto; / Автоматически изменяет высоту изображения пропорционально ширине */
}
command.js
Теперь, когда файлы index.html и style.css для главной страницы готовы, я добавил логику для работы клиента. Для этого я создал файл command.js, который отвечает за обновление видеострима, вывод сообщений в консоль для отладки, а также управление WebSocket и передачу команд бекенду.
Содержимое command.js:
// Ждём, пока загрузится весь контент DOM
document.addEventListener("DOMContentLoaded", () => {
// Создаём WebSocket-соединение с сервером
const ws = new WebSocket(ws://${window.location.host}/ws);
// Делаем запрос к серверу на эндпоинт "/config"
fetch('/config')
.then(response => response.json()) // Преобразуем ответ в JSON
.then(data => {
// Получаем значение stream_url из ответа
const streamUrl = data.stream_url;
const videoElement = document.getElementById("video-stream");
// Устанавливаем URL видеопотока в элемент
Особенность моего управления в том, что команда отправляется каждые 10 мс, пока пользователь удерживает кнопку. Задержку уточним экспериментально на реальном роботе. Отправка команд прекращается, как только кнопка отпускается.
Ссылка на получившийся в итоге open-source проект web-robot-control.
Итоговая cтруктура проекта
Структура теперь имеет следующий вид:
web-robot-control
├── src
│ └── web_robot_control
│ ├── pycache
│ ├── init.py
│ ├── main.py
│ ├── settings.py
│ ├── views.py
│ └── static
│ ├── command.js
│ ├── index.html
│ ├── style.css
└── tests
└── init.py
├── .env
├── .env.example
├── .gitignore
├── LICENSE
├── poetry.lock
├── pyproject.toml
└── README.md
Структура проекта соответствует принципу разделению ответственности файлов. Одна из его функций — определять, где должен находится каждый файл, чтобы код оставался упорядоченным, удобным для поддержки и расширения.
Web-приложение в действии
Запускаю веб-приложение:
poetry run uvicorn web_robot_control.main:app --host localhost --port 8095
Разбор команды:
poetry run
— запускает команду внутри виртуальной среды, созданной Poetry;uvicorn web_robot_control.main:app
— запускает ASGI-сервер Uvicorn, указывая:web_robot_control.main
— модуль Python, где находится приложение;app
— экземпляр приложения FastAPI, который будет запущен.
--host
localhost
— сервер будет слушать локальный хост, то есть доступен только на текущем устройстве;--port 8095
— приложение будет запущено на порту 8095.
Работа веб-приложения в браузере по URL http://localhost:8095:
На видео видно, что видеострим успешно работает в реальном времени, а команды «left», «right», «forward», «backward» отображаются в консоли браузера. Я управляю роботом обычным ИК-пультом.
Работа бэкенда:
INFO: Started server process [43146]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://localhost:8095 (Press CTRL+C to quit)
INFO: ::1:37202 - "GET / HTTP/1.1" 200 OK
INFO: ::1:37202 - "GET /static/command.js HTTP/1.1" 304 Not Modified
INFO: ('::1', 47800) - "WebSocket /ws" [accepted]
INFO: ::1:37202 - "GET /config HTTP/1.1" 200 OK
INFO: connection open
Получена команда: forward
Получена команда: right
Получена команда: backward
Получена команда: left
^CINFO: Shutting down
WebSocket отключен
INFO: connection closed
INFO: Waiting for application shutdown.
INFO: Application shutdown complete.
INFO: Finished server process [17634]
Бэкенд успешно принимает команды и выводит результат в терминал:
Получена команда: forward;
Получена команда: right;
Получена команда: backward;
Получена команда: left.
Заключение и планы на будущее
Были установлены все необходимые зависимости и библиотеки для видеострима. Видеострим с веб-камеры успешно протестирован. Настроено виртуальное окружение, а также установлены зависимости для проекта. Написан код, обеспечивающий отправку команд на сервер и отображение видеострима. В результате получилась достойная основа для open-source проекта, которая пока ещё не достигла статуса MVP1 и требует доработки.
Хочу добавить в проект logger — инструмент для ведения логов, который будет помогать отслеживать события во время работы программы. Также планирую реализовать валидацию команд и механизм их отправки роботу. Кроме того, нужно добавить утилиту для робота, которая будет принимать команды с сервера. Без неё робот не получит команды управления. Если у вас есть идеи по развитию проекта, поделитесь ими в комментариях — буду рад услышать ваши предложения!
Автор статьи @Arduinum
НЛО прилетело и оставило здесь промокод для читателей нашего блога:
— 15% на заказ любого VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.