STM32+ESP8266-连接阿里云-物联网通用Android app(2)

前言

接着上一篇的文章创建好了设备,云产品转发,让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获得网盘链接。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值