安卓APP控制ESP32
如何在安卓手机上使用智能家居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