前言
接着上一篇的文章创建好了设备,云产品转发,让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;
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
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>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
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>
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
后端文件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));
}
});
}
}
}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
- 9.
- 10.
- 11.
- 12.
- 13.
- 14.
- 15.
- 16.
- 17.
- 18.
- 19.
- 20.
- 21.
- 22.
- 23.
- 24.
- 25.
- 26.
- 27.
- 28.
- 29.
- 30.
- 31.
- 32.
- 33.
- 34.
- 35.
- 36.
- 37.
- 38.
- 39.
- 40.
- 41.
- 42.
- 43.
- 44.
- 45.
- 46.
- 47.
- 48.
- 49.
- 50.
- 51.
- 52.
- 53.
- 54.
- 55.
- 56.
- 57.
- 58.
- 59.
- 60.
- 61.
- 62.
- 63.
- 64.
- 65.
- 66.
- 67.
- 68.
- 69.
- 70.
- 71.
- 72.
- 73.
- 74.
- 75.
- 76.
- 77.
- 78.
- 79.
- 80.
- 81.
- 82.
- 83.
- 84.
- 85.
- 86.
- 87.
- 88.
- 89.
- 90.
- 91.
- 92.
- 93.
- 94.
- 95.
- 96.
- 97.
- 98.
- 99.
- 100.
- 101.
- 102.
- 103.
- 104.
- 105.
- 106.
- 107.
- 108.
- 109.
- 110.
- 111.
- 112.
- 113.
- 114.
- 115.
- 116.
- 117.
- 118.
- 119.
- 120.
- 121.
- 122.
- 123.
- 124.
- 125.
- 126.
- 127.
- 128.
- 129.
- 130.
- 131.
- 132.
- 133.
- 134.
- 135.
- 136.
- 137.
- 138.
- 139.
- 140.
- 141.
- 142.
- 143.
- 144.
- 145.
- 146.
- 147.
- 148.
- 149.
- 150.
- 151.
- 152.
- 153.
- 154.
- 155.
- 156.
- 157.
- 158.
- 159.
- 160.
- 161.
- 162.
- 163.
- 164.
- 165.
- 166.
- 167.
- 168.
- 169.
- 170.
- 171.
- 172.
- 173.
- 174.
- 175.
- 176.
- 177.
- 178.
- 179.
- 180.
- 181.
- 182.
- 183.
- 184.
- 185.
- 186.
- 187.
- 188.
- 189.
- 190.
- 191.
- 192.
- 193.
- 194.
- 195.
- 196.
- 197.
- 198.
- 199.
- 200.
- 201.
- 202.
- 203.
- 204.
- 205.
- 206.
- 207.
- 208.
- 209.
- 210.
- 211.
- 212.
- 213.
- 214.
- 215.
- 216.
- 217.
- 218.
- 219.
- 220.
- 221.
- 222.
- 223.
- 224.
- 225.
- 226.
- 227.
- 228.
- 229.
- 230.
- 231.
- 232.
- 233.
- 234.
- 235.
- 236.
- 237.
- 238.
- 239.
- 240.
- 241.
- 242.
- 243.
- 244.
- 245.
- 246.
- 247.
- 248.
- 249.
- 250.
- 251.
- 252.
- 253.
- 254.
- 255.
- 256.
- 257.
- 258.
- 259.
- 260.
- 261.
- 262.
- 263.
- 264.
- 265.
- 266.
- 267.
- 268.
- 269.
- 270.
- 271.
- 272.
- 273.
- 274.
- 275.
- 276.
- 277.
- 278.
- 279.
- 280.
- 281.
- 282.
- 283.
- 284.
- 285.
- 286.
- 287.
- 288.
- 289.
- 290.
- 291.
- 292.
- 293.
- 294.
- 295.
- 296.
- 297.
- 298.
- 299.
- 300.
- 301.
- 302.
- 303.
- 304.
- 305.
- 306.
- 307.
- 308.
- 309.
- 310.
- 311.
- 312.
- 313.
- 314.
- 315.
- 316.
- 317.
- 318.
- 319.
- 320.
- 321.
- 322.
- 323.
- 324.
结尾
完整代码可在gzh搜索嵌入式crafter,关注发送关键词:物联网安卓app获得网盘链接。