前言
接着上一篇的文章创建好了设备,云产品转发,让STM32连接上阿里云,发布和订阅了相关主题。本篇文章来编写一个Android app来进行控制STM32和接收传感器数据显示在屏幕上。基于Android studio。
演示视频
实现一个简单的app来控制stm32开关灯、蜂鸣器、门(舵机),显示温湿度(DTH11模块)数据,光度数据。
话不多说先看实验效果:
基于STM32+esp8266+freertos+Android app+阿里云的智能家居系统_哔哩哔哩_bilibili
Android app代码
AiotMqttOption.java:连接阿里云的MQTT类
package com.jiafei.test;import java.math.BigInteger;import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;/*** MQTT建连选项类,输入设备三元组productKey, deviceName和deviceSecret, 生成Mqtt建连参数clientId,username和password.*/
class AiotMqttOption {private String username = "";private String password = "";private String clientId = "";public String getUsername() { return this.username;}public String getPassword() { return this.password;}public String getClientId() { return this.clientId;}/*** 获取Mqtt建连选项对象* @param productKey 产品秘钥* @param deviceName 设备名称* @param deviceSecret 设备机密* @return AiotMqttOption对象或者NULL*/public AiotMqttOption getMqttOption(String productKey, String deviceName, String deviceSecret) {if (productKey == null || deviceName == null || deviceSecret == null) {return null;}try {String timestamp = Long.toString(System.currentTimeMillis());// clientIdthis.clientId = productKey + "." + deviceName + "|timestamp=" + timestamp +",_v=paho-android-1.0.0,securemode=2,signmethod=hmacsha256|";// userNamethis.username = deviceName + "&" + productKey;// passwordString macSrc = "clientId" + productKey + "." + deviceName + "deviceName" +deviceName + "productKey" + productKey + "timestamp" + timestamp;String algorithm = "HmacSHA256";Mac mac = Mac.getInstance(algorithm);SecretKeySpec secretKeySpec = new SecretKeySpec(deviceSecret.getBytes(), algorithm);mac.init(secretKeySpec);byte[] macRes = mac.doFinal(macSrc.getBytes());password = String.format("%064x", new BigInteger(1, macRes));} catch (Exception e) {e.printStackTrace();return null;}return this;}
}
AndroidManifest.xml文件
需要添加一些网络的permission和mqtt包android:name="org.eclipse.paho.android.service.MqttService",移植到自己项目,有报错可以参考此文件进行修改添加。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.jiafei.test"><uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/><uses-permission android:name="android.permission.WAKE_LOCK" /><applicationandroid:allowBackup="true"android:icon="@mipmap/ic_launcher"android:label="SmartHomeV2.0"android:networkSecurityConfig="@xml/network_security_config"android:roundIcon="@mipmap/ic_launcher_round"android:supportsRtl="true"android:theme="@style/AppTheme"><activity android:name=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><serviceandroid:name="org.eclipse.paho.android.service.MqttService"android:exported="true" /></application>
</manifest>
activity_main.xml APP界面布局文件
<?xml version="1.0" encoding="utf-8"?><RelativeLayout 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"tools:context=".MainActivity"android:background="#FFFFFF"tools:ignore="ExtraText">//SmartHome文字栏<LinearLayoutandroid:id="@+id/SmartHome"android:layout_width="720dp"android:layout_height="60dp"><TextViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:layout_marginTop="15dp"android:layout_marginLeft="105dp"android:text="SmartHome"android:textColor="#000000"android:textSize="28sp"android:textStyle="bold"></TextView></LinearLayout>//一张智能家居的照片<LinearLayoutandroid:layout_width="720dp"android:layout_height="340dp"android:layout_marginLeft="10dp"android:layout_marginTop="20dp"android:layout_marginRight="10dp"android:gravity="center_vertical"><ImageViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:src="@drawable/home"></ImageView><ImageViewandroid:id="@+id/humtuper"android:layout_width="50dp"android:layout_height="50dp"android:layout_marginStart="35dp"android:src="@drawable/humtuper"></ImageView></LinearLayout>//传感器相关<LinearLayoutandroid:layout_marginTop="340dp"android:layout_width="720dp"android:layout_height="180dp"android:gravity="center_vertical">//传感器 Sensor<TextViewandroid:id="@+id/Sensor1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="10dp"android:layout_marginTop="-70dp"android:text="传感器"android:textColor="#222222"android:textSize="28sp"></TextView><TextViewandroid:id="@+id/Sen2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginTop="-70dp"android:text="Sensor"android:textColor="#336699"android:textSize="18sp"></TextView>//温度图标和读取数据区域<ImageViewandroid:id="@+id/img_humtuper"android:layout_width="50dp"android:layout_height="50dp"android:layout_marginStart="-115dp"android:src="@drawable/humtuper"></ImageView><TextViewandroid:id="@+id/temp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="-45dp"android:layout_marginTop="50dp"android:hint="温度显示"android:textColor="#222222"android:textSize="20sp"></TextView>//红外图标和读取数据区域<ImageViewandroid:id="@+id/img_humi"android:layout_width="50dp"android:layout_height="50dp"android:layout_marginStart="40dp"android:src="@drawable/humi"></ImageView><TextViewandroid:id="@+id/humi"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="-40dp"android:layout_marginTop="50dp"android:hint="湿度显示"android:textColor="#222222"android:textSize="20sp"></TextView>//光照图标和读取数据区域<ImageViewandroid:id="@+id/img_light"android:layout_width="50dp"android:layout_height="50dp"android:layout_marginStart="30dp"android:src="@drawable/light"></ImageView><TextViewandroid:id="@+id/light"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginStart="-50dp"android:layout_marginTop="50dp"android:hint="光照显示"android:textColor="#222222"android:textSize="20sp"></TextView></LinearLayout>//控制器Control部分UI<LinearLayoutandroid:layout_marginTop="520dp"android:layout_width="720dp"android:layout_height="240dp"><TextViewandroid:id="@+id/Crl1"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="10dp"android:text="控制器"android:textColor="#222222"android:textSize="28sp"></TextView><TextViewandroid:id="@+id/Crl2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_marginLeft="-1dp"android:text="Control"android:textColor="#336699"android:textSize="18sp"></TextView>//Button Switch<Buttonandroid:id="@+id/Btn_OpenLED"android:layout_width="90dp"android:layout_height="90dp"android:layout_marginStart="-130dp"android:layout_marginTop="85dp"android:background="@drawable/myswitch"></Button><TextViewandroid:id="@+id/text_LED"android:layout_marginTop="150dp"android:layout_marginLeft="-65dp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="开灯"android:textSize="20dp"android:textColor="#000000"></TextView>//Button Door<Buttonandroid:id="@+id/Btn_Opendoor"android:layout_width="90dp"android:layout_height="90dp"android:layout_marginLeft="55dp"android:layout_marginTop="85dp"android:background="@drawable/opendoor"></Button><TextViewandroid:id="@+id/text_door"android:layout_marginTop="150dp"android:layout_marginLeft="-65dp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="开门"android:textSize="20dp"android:textColor="#000000"></TextView>//Button Beep<Buttonandroid:id="@+id/Btn_Openbeep"android:layout_width="90dp"android:layout_height="90dp"android:layout_marginLeft="45dp"android:layout_marginTop="85dp"android:background="@drawable/openbeep"></Button><TextViewandroid:id="@+id/text_beep"android:layout_marginTop="150dp"android:layout_marginLeft="-75dp"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="蜂鸣器"android:textSize="20dp"android:textColor="#000000"></TextView></LinearLayout>
</RelativeLayout>
后端文件MainActivity.java
三元组根据自己设备的参数进行添加,代码中有详细的变量名。不懂得创建设备,或者不知道添加什么参数的话可以看这篇文章:STM32+ESP8266-连接阿里云-创建云产品流转实现STM32与Android app通讯(1)
package com.jiafei.test;import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import okhttp3.OkHttpClient;
import okhttp3.Response;
import okhttp3.Request;
import java.io.IOException;
import java.util.List;import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;import com.google.gson.Gson;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.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.json.JSONObject;//private String username = "Android_app&k1h2hJkoTA7";
//private String password = "af14964c592410f330c40693ae1bfd12f7f1cfea52d5e08cb0e48876e0966f3d";
//private String clientId = "k1h2hJkoTA7.Android_app|securemode=2,signmethod=hmacsha256,timestamp=1721799788666|";public class MainActivity extends AppCompatActivity {private final String TAG = "AiotMqtt";final private String PRODUCTKEY = "k1h2hJkoTA7";final private String DEVICENAME = "Android_app";final private String DEVICESECRET = "9fe48b26f87e3219c89c655363920db6";/* 自动Topic, 用于上报消息 */final private String PUB_TOPIC = "/" + PRODUCTKEY + "/" + DEVICENAME + "/user/update";/* 自动Topic, 用于接受消息 */final private String SUB_TOPIC = "/" + PRODUCTKEY + "/" + DEVICENAME + "/user/get";/* 自定义Topic,用于上报消息 */final private String CUSTOM_PUB_TOPIC = "/k1h2hJkoTA7/Android_app/user/Android_STM32";/* 自动Topic, 用于接受消息 */final private String CUSTOM_SUB_TOPIC = "/k1h2hJkoTA7/Android_app/user/STM32toAndroid";/* 阿里云Mqtt服务器域名 */final String host = "tcp://" + PRODUCTKEY + ".iot-as-mqtt.cn-shanghai.aliyuncs.com:443";private String clientId;private String userName;private String passWord;MqttAndroidClient mqttAndroidClient;private static final String key1 = "Temp";private static final String key2 = "Humi";private static final String key3 = "light";private TextView data1, data2, data3;private String value1, value2, value3;private boolean isPublishing = false; // 添加一个标志,用于表示是否正在发布消息@RequiresApi(api = Build.VERSION_CODES.M)@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);Log.i("ButtonTest", "onCreate called");setContentView(R.layout.activity_main);/* 获取Mqtt建连信息clientId, username, password */AiotMqttOption aiotMqttOption = new AiotMqttOption().getMqttOption(PRODUCTKEY, DEVICENAME, DEVICESECRET);if (aiotMqttOption == null) {Log.e(TAG, "device info error");} else {clientId = aiotMqttOption.getClientId();userName = aiotMqttOption.getUsername();passWord = aiotMqttOption.getPassword();}/* 创建MqttConnectOptions对象并配置username和password */MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();mqttConnectOptions.setUserName(userName);mqttConnectOptions.setPassword(passWord.toCharArray());data1 = findViewById(R.id.temp);data2 = findViewById(R.id.humi);data3 = findViewById(R.id.light);Button buttonOpenLED = findViewById(R.id.Btn_OpenLED);Button buttonOpenDoor = findViewById(R.id.Btn_Opendoor);Button buttonOpenBeep = findViewById(R.id.Btn_Openbeep);setupButtonListeners(buttonOpenLED, buttonOpenDoor, buttonOpenBeep);/* 创建MqttAndroidClient对象, 并设置回调接口 */mqttAndroidClient = new MqttAndroidClient(getApplicationContext(), host, clientId);mqttAndroidClient.setCallback(new MqttCallback() {@Overridepublic void connectionLost(Throwable cause) {Log.i(TAG, "connection lost");}@Overridepublic void messageArrived(String topic, MqttMessage message) throws Exception {Log.i(TAG, "topic: " + topic + ", msg: " + new String(message.getPayload()));// 根据topic处理消息if (topic.equals(CUSTOM_SUB_TOPIC)) {// 处理从自定义主题接收到的消息handleReceivedMessage(new String(message.getPayload()));}}@Overridepublic void deliveryComplete(IMqttDeliveryToken token) {Log.i(TAG, "msg delivered");}});try {mqttAndroidClient.connect(mqttConnectOptions, null, new IMqttActionListener() {@Overridepublic void onSuccess(IMqttToken asyncActionToken) {Log.i(TAG, "connect succeed");subscribeTopic(CUSTOM_SUB_TOPIC);}@Overridepublic void onFailure(IMqttToken asyncActionToken, Throwable exception) {Log.i(TAG, "connect failed");}});} catch (MqttException e) {e.printStackTrace();}if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {requestPermissions(new String[]{"android.permission.INTERNET"}, 1);}}private void setupButtonListeners(Button buttonOpenLED, Button buttonOpenDoor, Button buttonOpenBeep) {buttonOpenLED.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (!isPublishing) { // 如果没有正在发布isPublishing = true;Log.i("ButtonTest", "LED Button Clicked");publishMessage("{\"LED\":1}");} else {Log.i("ButtonTest", "Already publishing, please wait...");Toast.makeText(MainActivity.this, "Already publishing, please wait...", Toast.LENGTH_SHORT).show();}}});buttonOpenDoor.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (!isPublishing) { // 如果没有正在发布isPublishing = true;Log.i("ButtonTest", "Door Button Clicked");publishMessage("{\"door\":2}");} else {Log.i("ButtonTest", "Already publishing, please wait...");Toast.makeText(MainActivity.this, "Already publishing, please wait...", Toast.LENGTH_SHORT).show();}}});buttonOpenBeep.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {if (!isPublishing) { // 如果没有正在发布isPublishing = true;Log.i("ButtonTest", "Beep Button Clicked");publishMessage("{\"beep\":3}");} else {Log.i("ButtonTest", "Already publishing, please wait...");Toast.makeText(MainActivity.this, "Already publishing, please wait...", Toast.LENGTH_SHORT).show();}}});}/*** 向默认的主题/user/update发布消息** @param payload 消息载荷*/public void publishMessage(String payload) {try {if (!mqttAndroidClient.isConnected()) {// 阻塞直到连接成功或超时mqttAndroidClient.connect().waitForCompletion(5000);}MqttMessage message = new MqttMessage();message.setPayload(payload.getBytes());message.setQos(1);mqttAndroidClient.publish(CUSTOM_PUB_TOPIC, message, null, new IMqttActionListener() {@Overridepublic void onSuccess(IMqttToken asyncActionToken) {Log.i(TAG, "publish succeed!");isPublishing = false; // 发布成功后重置标志}@Overridepublic void onFailure(IMqttToken asyncActionToken, Throwable exception) {Log.i(TAG, "publish failed! Attempting to reconnect...");handlePublishFailure(payload, exception);isPublishing = false; // 发布失败后也重置标志}});} catch (MqttException e) {Log.e(TAG, "Exception occurred during publishing: " + e.toString());e.printStackTrace();isPublishing = false; // 发布异常也要重置标志}}private void handlePublishFailure(String payload, Throwable exception) {Log.e(TAG, "Publish failed due to: " + exception.toString());// 重连并重试发布new Thread(() -> {try {mqttAndroidClient.disconnect();mqttAndroidClient.connect().waitForCompletion(5000);publishMessage(payload); // 再次尝试发布} catch (MqttException e) {Log.e(TAG, "Reconnection failed: " + e.toString());e.printStackTrace();}}).start();}/*** 订阅特定的主题** @param topic mqtt主题*/public void subscribeTopic(String topic) {try {mqttAndroidClient.subscribe(topic, 0, null, new IMqttActionListener() {@Overridepublic void onSuccess(IMqttToken asyncActionToken) {Log.i(TAG, "subscribed succeed");}@Overridepublic void onFailure(IMqttToken asyncActionToken, Throwable exception) {Log.i(TAG, "subscribed failed");}});} catch (MqttException e) {e.printStackTrace();}}private void handleReceivedMessage(final String message) {new Thread(new Runnable() {@Overridepublic void run() {try {Log.i(TAG, "Received message: " + message);JSONObject jsonObject = new JSONObject(message);JSONObject params = jsonObject.getJSONObject("params");if (params.has("light")) {String lightValue = params.getString("light");updateUI(lightValue, key3); // 假设 key3 是光照值}if (params.has("Temp")) {String tempValue = params.getString("Temp");updateUI(tempValue, key1); // 假设 key1 是温度值}if (params.has("Humi")) {String humiValue = params.getString("Humi");updateUI(humiValue, key2); // 假设 key2 是湿度值}} catch (Exception e) {Log.e("HandleMessage", "Error handling received message", e);e.printStackTrace();}}}).start();}private void updateUI(final String value, final String key) {// 根据 key 来更新相应的 UI 元素if (key.equals(key1)) {value1 = value;data1.post(new Runnable() {@Overridepublic void run() {data1.setText(String.format("%s℃", value1));}});} else if (key.equals(key2)) {value2 = value;data2.post(new Runnable() {@Overridepublic void run() {data2.setText(value2);}});} else if (key.equals(key3)) {value3 = value;data3.post(new Runnable() {@Overridepublic void run() {data3.setText(String.format("%slux", value3));}});}}
}
结尾
完整代码可在gzh搜索嵌入式crafter,关注发送关键词:物联网安卓app获得网盘链接。