Android手机端远程控制ESP32引脚电平实例

news2024/11/17 7:39:06

一、背景介绍

ESP32是一款高度集成的低功耗系统级芯片,它结合了双核处理器、无线通信、低功耗特性和丰富的外设,适用于各种物联网(IoT)应用,如果与Android结合,在手机端远程控制ESP32引脚高低电平输出,也是一件挺有趣的事情。

二、工作原理

我们这里使用MQTT协议来实现手机App与ESP32之间的通信。MQTT(Message Queuing Telemetry Transport,消息队列遥测传输)协议是一种基于发布/订阅(publish/subscribe)模式的轻量级通讯协议,它工作在TCP/IP协议族上,特别适用于计算能力有限且工作在低带宽、不可靠的网络的远程传感器和控制设备通讯。大概的原理图如下:

三、准备工作

要实现上述功能,我们需要准备以下东西:

1.可用于开发的Android手机(本文以小米手机为例)。

2.MQTT服务器:可以使用免费的公共 MQTT 服务器 | EMQ,也可以参考《MQTT服务器-安装篇(阿里云主机)-CSDN博客》自己搭建MQTT服务器。

3.配置好开发环境的ESP32:可以参考《01_ESP32 MicroPython开发环境搭建-CSDN博客》

四、实现步骤

我们假设已经完成了“准备工作”中的几项,开始实现功能,先来说下思路:

整个过程包括2个步骤:

一是手机端要能获取到ESP32当前引脚的状态。

二是手机端给ESP32发送指令,设置引脚状态。

根据消息流方向,我们这里使用2个MQTT主题(名称可以自定义):

esp32_down:用于手机端向ESP32发送指令,手机端向此主题发送消息,ESP32订阅该主题

esp32_up:用于ESP32向手机端上报状态,ESP32向此主题发送消息,手机端订阅该主题。

我们以点亮ESP32上蓝色的小灯为例,演示操作。

开始程序编写

1.ESP32端

import time
import network
import utime 
from umqtt.robust import MQTTClient
from machine import Pin

#GPIO引脚2,用于开灯或关灯
pin2 = Pin(2, Pin.OUT)

# WiFi 连接信息
wifi_ssid = '******' # 请替换为自己的WiFi
wifi_password = '******' # 请替换为自己的WiFi
 
# MQTT 服务器信息
mqtt_server = '******'  # 请替换为自己的配置
mqtt_port = 1883
mqtt_user = '******'    # 请替换为自己的配置
mqtt_password = '******'# 请替换为自己的配置
mqtt_client = 'esp32' # 客户端ID
mqtt_topic_down = 'esp32_down' # 给ESP32发送指令的主题
mqtt_topic_up = 'esp32_up' # ESP32上报状态的主题
mqtt_keepalive = 62  # 保持活动的时间间隔(秒)  
 
# 当前时间
def currentTime():
    # 获取当前时间元组
    current_time = utime.localtime()
    # 解构时间元组
    year, month, day, hour, minute, second, _, _ = current_time
    # 使用字符串格式化功能生成日期时间字符串
    formatted_time = "{:04d}-{:02d}-{:02d} {:02d}:{:02d}:{:02d} ".format(year, month, day, hour, minute, second)
    return formatted_time

# 连接 WiFi
def connect_wifi():  
    wifi = network.WLAN(network.STA_IF)    
    #若已连接过Wifi,切换新的Wifi时,需要重置下网络,才能连接成功
    if wifi.isconnected():
        # 断开当前连接
        wifi.disconnect()
        # 关闭接口
        wifi.active(False)        
    time.sleep(1)  # 休眠1秒
    wifi.active(True)
    print(currentTime()+"connecting to wifi: {0}".format(wifi_ssid))
    wifi.connect(wifi_ssid, wifi_password)
    while not wifi.isconnected():
        time.sleep(1)
    print(currentTime()+"connected to wifi: {0}".format(wifi_ssid))
    print(currentTime()+'network config:', wifi.ifconfig())

# 定义消息回调函数
def sub_cb(topic, msg):
    action=msg.decode('utf-8')
    print(currentTime()+"recv: topic("+topic.decode('utf-8')+")->msg:" +action)
    if(action=="on"):
        lightON()
    elif (action=="off"): 
        lightOFF()
    elif (action=="status"): 
        uploadStatus()
        
#亮灯(也可以是其他操作,此处仅做演示)
def lightON():
    pin2.value(1)
    client.publish(mqtt_topic_up, str(1))#动态取引脚电平不准确,直接写固定值
    
#关灯(也可以是其他操作,此处仅做演示)
def lightOFF():
    pin2.value(0)    
    client.publish(mqtt_topic_up, str(0))#动态取引脚电平不准确,直接写固定值    

#上报当前状态
def uploadStatus():             
    client.publish(mqtt_topic_up, str(pin2.value()))
    print(currentTime()+"send: topic("+mqtt_topic_up+')->msg:' +str(pin2.value()))   

#【主程序】
connect_wifi() 
mqtt_client = "esp32" + str(time.ticks_ms())  # 确保每次连接都有唯一的client_id
client = MQTTClient(mqtt_client, mqtt_server, mqtt_port,mqtt_user, mqtt_password, keepalive=mqtt_keepalive)
#设置订阅回调
client.set_callback(sub_cb)
try:
    #连接到MQTT服务器
    client.connect()
    print(currentTime()+"ESP32已连接到MQTT服务器"+mqtt_server) 

    # 订阅主题
    client.subscribe(mqtt_topic_down)
    print(currentTime()+"ESP32已开启消息订阅,请在EMQ客户端发送消息...") 
except OSError as e:
    print(currentTime()+'Connection or subscription failed: %r' % e)
    
# 持续接收消息
count=0 
while True: 
    count=count+1 
    #每次轮询间隔0.3秒,轮询200次就是60秒,在第200次时发送心跳包
    if(count==199):
        #发送心跳包,上报当前状态
        curtime=currentTime()
        uploadStatus()
        print(currentTime()+"heart beat...")
        count=0               
            
    client.check_msg()  
    time.sleep(0.3) 
        
    
# 断开与MQTT代理的连接
client.disconnect()

2.MQTT服务器端

参考《MQTT服务器-安装篇(阿里云主机)-CSDN博客》

3.Android手机端

import android.annotation.SuppressLint;
import android.graphics.Color;
import android.media.Ringtone;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import androidx.appcompat.app.ActionBar;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
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 org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

public class Esp32Activity extends BaseActivity {
    protected TextView txtLightStatus;
    protected Button btnTest;
    protected String ac = "off";


    String context = "tcp://******:1883";
    String clientId = "******";
    String username = "******";
    String password = "******";
    String topic_down = "esp32_down";
    String topic_up = "esp32_up";
    MqttClient client;
    MqttConnectOptions options;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_esp32);
        //不显示标题
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.hide();
        }
        SetStatusBarTransparent();//状态栏透明
        SetStatusBarTextColor(true);//状态栏文字变黑
        txtLightStatus = findViewById(R.id.txtLightStatus);
        btnTest = findViewById(R.id.btnTest);
        new Thread(() -> {
            try {
                client = new MqttClient(context, clientId, new MemoryPersistence());
                options = new MqttConnectOptions();
                options.setCleanSession(true);
                options.setUserName(username);
                options.setPassword(password.toCharArray());
                //订阅状态上报消息
                MqttCallback callback = new MqttCallback() {
                    public void connectionLost(Throwable cause) {
                        // 连接丢失后,一般在这里面进行重连
                        System.out.println("Connection lost!");
                    }

                    public void messageArrived(String topic, MqttMessage message) throws Exception {
                        Message m = new Message();
                        m.what = 3;
                        m.obj = new String(message.getPayload());
                        handler.sendMessage(m);
                    }

                    @Override
                    public void deliveryComplete(IMqttDeliveryToken token) {
                        System.out.println("Delivery complete");
                    }
                };
                client.setCallback(callback);
                client.connect(options);
                int[] QoS = {0};
                client.subscribe(new String[]{topic_up}, QoS);

                //发送查询状态消息
                String action = "status";
                MqttMessage message = new MqttMessage(action.getBytes());
                message.setQos(0);
                client.publish(topic_down, message);

            } catch (MqttException e) {
                Message message = new Message();
                message.what = 2;
                message.obj = e.getMessage();
                handler.sendMessage(message);
            }
        }).start();

        btnTest.setOnClickListener(view -> {

            new Thread(() -> {
                try {
                    //发送消息
                    String action = ac;
                    MqttMessage mqttMessage = new MqttMessage(action.getBytes());
                    mqttMessage.setQos(0);
                    client.publish(topic_down, mqttMessage);

                    Message message = new Message();
                    message.what = 1;
                    message.obj = action;
                    handler.sendMessage(message);

                } catch (MqttException e) {
                    Message message = new Message();
                    message.what = 2;
                    message.obj = e.getMessage();
                    handler.sendMessage(message);
                }
            }).start();

        });
    }

    public Handler handler = new Handler() {
        @SuppressLint("HandlerLeak")
        @Override
        public void handleMessage(Message msg) {
            String text = msg.obj.toString();
            if (text.equals("on")) {
                playVotice();
                btnTest.setText("关灯");
            }
            if (text.equals("off")) {
                playVotice();
                btnTest.setText("开灯");
            }
            switch (msg.what) {
                case 1:
                    Toast.makeText(Esp32Activity.this, "发送命令:" + ac + " 成功", Toast.LENGTH_SHORT).show();
                    break;
                case 2:
                    Toast.makeText(Esp32Activity.this, "抱歉,发送异常:" + text, Toast.LENGTH_SHORT).show();
                    break;
                case 3:
                    txtLightStatus.setText("当前状态:" + (text.equals("1") ? "点亮" : "熄灭"));
                    if (text.equals("1")) {
                        ac = "off";
                        btnTest.setBackgroundColor(Color.GREEN);
                        btnTest.setText("关灯");
                    }
                    if (text.equals("0")) {
                        ac = "on";
                        btnTest.setBackgroundColor(Color.GRAY);
                        btnTest.setText("开灯");
                    }
                    break;

            }
        }
    };

    public void playVotice() {
        Uri notificationUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        Ringtone ringtone = RingtoneManager.getRingtone(Esp32Activity.this, notificationUri);
        ringtone.play();
    }

}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"

    android:orientation="vertical"
    tools:context=".Esp32Activity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:text="@string/menu_esp32"
            android:textAlignment="center"
            android:textStyle="bold"
            android:textSize="24dp">

        </TextView>
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="20dp"
        android:orientation="vertical"
        android:paddingLeft="15dp"
        android:paddingRight="15dp">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:orientation="horizontal">

            <Button
                android:id="@+id/btnTest"
                android:layout_width="250dp"
                android:layout_height="100dp"
                android:layout_marginTop="10dp"
                android:text="开灯"
                android:textAlignment="center" />
        </LinearLayout>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center_horizontal"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/txtLightStatus"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="20dp"
                android:text="当前状态"
                android:textSize="22dp" />
        </LinearLayout>
    </LinearLayout>


</LinearLayout>

此处只列出前台和后台代码,前台代码及配置请参考《Android 发送MQTT消息-CSDN博客》

五、运行效果

以上只是点亮小灯的演示,扩展上面实例,可以实现其他一些操作。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1981647.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

武汉流星汇聚:依托亚马逊平台助力初创企业出海,共创国际品牌辉煌

在全球跨境电商的浩瀚蓝海中&#xff0c;亚马逊如同一座灯塔&#xff0c;以其庞大的市场规模、强大的品牌影响力和成熟的运营体系&#xff0c;引领着无数企业扬帆出海&#xff0c;探索未知的市场机遇。对于中国卖家而言&#xff0c;亚马逊不仅是通往全球消费者的桥梁&#xff0…

一文详解:企业优化仓库管理——WMS系统

如有你有一间小仓库&#xff0c;几位工人埋头于一摞摞的纸质单据中&#xff0c;手工记录着每一次货物的进出与变动。目前看来&#xff0c;这样的时间和效率&#xff0c;作为管理者的你还可以接受。 但是&#xff0c;令你头疼的是&#xff0c;随着企业规模的扩大&#xff0c;你…

springboot信息安全技术在投票网站-计算机毕业设计源码12740

目录 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2系统分析 2.1 可行性分析 2.2 系统流程分析 2.2.1系统开发流程 2.2.2 用户登录流程 2.2.3 系统操作流程 2.2.4 添加信息流程 2.2.5 修改信息流程 2.2.6 删除信息流程 2.3 系统功能分析 …

ai自动配音工具

AI拟音大师&#xff0c;给你的无声视频添加生动而且同步的音效 &#x1f61d;文件夹是一种基于文本的视频到音频生成框架,可以生成高质量的音频,在语义上相关,并与输入视频时间同步。 下载地址&#xff1a;https://pan.quark.cn/s/5a2be1cc5551

微服务概述及如何搭建微服务

1. 微服务架构—SpringCloud 1.1 单体应用架构 将项目所有模块【功能】打成jar包或者war包&#xff0c;然后部署一个进程 优点&#xff1a; 部署简单&#xff1a;由于是完整的结构体&#xff0c;可以直接部署在一个服务器上即可技术单一&#xff1a;项目不需要复杂的技术栈&am…

windows启动nacos时报Caused by: java.lang.UnsatisfiedLinkErro错误

Caused by: java.lang.UnsatisfiedLinkError: C:\Users\Administrator\AppData\Local\Temp\2\librocksdbjni6009210463092880400.dll: Can’t find dependent libraries 因为电脑没有vc&#xff0c;或vc版本问题&#xff0c;下载对应的vc安装就可以了 VC“2015-2022”运行库官…

基础复习(反射、注解、动态代理)

反射 反射&#xff0c;指的是加载类的字节码到内存&#xff0c;并以编程的方法解刨出类中的各个成分&#xff08;成员变量、方法、构造器等&#xff09;。 1.获取类的字节码 &#xff08;3种方式&#xff09; public class Test1Class{public static void main(String[] arg…

全球社区的建立:Facebook在跨文化交流中的角色

在全球化日益加深的今天&#xff0c;跨文化交流成为了人们日常生活中的重要部分。Facebook作为全球最大的社交网络平台之一&#xff0c;正在发挥着越来越重要的作用。通过其广泛的用户基础和丰富的功能&#xff0c;Facebook不仅连接了来自不同国家和地区的人们&#xff0c;也在…

数据迁移数亿小文件该如何做到?

在数字化转型的浪潮中&#xff0c;企业在数据管理上遇到了诸多挑战&#xff0c;尤其是面对海量小文件的数据迁移问题。这类迁移任务不仅复杂&#xff0c;而且对效率、数据一致性和完整性的要求极高。 处理数亿小文件的数据迁移并非易事。当文件数量庞大&#xff0c;尤其是文件大…

吴恩达老师机器学习-ex8

data1 导入库&#xff0c;读取数据并进行可视化 因为这次的数据是mat文件&#xff0c;需要使用scipy库中的loadmat进行读取数据。 通过对数据类型的分析&#xff0c;发现是字典类型&#xff0c;查看该字典的键&#xff0c;可以发现又X等关键字。 import numpy as np import…

matplotlib库学习之绘图透明度设置(精炼准确)

matplotlib库学习之透明颜色设置 一、简介 在数据可视化中&#xff0c;透明度设置可以使图表更具层次感&#xff0c;特别是在多层叠加图表时。matplotlib库提供了多种方法来设置图表各个部分的透明度&#xff0c;包括图形、文本、图例、坐标轴等部分。 二、为什么要设置成透明…

某PM2项目管理系统 ExcelIn.aspx 文件上传漏洞复现

FOFA:body="PM2项目管理系统BS版增强工具.zip" 访问漏洞url抓包 上传压缩包 请求包 POST /FlowChartDefine/ExcelIn.aspx HTTP/1.1 Host: Accept-Encoding: gzip, deflate Content-Type: multipart/form-data; boundary=---------------------------335518608136…

WPF学习(2)-UniformGrid控件(均分布局)+StackPanel控件(栈式布局)

UniformGrid控件&#xff08;均分布局&#xff09; UniformGrid和Grid有些相似&#xff0c;只不过UniformGrid的每个单元格面积都是相等的&#xff0c;不管是横向的单元格&#xff0c;或是纵向的单元格&#xff0c;它们会平分整个UniformGrid。 UniformGrid控件提供了3个属性…

自动化测试使用jenkins做CICD持续集成(docker)

1.什么是&#xff08;CI/CD&#xff09; 1.1持续集成 定义&#xff1a;频繁地&#xff08;一天多次&#xff09;将代码集成到主干。将软件个人研发的部分向软件整体部分 交付&#xff0c;频繁进行集成以便更快地发现其中的错误。 每完成一点更新&#xff0c;就集成到主干&…

儿童活动床栏亚马逊temu美国ASTM F2085测试报告16 CFR 1224标准CPC认证办理

亚马逊temu美国站儿童活动床栏ASTM F2085-19测试报告活动床栏16 CFR 1224标准CPC认证 什么是便携式床栏杆&#xff1f; 便携式床栏杆是一种旨在安装在成人床上以防止儿童跌落的装置。便携式床栏适用于在没有帮助的情况下方便儿童&#xff08;通常为 2 至 5 岁&#xff09;上下…

编程深水区之并发①:什么是并发编程

并发编程是一种让程序能够执行多个任务的编程技术&#xff0c;多个任务的执行时间有重合&#xff0c;如交替执行、同时执行等。相对于传统的从上到下依次同步执行代码&#xff0c;我们也称并发编程为异步编程。目前&#xff0c;常见的并发模型主要有两种&#xff0c;一是多线程…

MySQL --- 内置函数介绍

目录 一、日期函数 二、字符串函数 三、数学函数 四、 其他函数 一、日期函数 current_date()当前日期current_time()当前时间current_timestamp()当前时间戳date(datetime)返回datetime的日期部分date_add(date&#xff0c;interval d_value_type) 在date中添加时间/日期…

在没有备份的条件下,如何恢复微信聊天记录?

推荐2款数据恢复工具1个简单实用方法&#xff0c;帮你找回微信误删聊天记录&#xff01; 操作简单&#xff0c;可用于iOS、安卓手机和PC电脑端 1、万兴数据管家 点击直达官网下载>>https://huifu.wondershare.cn 万兴数据管家是一款专业的微信数据恢复软件。 支持苹果…

鲁班上门维修安装系统源码开发之功能模式

鲁班上门维修安装系统在当今的趋势呈现出显著的增长与创新。随着物联网、智能家居的普及&#xff0c;以及消费者对便捷、高效生活方式的追求&#xff0c;鲁班上门维修安装系统凭借其多渠道预约、智能派单、在线支付与费用明细透明等优势&#xff0c;赢得了市场的广泛认可。 …

「面试必看」Vue百题斩~ Vue数据响应式原理的四个核心模块

vue 响应式原理的四个核心模块 Observe Observe 要实现的目标非常简单&#xff0c;就是把一个普通对象转换成响应式对象。 为了实现这一点&#xff0c;Observe 把对象的每个属性通过 Object.defineProperty 转换为带有 setter 和 getter 的属性&#xff0c;这样一来&#xf…