Привет, Хабр!
Сегодня я поделюсь опытом работы с протоколом UDP вместе с микроконтроллером ESP8266, где я управлял светодиодом, а также получал температуру с датчика DHT11. Всё управление будет происходить из Android-приложения, написание логики которого также будет рассмотрено.

Почему UDP?
Немного теории о природе протокола UDP.UDP (User Datagram Protocol) — это один из основных протоколов транспортного уровня в стеке TCP/IP. Он используется для отправки и получения данных между устройствами в сети без установления соединения, что позволяет обмениваться данными с большей скоростью, однако при этом целостность данных может быть повреждена, но для наших целей он будет оптимальным, поскольку наши пакеты не будут содержать критичных данных, но их обмен будет проходить быстрее.

Схема устройства
Основным управляющим устройством выступает микроконтроллер ESP8266, снабжённый модулем Wi-Fi, который будет работать в роли сервера. К микроконтроллеру также подключён светодиод с резистором на 20 кОм (у меня не было под рукой резистора меньшего номинала, поэтому светодиод может светиться тускло), а также температурный сенсор DHT11. Общая схема представлена ниже.

Настраиваем ESP8266
Чтобы настроить работу по UDP, воспользуемся библиотекой WiFiUdp.h
, которая добавит в наш проект поддержку сетевого протокола. Также потребуется библиотека ESP8266WiFi.h
— с её помощью настроим работу микроконтроллера в режиме точки доступа. Ну и, наконец, добавим библиотеку DHT.h
для работы с температурным сенсором.
#include <DHT.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
Для начала подключим все библиотеки и несколько переменных, а также экземпляры нескольких классов: DHT
и WiFiUDP
.
#define DHT_PIN 13
const char* ssid = "ESP8266";
const int port = 4210;
char incomingBytes[20];
DHT dht(DHT_PIN, DHT11);
WiFiUDP udp;
Далее, внутри функции setup()
произведём настройку всей периферии.
void setup() {
WiFi.mode(WIFI_AP);
WiFi.softAP(ssid);
dht.begin();
udp.begin(port);
Serial.begin(9600);
pinMode(15, OUTPUT);
}
В loop()
производится отслеживание входящих UDP-пакетов. За парсинг полученных данных отвечает функция udpHandler
, которой передаётся принятое сообщение.
void loop() {
int packetSize = udp.parsePacket();
if (packetSize){
int len = udp.read(incomingBytes, 20);
incomingBytes[len] = '\0';
if (len){
char* str = (char*)incomingBytes;
udpHandler(str);
}
}
}
Если поступает сообщение "LedOn"
— светодиод загорается, если "LedOff"
— соответственно, выключается. Функция strcmp
сравнивает две строки и возвращает 0, если они совпадают — поэтому используется именно такая конструкция.
Если в UDP-пакете приходит "Temperature"
— считывается текущая температура с сенсора и отправляется обратно по IP и порту.
void udpHandler(char* message){
Serial.println(message);
if (!strcmp(message,"LedOn")){digitalWrite(15, HIGH);}
else if (!strcmp(message,"LedOff")){digitalWrite(15, LOW);}
else if (!strcmp(message, "Temperature")){
float temp = dht.readTemperature();
char replyPacket[10];
snprintf(replyPacket, sizeof(replyPacket), "%.2f", temp);
udp.beginPacket(udp.remoteIP(), udp.remotePort());
udp.write(replyPacket, strlen(replyPacket));
udp.endPacket();
}
На этом работа с ESP8266 завершена — переходим к Android-приложению на Java.
#include <DHT.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#define DHT_PIN 13
const char* ssid = "ESP8266";
const int port = 4210;
char incomingBytes[20];
DHT dht(DHT_PIN, DHT11);
WiFiUDP udp;
void setup() {
WiFi.mode(WIFI_AP);
WiFi.softAP(ssid);
dht.begin();
udp.begin(port);
pinMode(15, OUTPUT);
}
void loop() {
int packetSize = udp.parsePacket();
if (packetSize){
int len = udp.read(incomingBytes, 20);
incomingBytes[len] = '\0';
if (len){
char* str = (char*)incomingBytes;
udpHandler(str);
}
}
}
void udpHandler(char* message){
if (!strcmp(message,"LedOn")){digitalWrite(15, HIGH);}
else if (!strcmp(message,"LedOff")){digitalWrite(15, LOW);}
else if (!strcmp(message, "Temperature")){
float temp = dht.readTemperature();
char replyPacket[10];
snprintf(replyPacket, sizeof(replyPacket), "%.2f", temp);
udp.beginPacket(udp.remoteIP(), udp.remotePort());
udp.write(replyPacket, strlen(replyPacket));
udp.endPacket();
}
}
Пишем "приложение"
Для управления светодиодом и получения температуры создадим три кнопки Button
, а также один TextView
, куда будет выводиться полученная температура. Весь код XML-файла приведён ниже.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/Temp_btn"
android:layout_width="181dp"
android:layout_height="51dp"
android:layout_marginTop="50dp"
android:text="Temperature"
android:textColor="@color/black"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/led_off_btn" />
<Button
android:id="@+id/led_on_btn"
android:layout_width="181dp"
android:layout_height="51dp"
android:layout_marginTop="50dp"
android:text="Led On"
android:textColor="@color/black"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/temp_view" />
<Button
android:id="@+id/led_off_btn"
android:layout_width="181dp"
android:layout_height="51dp"
android:layout_marginTop="50dp"
android:text="Led Off"
android:textColor="@color/black"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/led_on_btn" />
<TextView
android:id="@+id/temp_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:textColor="@color/black"
android:textSize="34sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Также, для работы с сетью, в приложение нужно добавить разрешение на доступ к интернету.
<uses-permission android:name="android.permission.INTERNET" />
Далее напишем логику приложения в классе MainActivity
. Инициализируем несколько переменных для работы с UI соответствующих типов. Присваиваем им значения по их ID из XML.
Один момент: добавим несколько строк, чтобы задать фоновый цвет приложения белым (на некоторых устройствах по умолчанию он может быть другим), а также уберём фиолетовую полосу сверху, которая добавляется при создании новой активности.
Создадим отдельную функцию udpPost
, принимающую в качестве аргумента сообщение для отправки по UDP. Создаём новый поток, поскольку все сетевые операции должны выполняться во вторичном потоке, чтобы не повесить интерфейс.
Для создания UDP-соединения создаём экземпляр класса DatagramSocket
. Затем создаём два буфера: для отправки и получения сообщения. Далее создаём два экземпляра DatagramPacket
— для формирования UDP-пакетов. В первый передаём наше сообщение, IP-адрес микроконтроллера (он статичен, так как микроконтроллер работает как точка доступа) и номер порта.
Далее отправляем сообщение и получаем ответ. После этого переходим в UI-поток и отображаем полученное значение в TextView
— но только если это сообщение о температуре. Если мы управляем светодиодом — ответ не приходит, и выводить ничего не нужно.
void udpPost(String message){
new Thread(new Runnable() {
@Override
public void run() {
try {
DatagramSocket datagramSocket = new DatagramSocket();
byte sendBuffer[] = message.getBytes();
byte[] receiveBuffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(sendBuffer, sendBuffer.length, InetAddress.getByName(ip), port);
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
datagramSocket.send(packet); datagramSocket.receive(receivePacket);
String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
runOnUiThread(new Runnable() {
@Override
public void run() {
if (message != ""){
tempView.setText(message + "°C");
}
}
});
} catch (SocketException | UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
Последним шагом возвращаемся в функцию onCreate()
, где каждой кнопке добавляем обработчик нажатия. Внутри него вызываем созданную нами функцию, передавая соответствующее сообщение.
package com.example.udpclient;import android.annotation.SuppressLint;
import android.graphics.Color;
import android.os.Bundle;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;public class MainActivity extends AppCompatActivity {
ConstraintLayout constraintLayout;
Button ledOn, ledOff, temp;
TextView tempView;
int port = 4210;
String ip = "192.168.4.1";
@SuppressLint("MissingInflatedId")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
EdgeToEdge.enable(this);
setContentView(R.layout.activity_main);
getWindow().setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
);
constraintLayout = findViewById(R.id.main);
constraintLayout.setBackgroundColor(Color.WHITE);
ledOn = findViewById(R.id.led_on_btn);
ledOff = findViewById(R.id.led_off_btn);
temp = findViewById(R.id.Temp_btn);
tempView = findViewById(R.id.temp_view); ledOn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
udpPost("LedOn");
}
});
ledOff.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
udpPost("LedOff");
}
});
temp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
udpPost("Temperature");
}
});
}
void udpPost(String message){
new Thread(new Runnable() {
@Override
public void run() {
try {
DatagramSocket datagramSocket = new DatagramSocket();
byte sendBuffer[] = message.getBytes();
byte[] receiveBuffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(sendBuffer, sendBuffer.length, InetAddress.getByName(ip), port);
DatagramPacket receivePacket = new DatagramPacket(receiveBuffer, receiveBuffer.length);
datagramSocket.send(packet);
datagramSocket.receive(receivePacket);
String message = new String(receivePacket.getData(), 0, receivePacket.getLength());
runOnUiThread(new Runnable() {
@Override
public void run() {
if (message != ""){
tempView.setText(message + "°C");
}
}
}); } catch (SocketException | UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start();
}
}
Смотрим результат
Настала пора взглянуть на результат. Устанавливаем приложение на Android-устройство и загружаем прошивку в ESP8266. Подключаемся со смартфона к точке доступа микроконтроллера. Открываем приложение, где управляем светодиодом и получаем данные о температуре — таким образом можно отслеживать температурные показатели (или любые другие значения) прямо через собственное приложение по wifi сети.



Комментарии (6)
dmitryrf
08.07.2025 04:36Хорошее демо!
Мне в этой схеме вот что кажется неудобным: IP адрес жестко прошит и никак не настраивается. Плюс нужно отключаться от сети с доступом в интернет чтобы воспользоваться устройством. Было бы здорово, если бы устройство подключалось к домашней сети как клиент, а приложение искало бы его через mdns
Danchkin_Sab Автор
08.07.2025 04:36Можно и так, никаких проблем: настраиваем esp на работу в роли клиента, задаём ей статичный ip и также обмениваемся данными. Нюанс просто в том что не везде может быть собственная wifi сеть
aladkoi
Esp серия обычно программируется через среду esp-idf в vscode. Еще можно использовать спец. графическую среду без "ручного" такого написания кода. Да использование устаревшего 8266 не самое удачное решение. Есть мини платы esp32c6 , например, с поддержкой зигби. Это более интересное решение.
randomsimplenumber
Ну это же демка.
Но тема необычно высоких скоростей передачи по UDP не раскрыта. Где бенчмарки? Где сравнение с TCP? Может, в этой конфигурации (esp8266 + Android) и нет никакой разницы?
Danchkin_Sab Автор
Возможности esp32c6 я ещё не изучал, хотя идея хорошая, под рукой оказалась только esp8266 с которой уже имел опыт работы
Danchkin_Sab Автор
Это идея для следующий статьи, поскольку опыт работы с tcp и даже http я имею - сделаю сравнение