安卓APP控制ESP32

发布时间:2024-12-16 05:04

如何在安卓手机上使用智能家居APP遥控器功能:下载对应的APP,开启手机映射功能 #生活技巧# #数码产品使用技巧# #智能设备设置教程#

目录

初衷

技术栈

安卓端

ESP32端

初衷

        本人所学专业为电力电子专业,想做关于负载的连续变化实验,变化频率一定,首先想到的是电子负载,但是实验室中现有的电子负载无法应用与所研究的小功率变换器,于是想通过单片机来控制继电器进行负载切换。

        最简单的方法是PC通过串口连接单片机,单片机控制继电器吸合,进行负载的断开与连接。

        但是这一点都不好玩,太枯燥,不符合创客精神,是时候尝试各种开发的结合!

        控制必须远程,远程必须联网,联网显示QR,APP扫码连接。(还有另外一种方式SmartConfig,跟AirKiss技术类似,通过广播的方式,将WiFi信息间接传递给ESP32单片机)

       对于单片机的选择,具备联网功能的ESP32 WROOM,价格美丽,资源足够。

        安卓APP开发有多种方式,最简单的是使用web相关技术开发,uniapp,webapp等,对于页面质量的提升极其明显,但是感觉不够接近底层,对于底层逻辑的开发体验不如原生,因此使用Android Studio从而通过java进行app开发。

        成果展示:

        手机APP:

技术栈

        1.C控制单片机配置网络(手机作为服务器,ESP32为客户端),通过二维码显示设备ip信息(设备:ESP32 WROOM,TFT屏幕,5V继电器)

        2.安卓原生程序开发,扫码连接设备,从而控制单片机,同时显示单片机状态。(Android Studio)

安卓端

    为实现扫码功能,使用了华为的统一扫码服务 HMS scan kithttps://developer.huawei.com/consumer/cn/hms/huawei-scankit

     (挺好用的,简单配置一下即可,添加相关依赖)

(1)首先是Manifest配置,添加相机权限,网络权限等

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.CAMERA" />

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<uses-feature android:name="android.hardware.camera" />

<uses-feature android:name="android.hardware.camera.autofocus" />

<uses-permission android:name="android.permission.READ_PHONE_STATE"></uses-permission>

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

<application

android:allowBackup="true"

android:dataExtractionRules="@xml/data_extraction_rules"

android:fullBackupContent="@xml/backup_rules"

android:icon="@mipmap/ic_launcher"

android:label="@string/app_name"

android:supportsRtl="true"

android:theme="@style/Theme.ESP32IOT"

tools:targetApi="31">

<activity

android:name=".MainActivity"

android:exported="true">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

</manifest>

 (2)添加gradle依赖包

plugins {

id 'com.android.application'

}

android {

namespace 'com.example.esp32_iot'

compileSdk 33

defaultConfig {

applicationId "com.example.esp32_iot"

minSdk 29

targetSdk 33

versionCode 1

versionName "1.0"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

}

buildTypes {

release {

minifyEnabled false

proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'

}

}

compileOptions {

sourceCompatibility JavaVersion.VERSION_1_8

targetCompatibility JavaVersion.VERSION_1_8

}

}

dependencies {

implementation 'androidx.appcompat:appcompat:1.4.1'

implementation 'com.google.android.material:material:1.5.0'

implementation 'androidx.constraintlayout:constraintlayout:2.1.3'

testImplementation 'junit:junit:4.13.2'

androidTestImplementation 'androidx.test.ext:junit:1.1.3'

androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'

implementation "com.huawei.hms:scanplus:1.1.3.301"

}

(3)界面设计

<?xml version="1.0" encoding="utf-8"?>

<androidx.appcompat.widget.LinearLayoutCompat 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"

tools:context=".MainActivity"

android:orientation="vertical"

android:background="@drawable/app_esp32"

>

<FrameLayout

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_weight="2">

<TextView

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:hint="未连接"

android:id="@+id/tv"

android:textSize="50sp"

android:textColor="#dcdcdc"

android:textColorHint="#dcdcdc"

android:layout_gravity="center"

android:gravity="center"

></TextView>

</FrameLayout>

<RelativeLayout

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:id="@+id/RL"

android:layout_weight="1">

<ImageView

android:layout_width="50dp"

android:layout_height="50dp"

android:background="@drawable/baseline_qr_code_scanner_24"

android:layout_centerHorizontal="true"

android:onClick="loadScanKitBtnClick"

android:layout_marginLeft="20dp"

/>

<EditText

android:layout_width="wrap_content"

android:layout_height="50dp"

android:hint="服务器地址"

android:textSize="20sp"

android:id="@+id/IPet"

android:textColorHint="#dcdcdc"

android:layout_alignParentLeft="true"

></EditText>

<EditText

android:layout_width="wrap_content"

android:layout_height="50sp"

android:hint="端口地址"

android:id="@+id/port"

android:textSize="20sp"

android:textColorHint="#dcdcdc"

android:layout_alignParentRight="true"

></EditText>

</RelativeLayout>

<LinearLayout

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_weight="4"

android:orientation="vertical"

android:gravity="center">

<LinearLayout

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:orientation="horizontal"

android:layout_weight="4"

android:gravity="bottom"

>

<Button

android:layout_width="match_parent"

android:layout_height="200dp"

android:text="连接"

android:textSize="40sp"

android:id="@+id/conn"

android:layout_weight="1"

android:layout_marginHorizontal="10dp"

android:background="@drawable/btn"

></Button>

<Button

android:layout_width="match_parent"

android:layout_height="200dp"

android:textSize="40sp"

android:text="断开"

android:id="@+id/disconn"

android:layout_weight="1"

android:layout_marginHorizontal="10dp"

android:background="@drawable/btn"

></Button>

</LinearLayout>

<LinearLayout

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:layout_weight="4"

android:gravity="top"

android:layout_marginTop="20dp"

android:paddingBottom="50dp"

>

<Button

android:layout_width="match_parent"

android:layout_height="200dp"

android:text="ON"

android:textSize="40sp"

android:id="@+id/on"

android:layout_weight="1"

android:layout_marginHorizontal="10dp"

android:background="@drawable/btn"

></Button>

<Button

android:layout_width="match_parent"

android:layout_height="200dp"

android:text="OFF"

android:textSize="40sp"

android:id="@+id/off"

android:layout_weight="1"

android:layout_marginHorizontal="10dp"

android:background="@drawable/btn"

></Button>

</LinearLayout>

<LinearLayout

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_weight="700"

></LinearLayout>

</LinearLayout>

</androidx.appcompat.widget.LinearLayoutCompat>

 (4)逻辑实现

package com.example.esp32_iot;

import androidx.appcompat.app.AppCompatActivity;

import androidx.core.app.ActivityCompat;

import android.Manifest;

import android.content.Intent;

import android.content.pm.PackageManager;

import android.os.Bundle;

import android.os.Handler;

import android.os.Message;

import android.os.StrictMode;

import android.view.View;

import android.widget.Button;

import android.widget.EditText;

import android.widget.TextView;

import android.widget.Toast;

import com.huawei.hms.hmsscankit.ScanUtil;

import com.huawei.hms.ml.scan.HmsScan;

import com.huawei.hms.ml.scan.HmsScanAnalyzerOptions;

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStream;

import java.io.InputStreamReader;

import java.io.OutputStream;

import java.net.Socket;

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

private TextView tv;

private EditText IPet,port;

private Handler myhandler;

private Socket socket;

private String str = "";

boolean running = false;

private Button conn,disconn,on,off;

private StartThread st;

private ReceiveThread rt;

public static final int CAMERA_REQ_CODE = 111;

public static final int DECODE = 1;

private static final int REQUEST_CODE_SCAN_ONE = 0X01;

String[] permissions = new String[]{Manifest.permission.READ_PHONE_STATE};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

tv = findViewById(R.id.tv);

IPet = findViewById(R.id.IPet);

port = findViewById(R.id.port);

conn = (Button) findViewById(R.id.conn);

disconn = (Button) findViewById(R.id.disconn);

on = (Button) findViewById(R.id.on);

off = findViewById(R.id.off);

setButtonOnStartState(true);

conn.setOnClickListener(this);

disconn.setOnClickListener(this);

on.setOnClickListener(this);

off.setOnClickListener(this);

myhandler = new MyHandler();

}

public void loadScanKitBtnClick(View view) {

requestPermission(CAMERA_REQ_CODE, DECODE);

}

private void requestPermission(int requestCode, int mode) {

ActivityCompat.requestPermissions(

this,

new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE},

requestCode);

}

@Override

public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {

super.onRequestPermissionsResult(requestCode, permissions, grantResults);

if (permissions == null || grantResults == null) {

return;

}

if (grantResults.length < 2 || grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) {

return;

}

if (requestCode == CAMERA_REQ_CODE) {

ScanUtil.startScan(this, REQUEST_CODE_SCAN_ONE, new HmsScanAnalyzerOptions.Creator().create());

}

}

@Override

protected void onActivityResult(int requestCode, int resultCode, Intent data) {

super.onActivityResult(requestCode, resultCode, data);

if (resultCode != RESULT_OK || data == null) {

return;

}

if (requestCode == REQUEST_CODE_SCAN_ONE) {

HmsScan obj = data.getParcelableExtra(ScanUtil.RESULT);

if (obj != null) {

Toast.makeText(this,obj.originalValue,Toast.LENGTH_SHORT).show();

IPet.setText(obj.originalValue);

}

}

}

@Override

public void onClick(View v) {

switch (v.getId()){

case R.id.conn:

st = new StartThread();

st.start();

setButtonOnStartState(false);

break;

case R.id.on:

new Thread(new Runnable() {

@Override

public void run() {

try {

OutputStream os = null;

os = socket.getOutputStream();

os.write(("123\n").getBytes("utf-8"));

}catch (IOException e){

e.printStackTrace();

}

}

}).start();

break;

case R.id.off:

new Thread(new Runnable() {

@Override

public void run() {

try {

OutputStream os = null;

os = socket.getOutputStream();

os.write(("456\n").getBytes("utf-8"));

}catch (IOException e){

e.printStackTrace();

}

}

}).start();

break;

case R.id.disconn:

new Thread(new Runnable() {

@Override

public void run() {

try {

OutputStream os = null;

os = socket.getOutputStream();

os.write(("789\n").getBytes("utf-8"));

}catch (IOException e){

e.printStackTrace();

}

}

}).start();

running = false;

setButtonOnStartState(true);

try {

socket.close();

}catch (NullPointerException e){

e.printStackTrace();

disPlayToast("断开连接");

}catch (IOException e){

e.printStackTrace();

}

break;

}

}

private class StartThread extends Thread{

@Override

public void run(){

try {

int portnum = Integer.parseInt(port.getText().toString());

socket = new Socket(IPet.getText().toString(),portnum);

System.out.println(IPet.getText());

rt = new ReceiveThread(socket);

rt.start();

running = true;

System.out.println(socket.isConnected());

if(socket.isConnected()){

Message msg0 = myhandler.obtainMessage();

msg0.what = 0;

myhandler.sendMessage(msg0);

tv.setText("已连接ESP32");

}

}catch (IOException e){

e.printStackTrace();

}

}

}

private class ReceiveThread extends Thread{

private InputStream is;

public ReceiveThread(Socket socket) throws IOException{

is = socket.getInputStream();

}

@Override

public void run(){

while (running){

InputStreamReader isr = new InputStreamReader(is);

BufferedReader br = new BufferedReader(isr);

try {

System.out.println(str = br.readLine());

}catch (NullPointerException e){

running = false;

Message msg2 = myhandler.obtainMessage();

msg2.what = 2;

myhandler.sendMessage(msg2);

e.printStackTrace();

break;

}catch (IOException e){

e.printStackTrace();

}

Message msg = myhandler.obtainMessage();

msg.what = 1;

msg.obj = str;

myhandler.sendMessage(msg);

try{

sleep(400);

}catch (InterruptedException e){

e.printStackTrace();

}

}

Message msg2 = myhandler.obtainMessage();

msg2.what = 2;

myhandler.sendMessage(msg2);

}

}

private void setButtonOnStartState(boolean flag) {

conn.setEnabled(flag);

disconn.setEnabled(!flag);

on.setEnabled(!flag);

off.setEnabled(!flag);

}

private void disPlayToast(String s){

Toast.makeText(this,s,Toast.LENGTH_SHORT).show();

}

class MyHandler extends Handler{

@Override

public void handleMessage(Message msg){

switch (msg.what){

case 1:

String str = (String) msg.obj;

System.out.println(msg.obj);

tv.setText(str);

break;

case 0:

disPlayToast("连接成功");

break;

case 2:

disPlayToast("服务器已断开");

tv.setText(null);

setButtonOnStartState(true);

break;

}

}

}

}

思路介绍完之后,附上gitee ESP32 IOT APPhttps://gitee.com/cheplus/esp32-iotapp

ESP32端

通过C语言配置,platformio

使用的屏幕是TFT 240*240(ST7789驱动)

配置页如下(.pio\libdeps\upesy_wroom\TFT_eSPI\User_Setups\Setup24_ST7789.h)

#define TFT_MISO -1

#define TFT_MOSI 23

#define TFT_SCLK 18

#define TFT_CS -1

#define TFT_DC 2

#define TFT_RST 15

#include <SPI.h>

#include <WiFi.h>

#include <WebServer.h>

#include <TFT_eSPI.h>

#include <qrcode_espi.h>

TFT_eSPI tft = TFT_eSPI();

const char *AP_SSID = "Redmi K30S Ultra";

const char *AP_Password = "qwerty123";

WiFiServer esp32_server(8080);

QRcode_eSPI qrcode(&tft);

void setup(void)

{

tft.init();

pinMode(2, OUTPUT);

pinMode(13, OUTPUT);

tft.fillScreen(0xffa500);

Serial.begin(115200);

delay(500);

Serial.println("111111111111");

WiFi.begin(AP_SSID, AP_Password);

while (WiFi.status() != WL_CONNECTED)

{

delay(5000);

Serial.println("正在连接");

tft.println("正在连接");

}

Serial.println("连接成功");

tft.setCursor(0, 0, 4);

tft.setTextColor(TFT_BLUE);

tft.println("Connected");

tft.println(WiFi.localIP());

qrcode.init();

qrcode.create(WiFi.localIP().toString().c_str());

esp32_server.begin();

}

void loop()

{

WiFiClient client = esp32_server.available();

String msgs = "";

if (client)

{

if(client.connected()){

tft.fillScreen(0x42a5f5);

tft.setTextColor(TFT_WHITE);

tft.println("client Connected");

}

while (client.connected())

{

if (client.available())

{

String line = client.readStringUntil('\n');

Serial.print("读取到数据:");

Serial.println(line);

msgs = line + "\n";

client.write(msgs.c_str());

if(line=="123"){

digitalWrite(2, HIGH);

digitalWrite(13, HIGH);

}else if(line=="456"){digitalWrite(2, LOW);digitalWrite(13, LOW);}

}

msgs = "";

delay(10);

}

client.stop();

qrcode.create(WiFi.localIP().toString().c_str());

Serial.println("Client disconnected");

}

}

这个程序就相对简单,附上gitee

ESP32 IOT TFThttps://gitee.com/cheplus/esp32-iottft实物图

网址:安卓APP控制ESP32 https://www.yuejiaxmz.com/news/view/485877

相关内容

基于ESP32制作安卓应用蓝牙控制的家庭自动化系统
基于ESP32的智能家居控制系统设计
智能家居新体验:基于Blinker控制ESP32红外遥控空调
实现ESP32
ESP32
【阿里云生活物联网架构师专题 ①】esp32 sdk 直连接入阿里云物联网平台,实现天猫精灵语音控制;
免费安卓远程控制APP有哪些?(三款免费软件)
基于ESP32的智能家庭健康系统
安卓关机app有哪些 手机控制软件下载推荐
基于ESP32

随便看看