android通过mqtt协议 与esp8266 通讯,通过发布和订阅消息,控制esp8266小灯的开关,基于此可以实现物联网的其他功能的实现 提供arduino 和android手机app源码 不含esp8266模块

Android和ESP8266玩MQTT绝对是物联网入门的经典项目,今天我就把自己踩过坑又简化后的方案分享给大家——用Android手机APP发消息,ESP8266接收到直接控制小灯开关,举一反三的话,家里的风扇、窗帘、温湿度监测联动都能靠这套逻辑堆出来。

android通过mqtt协议 与esp8266 通讯,通过发布和订阅消息,控制esp8266小灯的开关,基于此可以实现物联网的其他功能的实现 提供arduino 和android手机app源码 不含esp8266模块

首先说MQTT服务器,我直接用的免费公共MQTT broker:EMQX。地址是broker.emqx.io,端口1883(TCP协议),1884是WebSocket,今天主要讲TCP的。


1. Arduino端(ESP8266)

硬件连接

这里虽然不含ESP8266模块,但我简单提下 wiring 逻辑:

  • LED长脚(阳极)→ ESP8266的D1引脚(对应GPIO5)
  • LED短脚(阴极)→ 串联一个220Ω电阻 → ESP8266的GND引脚
  • ESP8266接5V或者3.3V电源,GND接GND,CH_PD接VCC或者拉高。

代码实现

先安装Arduino的ESP8266开发板支持库和PubSubClient库(MQTT通信库)。

#include <ESP8266WiFi.h>
#include <PubSubClient.h>

// WiFi配置
const char* ssid = "你的WiFi名称";
const char* password = "你的WiFi密码";
// MQTT配置
const char* mqtt_server = "broker.emqx.io";
const int mqtt_port = 1883;
const char* client_id = "ESP8266_LED_01"; // 每个设备ID要唯一,随便改
const char* mqtt_topic_sub = "iot/esp8266/led01/control"; // 订阅的控制主题
const char* mqtt_topic_pub = "iot/esp8266/led01/status"; // 发布的状态主题

// WiFi和MQTT客户端
WiFiClient espClient;
PubSubClient client(espClient);
// LED引脚
const int ledPin = D1;
// WiFi连接状态
unsigned long lastReconnectAttempt = 0;

// 连接WiFi函数
void setupWiFi() {
  delay(10);
  Serial.println("Connecting to " + String(ssid));
  WiFi.begin(ssid, password);
  // 一直等到连接成功
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.println("WiFi connected! IP address: " + WiFi.localIP().toString());
}

// 连接MQTT服务器函数
void setupMQTT() {
  client.setServer(mqtt_server, mqtt_port);
  client.setCallback(callback);
}

// MQTT消息回调函数(收到消息后自动触发)
void callback(char* topic, byte* payload, unsigned int length) {
  Serial.print("Received message [");
  Serial.print(topic);
  Serial.print("]: ");
  
  String message;
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  Serial.println(message);
  
  // 解析控制命令
  if (message == "ON") {
    digitalWrite(ledPin, HIGH);
    // 发送状态到服务器,方便APP显示
    client.publish(mqtt_topic_pub, "ON");
    Serial.println("LED turned on");
  } else if (message == "OFF") {
    digitalWrite(ledPin, LOW);
    client.publish(mqtt_topic_pub, "OFF");
    Serial.println("LED turned off");
  }
}

// 重连MQTT函数(网络波动时自动重连)
boolean reconnect() {
  if (client.connect(client_id)) {
    Serial.println("MQTT connected!");
    client.subscribe(mqtt_topic_sub); // 连接成功后立即订阅主题
  } else {
    Serial.print("Failed to connect, rc=");
    Serial.print(client.state());
    Serial.println(" try again in 5 seconds");
  }
  return client.connected();
}

void setup() {
  Serial.begin(115200);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, LOW);
  
  setupWiFi();
  setupMQTT();
}

void loop() {
  // 检查MQTT连接状态
  if (!client.connected()) {
    unsigned long now = millis();
    if (now - lastReconnectAttempt > 5000) {
      lastReconnectAttempt = now;
      if (reconnect()) {
        lastReconnectAttempt = 0;
      }
    }
  } else {
    client.loop(); // 处理MQTT消息
  }
}

代码分析

  • WiFi连接与重连setupWiFi()是一次性连接,loop()里的重连逻辑是处理MQTT的,因为公共broker偶尔会断。
  • 消息回调:重点在callback()函数,收到iot/esp8266/led01/control的主题消息后,先转成String,再判断是ON还是OFF,同时发状态回iot/esp8266/led01/status
  • PubSubClient库:这个库非常轻量,很适合资源少的ESP8266,client.setCallback()绑定处理函数,client.subscribe()client.publish()是核心API。

2. Android端

依赖库

Android Studio里的build.gradle(Module: app)添加依赖:

dependencies {
    // MQTT通信库
    implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'
    implementation 'org.eclipse.paho:org.eclipse.paho.android.service:1.1.1'
    // UI库
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.android.material:material:1.11.0'
}

权限配置

AndroidManifest.xml里添加:

<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<!-- 访问网络状态 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- 后台服务权限(MQTT服务需要) -->
<uses-permission android:name="android.permission.WAKE_LOCK" />

<application
    ...
    <!-- 声明Paho服务 -->
    <service android:name="org.eclipse.paho.android.service.MqttService" />
    ...
</application>

代码实现

MainActivity.java
package com.example.mqttledcontrol;

import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;

import android.Manifest;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.CompoundButton;
import android.widget.Toast;
import android.widget.ToggleButton;

import org.eclipse.paho.android.service.MqttAndroidClient;
import org.eclipse.paho.client.mqttv3.IMqttActionListener;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.IMqttToken;
import org.eclipse.paho.client.mqttv3.MqttCallbackExtended;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.io.UnsupportedEncodingException;
import java.util.UUID;

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private static final String MQTT_BROKER = "tcp://broker.emqx.io:1883";
    private static final String MQTT_CLIENT_ID = "Android_LED_Control_" + UUID.randomUUID().toString().substring(0, 8); // 唯一ID
    private static final String MQTT_TOPIC_CONTROL = "iot/esp8266/led01/control";
    private static final String MQTT_TOPIC_STATUS = "iot/esp8266/led01/status";

    private MqttAndroidClient mqttAndroidClient;
    private ToggleButton tbLed;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tbLed = findViewById(R.id.tb_led);

        // 请求网络权限
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.INTERNET)
                != PackageManager.PERMISSION_GRANTED) {
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.INTERNET}, 1);
        }

        // 初始化MQTT客户端
        initMqttClient();
        // 连接MQTT服务器
        connectMqtt();
        // 绑定按钮监听
        tbLed.setOnCheckedChangeListener((buttonView, isChecked) -> {
            if (mqttAndroidClient.isConnected()) {
                publishMessage(MQTT_TOPIC_CONTROL, isChecked ? "ON" : "OFF");
            } else {
                Toast.makeText(this, "MQTT未连接!", Toast.LENGTH_SHORT).show();
                tbLed.setChecked(!isChecked);
            }
        });
    }

    private void initMqttClient() {
        mqttAndroidClient = new MqttAndroidClient(getApplicationContext(), MQTT_BROKER, MQTT_CLIENT_ID);
        mqttAndroidClient.setCallback(new MqttCallbackExtended() {
            @Override
            public void connectComplete(boolean reconnect, String serverURI) {
                Log.d(TAG, "MQTT连接成功!重连:" + reconnect);
                Toast.makeText(getApplicationContext(), "MQTT连接成功!", Toast.LENGTH_SHORT).show();
                // 连接成功后订阅状态主题
                subscribeTopic(MQTT_TOPIC_STATUS);
            }

            @Override
            public void connectionLost(Throwable cause) {
                Log.e(TAG, "MQTT连接断开:" + cause.getMessage());
                Toast.makeText(getApplicationContext(), "MQTT连接断开!", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void messageArrived(String topic, MqttMessage message) {
                Log.d(TAG, "收到消息 [ " + topic + " ] : " + new String(message.getPayload()));
                // 收到ESP8266的状态消息,同步按钮状态
                if (topic.equals(MQTT_TOPIC_STATUS)) {
                    String status = new String(message.getPayload());
                    runOnUiThread(() -> tbLed.setChecked(status.equals("ON")));
                }
            }

            @Override
            public void deliveryComplete(IMqttDeliveryToken token) {
                Log.d(TAG, "消息发送成功!Token: " + token.hashCode());
            }
        });
    }

    private void connectMqtt() {
        MqttConnectOptions options = new MqttConnectOptions();
        options.setCleanSession(true); // 每次连接都是新会话
        options.setAutomaticReconnect(true); // 自动重连
        options.setKeepAliveInterval(60); // 心跳间隔(秒)
        options.setConnectionTimeout(30); // 连接超时(秒)

        try {
            mqttAndroidClient.connect(options, null, new IMqttActionListener() {
                @Override
                public void onSuccess(IMqttToken asyncActionToken) {
                    Log.d(TAG, "MQTT连接请求成功!");
                }

                @Override
                public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                    Log.e(TAG, "MQTT连接请求失败:" + exception.getMessage());
                    Toast.makeText(getApplicationContext(), "连接失败:" + exception.getMessage(), Toast.LENGTH_SHORT).show();
                }
            });
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

    private void publishMessage(String topic, String message) {
        try {
            byte[] payload = message.getBytes("UTF-8");
            MqttMessage mqttMessage = new MqttMessage(payload);
            mqttMessage.setQos(0); // QoS等级0:最多一次,不保证送达,适合小灯控制这种实时性不高的场景
            mqttMessage.setRetained(false); // 不保留消息
            mqttAndroidClient.publish(topic, mqttMessage);
        } catch (UnsupportedEncodingException | MqttException e) {
            e.printStackTrace();
        }
    }

    private void subscribeTopic(String topic) {
        try {
            mqttAndroidClient.subscribe(topic, 0, null, new IMqttActionListener() {
                @Override
                public void onSuccess(IMqttToken asyncActionToken) {
                    Log.d(TAG, "订阅成功:" + topic);
                }

                @Override
                public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
                    Log.e(TAG, "订阅失败:" + exception.getMessage());
                }
            });
        } catch (MqttException e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mqttAndroidClient.isConnected()) {
            try {
                mqttAndroidClient.disconnect();
            } catch (MqttException e) {
                e.printStackTrace();
            }
        }
    }
}

布局文件(activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="ESP8266小灯控制"
        android:textSize="24sp"
        android:layout_marginBottom="30dp"/>

    <ToggleButton
        android:id="@+id/tb_led"
        android:layout_width="200dp"
        android:layout_height="100dp"
        android:textOn="小灯 ON"
        android:textOff="小灯 OFF"
        android:textSize="20sp"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tips: 连接MQTT服务器中..."
        android:textSize="14sp"
        android:textColor="#999999"
        android:layout_marginTop="20dp"/>

</LinearLayout>

代码分析

  • MqttAndroidClient:这是Paho的Android版本,比纯Java的v3库多了Service支持,更适合后台运行。
  • MQTT连接connectMqtt()里的MqttConnectOptions设置了自动重连、清洁会话、心跳间隔这些参数,公共broker推荐自动重连。
  • 消息双向同步:按钮按下发控制消息,收到状态消息后同步按钮状态(避免APP杀掉重开后状态不一致),这里要注意runOnUiThread(),因为MQTT消息是在后台线程处理的,更新UI必须在主线程。
  • 权限请求:Android 6.0+需要动态请求INTERNET权限,虽然一般默认给,但加上保险。

3. 测试步骤

  1. 烧录Arduino代码到ESP8266。
  2. 安装Android APK到手机。
  3. 手机和ESP8266连同一WiFi。
  4. 打开APP,等提示MQTT连接成功。
  5. 按下ToggleButton,ESP8266的小灯就亮灭啦!

4. 功能扩展思路

既然基础逻辑通了,其他物联网功能就是:

  • 增加传感器:比如DHT11测温湿度,ESP8266定时publish到主题,APP订阅显示。
  • 增加控制设备:比如继电器控制风扇,APP发1和0到主题。
  • 自定义主题:把主题改成iot/你的房间/你的设备/控制,避免和别人冲突。
  • 本地MQTT服务器:如果公共broker不稳定,可以用树莓派搭个本地的EMQX或Mosquitto。

如果有问题,欢迎留言!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐