前言
接着上一篇的文章创建好了设备,云产品转发,让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());
// clientId
this.clientId = productKey + "." + deviceName + "|timestamp=" + timestamp +
",_v=paho-android-1.0.0,securemode=2,signmethod=hmacsha256|";
// userName
this.username = deviceName + "&" + productKey;
// password
String 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" />
<application
android: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>
<service
android: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文字栏
<LinearLayout
android:id="@+id/SmartHome"
android:layout_width="720dp"
android:layout_height="60dp">
<TextView
android: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>
//一张智能家居的照片
<LinearLayout
android:layout_width="720dp"
android:layout_height="340dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="20dp"
android:layout_marginRight="10dp"
android:gravity="center_vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="@drawable/home">
</ImageView>
<ImageView
android:id="@+id/humtuper"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="35dp"
android:src="@drawable/humtuper"></ImageView>
</LinearLayout>
//传感器相关
<LinearLayout
android:layout_marginTop="340dp"
android:layout_width="720dp"
android:layout_height="180dp"
android:gravity="center_vertical">
//传感器 Sensor
<TextView
android: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>
<TextView
android: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>
//温度图标和读取数据区域
<ImageView
android:id="@+id/img_humtuper"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="-115dp"
android:src="@drawable/humtuper">
</ImageView>
<TextView
android: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>
//红外图标和读取数据区域
<ImageView
android:id="@+id/img_humi"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="40dp"
android:src="@drawable/humi">
</ImageView>
<TextView
android: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>
//光照图标和读取数据区域
<ImageView
android:id="@+id/img_light"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginStart="30dp"
android:src="@drawable/light">
</ImageView>
<TextView
android: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
<LinearLayout
android:layout_marginTop="520dp"
android:layout_width="720dp"
android:layout_height="240dp">
<TextView
android: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>
<TextView
android: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
<Button
android: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>
<TextView
android: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
<Button
android: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>
<TextView
android: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
<Button
android: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>
<TextView
android: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)
@Override
protected 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() {
@Override
public void connectionLost(Throwable cause) {
Log.i(TAG, "connection lost");
}
@Override
public 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()));
}
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
Log.i(TAG, "msg delivered");
}
});
try {
mqttAndroidClient.connect(mqttConnectOptions, null, new IMqttActionListener() {
@Override
public void onSuccess(IMqttToken asyncActionToken) {
Log.i(TAG, "connect succeed");
subscribeTopic(CUSTOM_SUB_TOPIC);
}
@Override
public 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() {
@Override
public 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() {
@Override
public 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() {
@Override
public 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() {
@Override
public void onSuccess(IMqttToken asyncActionToken) {
Log.i(TAG, "publish succeed!");
isPublishing = false; // 发布成功后重置标志
}
@Override
public 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() {
@Override
public void onSuccess(IMqttToken asyncActionToken) {
Log.i(TAG, "subscribed succeed");
}
@Override
public 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() {
@Override
public 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() {
@Override
public void run() {
data1.setText(String.format("%s℃", value1));
}
});
} else if (key.equals(key2)) {
value2 = value;
data2.post(new Runnable() {
@Override
public void run() {
data2.setText(value2);
}
});
} else if (key.equals(key3)) {
value3 = value;
data3.post(new Runnable() {
@Override
public void run() {
data3.setText(String.format("%slux", value3));
}
});
}
}
}
结尾
完整代码可在gzh搜索嵌入式crafter,关注发送关键词:物联网安卓app获得网盘链接。