基于ESP32的远程开关灯控制(ESP32+舵机+Android+物联网云平台)——下

发布于:2025-02-12 ⋅ 阅读:(159) ⋅ 点赞:(0)

Android部分和云平台数据流转

Android部分需要一定的基础,至少你得有Android Studio 这个软件吧。
如有需要可以联系我,简单的修改和Android软件可以帮代做。
可自行下载Android,源码可全部给出。

创建工程+配置工程

首先创建一个空的工程。
在这里插入图片描述
主要注意两个地方,一个是保存路径,一个是最后那个选择,另外两个似乎是另一种语言。使用java语言编写就选这个选项。

然后需要配置AndroidManifest.xml这个文件
主要是配置所需的权限,app需要访问网络的权限
权限

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET" />

现在的版本基本上要求动态申请权限,所以需要在Activity中进行申请。

   private List<String> mPermissionlist;
    private static final int REQUEST_EXTERNAL_STORAGE = 10;
    private static final String[] PERMISSIONS = {
            "android.permission.ACCESS_NETWORK_STATE",
            "android.permission.INTERNET",
    };
    private void initPermission(){
        mPermissionlist.clear();
        //逐个判断你要的权限是否已经通过
        for (String permission : PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                mPermissionlist.add(permission);//添加还未授予的权限
            }
        }
        //申请权限
        if(mPermissionlist.size()>0){//有权限没有通过,需要申请
            ActivityCompat.requestPermissions(this,PERMISSIONS,REQUEST_EXTERNAL_STORAGE);
        }
    }

最后,mqtt协议相关库导入。Android连接到阿里云有官方的sdk可以进行下载然后使用,也可以直接通过mqtt协议进行连接,这里使用库连接,就不再去下载SDK了。
在模块中的build.gradle中找到对应位置
mqtt_lib

implementation 'org.eclipse.paho:org.eclipse.paho.client.mqttv3:1.2.5'

UI界面

主要是两个fragment,两个简单页面,一个两个按钮控制,一个调试用。

主界面fragment_home.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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">
<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_centerInParent="true"
    android:orientation="vertical"
    >

    <Button
        android:id="@+id/light_on"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:text="开灯"
        android:textSize="50sp"
        android:background="@drawable/on_button_shape_circle"
        android:gravity="center"/>
    
    <Button
        android:id="@+id/light_off"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_centerHorizontal="true"
        android:gravity="center"
        android:textSize="50sp"
        android:background="@drawable/off_button_shape_circle"
        android:text="关灯" />
</LinearLayout>
</RelativeLayout>

两个圆形按钮的实现
drawable中 on_button_shape_circle.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="oval">
    <corners android:radius="0dp"/><!-- 设置圆角弧度 -->
    <solid android:color="#3ddc84" />   <!-- 设置背景颜色 -->
    <stroke
        android:width="0dp"
        android:color="#fff" /><!-- 设置边框 -->
</shape>

调试界面fragment_debug.xml

java实现功能

MainActivity.java

public class MainActivity extends AppCompatActivity {
    private Fragment fragment1;
    private Fragment fragment2;
    private boolean isFragment1Displayed = true;
    public static Handler handler;
    private List<String> mPermissionlist;
    private static final int REQUEST_EXTERNAL_STORAGE = 10;
    private static final String[] PERMISSIONS = {
            "android.permission.ACCESS_NETWORK_STATE",
            "android.permission.INTERNET",
    };
    private void initPermission(){
        mPermissionlist.clear();
        //逐个判断你要的权限是否已经通过
        for (String permission : PERMISSIONS) {
            if (ContextCompat.checkSelfPermission(this, permission) != PackageManager.PERMISSION_GRANTED) {
                mPermissionlist.add(permission);//添加还未授予的权限
            }
        }
        //申请权限
        if(mPermissionlist.size()>0){//有权限没有通过,需要申请
            ActivityCompat.requestPermissions(this,PERMISSIONS,REQUEST_EXTERNAL_STORAGE);
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // 权限申请和设备验证连接到阿里云平台
//        动态申请权限
        mPermissionlist = new ArrayList<>();
        //6.0才用动态权限 现在都大于6.0了
        initPermission();
        System.out.println("申请完成");
        // Initialize fragments
        fragment1 = new FragmentHome();
        fragment2 = new FragmentDebug();
        // Load Fragment1 by default
        loadFragment(fragment1);
        System.out.println("加载完成");
        // Find the button and set a long click listener
        Button switchButton = findViewById(R.id.switchButton);
        switchButton.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if (isFragment1Displayed) {
                    loadFragment(fragment2);
                } else {
                    loadFragment(fragment1);
                }
                isFragment1Displayed = !isFragment1Displayed;
                return true; // Return true to indicate the long click was handled
            }
        });
        handler = new Handler(Looper.getMainLooper()) {//用于处理
            @SuppressLint("SetTextI18n")
            public void handleMessage(@NonNull Message msg) {
                super.handleMessage(msg);
                switch (msg.what) {
                    case 1: //开机校验更新回传
                        break;
                    case 2:  // 反馈回传
                        break;
                    case 3:  //MQTT 收到消息回传
                        System.out.println(msg.obj.toString());   // 显示MQTT数据
                        break;
                    case 200:   //连接成功
                        switchButton.setEnabled(true);
                        System.out.println("连接成功");
                        break;
                    case 404:  //连接失败
                        Toast.makeText(MainActivity.this, "连接失败", Toast.LENGTH_SHORT).show();
                        switchButton.setEnabled(false);
                        System.out.println("连接失败");
                        break;
                    default:
                        break;
                }
            }
        };
        //设备注册,连接到阿里云平台
        MqttHelper mqttHelper = new MqttHelper();
        mqttHelper.MqttInit();
        //初始化就是配置了对应的参数,在类中
        //然后需要设置回调函数
    }
    private void loadFragment(Fragment fragment) {
        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.fragmentContainer, fragment);
        transaction.addToBackStack(null); // 将当前事务加入到返回栈,支持返回时回到之前的 Fragment
        transaction.commit();
    }
}

主要实现的就是一个界面的长按切换fragment权限申请,还有一个mqtthelper的对象创建,具体的连接操作在helper类中实现。

public class MqttHelper {
    public static MqttClient mqttClient;
    private MqttConnectOptions options;
    public static final String servo_topic = "";
    public final String person_detected_topic = "";
    public MqttHelper() {
    }
    // MQTT连接函数
    private void MqttConnect() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    if (!mqttClient.isConnected())  //如果还未连接
                    {
                        mqttClient.connect(options);
                        //连接成功设置订阅
                        mqttClient.subscribe(servo_topic,1);
                        mqttClient.subscribe(person_detected_topic,1);
                        MainActivity.handler.obtainMessage(200).sendToTarget();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    e.getMessage();
                    MainActivity.handler.obtainMessage(404).sendToTarget();
                }

            }
        }).start();
    }
    // MQTT重新连接函数
    private void startReconnect() {
        ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
        scheduler.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                if (!mqttClient.isConnected()) {
//                    Log.d("MQTT", "Connection lost, attempting to reconnect...");
                    MqttConnect();
                }
            }
        }, 1000, 10 * 1000, TimeUnit.MILLISECONDS);
    }
    public void MqttInit() {
        try {
            String clientId = "";
            // url  端口号为 1883
            String mqttHostUrl = "tcp://iot-06z00by4rtywg1d.mqtt.iothub.aliyuncs.com";
            String username = "";
            String passwd = "";
            System.out.println("准备创建");
            mqttClient = new MqttClient(mqttHostUrl, clientId, new MemoryPersistence());
            System.out.println("创建成功");
            // 设置连接选项
            options = new MqttConnectOptions();
//            mqttConnectOptions.setCleanSession(false);
            options.setUserName(username); // 用户名:productKey + deviceName
            options.setPassword(passwd.toCharArray()); // 密码:deviceSecret
            options.setConnectionTimeout(10);
            // 设置会话心跳时间 单位为秒 服务器30秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
            options.setKeepAliveInterval(30);
            mqttClient.setCallback(new MqttCallback() {
                @Override
                public void connectionLost(Throwable cause) {
                    Log.d("MQTT", "Connection lost, attempting to reconnect...");
                    // 这里可以添加重连逻辑
//                    startReconnect();
                }
                @Override
                public void messageArrived(String topic, MqttMessage message) throws Exception {
                    // 处理接收到的消息
                    System.out.println("messageArrived----------topic: "+ topic);
                    System.out.println("message: "+ message);
                    System.out.println(" Message: " + new String(message.getPayload()));
                    //处理逻辑
                }
                @Override
                public void deliveryComplete(IMqttDeliveryToken token) {
                    // 处理消息发送完成后的回调
                    System.out.println("delivery success");
                }
            });
            // 连接到 MQTT 服务器
            mqttClient.connect(options);
            System.out.println("mqtt连接成功");
            //连接成功设置订阅
            mqttClient.subscribe(servo_topic,1);
            mqttClient.subscribe(person_detected_topic,1);
//            MainActivity.handler.obtainMessage(200, obj).sendToTarget();  同样可以带参数传递
            MainActivity.handler.obtainMessage(200).sendToTarget();
//            // 发布消息
//            String publishTopic = "/sys/" + productKey + "/" + deviceName + "/thing/event/property/post";
//            MqttMessage message = new MqttMessage("Test message".getBytes());
//            message.setQos(1); // 设置消息 QoS(质量服务)
//            mqttClient.publish(publishTopic, message);
//            Log.d("MQTT", "Message sent: Test message");

        } catch (Exception e) {
            e.printStackTrace();
            MainActivity.handler.obtainMessage(404).sendToTarget();
        }
    }
    // 断开 MQTT 连接
    public void disconnect() {
        try {
            if (mqttClient != null && mqttClient.isConnected()) {
                mqttClient.disconnect();
                Log.d("MQTT", "Disconnected from broker.");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

这里需要注意的就一个地方mqttHostUrl = “tcp://iot-06z00by4rtywg1d.mqtt.iothub.aliyuncs.com”;
这里需要加一个前缀,不然无法连接成功。tcp://

需要加前缀“tcp://String mqttHostUrl = "tcp://iot-06z00by4rtywg1d.mqtt.iothub.aliyuncs.com";

MqttHelper中实现了初始化函数,MqttInit配置设备Mqtt的连接参数,创建连接对象,然后进行初次连接,如果连接失败会尝试重连,连接成功后会进行订阅和回调函数的设置,回调函数用来处理到达的信息。
这里暂时没有进行处理,目前还没有什么需要展示的信息,可以配置,有人经过时消息转发到手机从而实现提醒功能。

另外就是调试fragment_debug中的逻辑就是对按键和手动输入值进行处理,fragment_home也是类似操作,而且更简单就不列出来了。

public class FragmentDebug extends Fragment {
    public FragmentDebug() {
        // Required empty public constructor
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        // 加载 Fragment 的布局
        View view = inflater.inflate(R.layout.fragment_debug, container, false);
        // 获取控件引用
        EditText inputVal = view.findViewById(R.id.inputVal);
        Button homing = view.findViewById(R.id.homing);
        Button setButton = view.findViewById(R.id.setButton);
        // 设置按钮的点击事件
        homing.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 发布消息  开灯操作
                MqttMessage message = new MqttMessage("90".getBytes());
                message.setQos(1); // 设置消息 QoS(质量服务)
                try {
                    MqttHelper.mqttClient.publish(MqttHelper.servo_topic, message);
                } catch (MqttException e) {
                    Toast.makeText(getActivity(), "归位失败", Toast.LENGTH_SHORT).show();
                    System.out.println("归位失败");
                    throw new RuntimeException(e);
                }
            }
        });
        // 设置按钮的点击事件
        setButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 发布消息  开灯操作
                String setVal = inputVal.getText().toString().trim();
                MqttMessage message = new MqttMessage(setVal.getBytes());
                message.setQos(1); // 设置消息 QoS(质量服务)
                try {
                    MqttHelper.mqttClient.publish(MqttHelper.servo_topic, message);
                } catch (MqttException e) {
                    Toast.makeText(getActivity(), "设置 "+setVal+" 失败", Toast.LENGTH_SHORT).show();
                    System.out.println("设置 "+setVal+" 失败");
                    throw new RuntimeException(e);
                }
            }
        });
        return view;
    }
}

主要就是两个按键的操作,都是发送一个舵机角度值给云平台,一个是将输入的舵机角度值,一个是置为90,90°舵机处于平的状态,这样不影响手动开关灯。fragment_home类似。

云平台数据流转

无论是esp32还是android都只是和云平台的交互,想要实现esp32和android的通讯就需要借助云平台的帮助,将一方的消息转发给另一方,比如将Android的控制消息转发给esp32从而实现控制。

在这里插入图片描述
阿里云平台中的消息转发–》云产品流转就可以实现这样的功能。
首先需要创建数据源和数据目的,数据源就是来自Android设备的控制信号,数据目的自然就是ESP32对应topic,这样转发到ESP32就能进行接收和解析。
创建数据目的后需要为其添加Topic。需要选择产品和设备,以及topic
在这里插入图片描述
根据自己创建的产品和设备进行选择输入,注意这里要选android 设备,另外这里的主题选用的是我们自定义的topic,也可以选择其他topic。

数据目的,发布到另一个topic,就是esp32的那个topic,产品选对。
在这里插入图片描述
然后就需要创建解析器,用来转发数据。
首先需要关联数据源和数据目的
在这里插入图片描述
最后解析器脚本只需要改对应主题就行,就是ESP32设备对应的主题。
在这里插入图片描述

// 如果默认脚本自动保存过,继续绑定数据目的,默认脚本不会自动更新
// 此时清空脚本并保存之后,重新进入草稿页即可重新生成包含最新数据目的的默认脚本
// 设备上报数据内容,json格式
var data = payload('json');
// 流转到另一个Topic
writeIotTopic(1005, "/***/${DeviceName}/user/servo_control", data);

编辑好后可以进行简单的调试,可以正确的接收消息即可。
最后进行发布,然后启动解析器即可。

实物演示

配置好代码启动esp32,将app下载到手机并打开后,云平台显示在线即可实现通讯。

online
效果演示

light_control

以上就是全部内容了,有什么问题欢迎评论区留言,有需要帮助的地方也欢迎留言或私信。
若觉得有帮助,欢迎点赞关注收藏💕。


网站公告

今日签到

点亮在社区的每一天
去签到