• 基于局域网内物联网的androidAPP开发——实现消息上报与命令下达


    目录

    写在前面

    前提准备 

    本次干的事情——编写安卓app

    正文

    比如 子线程定时获取数据库信息实时更新UI

     比如子线程判断主线程事件进行数据操作实现对设备的控制


    写在前面

    为实现第三方移动端控制的要求,我需要实现一个在个人局域网内控制设备的移动端APP。

    前提准备 

    一个已经实现好通信控制的硬件和相关外设(单片机)。

    一块stm32主控板,若干块通信模块(esp32,lora模块)。

    一个emqx的mqtt消息服务器。

    一个自己写网络接口的java客户端(一个基于springboot整合mybatis的后端服务器,提供命令调用的接口和监听硬件采集的数据并转储于数据库)。

    这个java客户端连接mqtt服务器,订阅数据获取的主题,发布对应主题的消息以控制硬件。

    在以上的前提都准备后,理论上我已经干的差不多的,安卓只是一个写界面来更好地服务与我控制的操作而已。。。。我可以在网页上直接访问java客户端提供的接口,来访问硬件采集的数据,控制硬件的外设,以及干更多你想干的事情(比如RF高频卡感应来模拟门禁,通过发送的信息实现语音播报和语音控制等等)

    本次干的事情——编写安卓app

    我的想法就是越简单越好,这会让人更加地清晰和明白,我没有像网上一般的那样让安卓去连接

    mqtt服务器,然后通过安卓与mqtt服务器直接打交道,(获取数据和命令控制)。但是,我只是想直接简单明了的调用接口,以最简单的http请求的方式实现我的目的。为了这个,java客户端干了所有的事,再提供访问的接口。

    参考我的java客户端

    局域网内用JAVA建立MQTT客户端监听MQTT服务器消息并持久化到数据库_昊月光华的博客-CSDN博客_java mqtt服务端

    java客户端运行

     提供接口(http请求)访问

    发布命令

    当然安卓连接mqtt服务器也不是一件难事,问题在于假如我有多个应用程序都要访问数据和控制呢?哪不是每一个都得连接mqtt服务器,每一个都要写监听对应主题以及发布主题消息的方法?

    当然,目前很多的外网云平台正是提供第三方访问的接口请求。也就是API

    比如巴法云平台,华为云平台等等。

    正文

    首先是编写界面和交互逻辑代码,最主要的也就是主线程与子线程的通信,也就是子线程获取数据,主线程更新界面。

    这里有常见几种的情形:

    • 在一个页面内(activity)内,创建一个内部类(继承线程类,重写run方法),子线程不断地获取数据,然后通过runonUIthread 更新界面。
    • 主线程持有子线程的handler引用,子线程收到主线程发过来的message ,通过message.what判断是哪个控件发过来的,然后进行数据操作(访问数据库等等),数据操作完后,通过runonUIthread 更新UI界面。

    比如 子线程定时获取数据库信息实时更新UI

    在首页中

    1. package com.example.yuezhenhao;
    2. import androidx.appcompat.app.AppCompatActivity;
    3. import android.annotation.SuppressLint;
    4. import android.content.Intent;
    5. import android.os.Bundle;
    6. import android.os.Handler;
    7. import android.os.Looper;
    8. import android.os.Message;
    9. import android.util.Log;
    10. import android.view.View;
    11. import android.widget.ImageView;
    12. import android.widget.TextView;
    13. import android.widget.Toast;
    14. import com.alibaba.fastjson.JSONArray;
    15. import com.alibaba.fastjson.JSONObject;
    16. import com.example.yuezhenhao.HttpRequest.DAO;
    17. import java.security.PrivateKey;
    18. import java.util.Timer;
    19. import java.util.TimerTask;
    20. public class MainActivity extends AppCompatActivity {
    21. private View exit,mainpage,control,chart,querypage;
    22. private TextView runt, temp, humi, ls,tl,hl,ll,state,isc;
    23. private String TAG="MAIN";
    24. int i=0;
    25. private Handler mHandler= new Handler(Looper.getMainLooper()) {
    26. @Override
    27. //重写handleMessage方法,根据msg中what的值判断是否执行后续操作
    28. public void handleMessage(Message msg) {
    29. if (msg.what == 0) {
    30. i++;
    31. runt.setText(String.valueOf(i));
    32. }
    33. }
    34. };
    35. @Override
    36. protected void onCreate(Bundle savedInstanceState) {
    37. super.onCreate(savedInstanceState);
    38. setContentView(R.layout.activity_main);
    39. //开启工作线程
    40. WorkThread workThread = new WorkThread();
    41. workThread.start();
    42. querypage=findViewById(R.id.querypage);
    43. init(); //初始化控件
    44. //转主页
    45. mainpage.setOnClickListener(new View.OnClickListener() {
    46. @Override
    47. public void onClick(View view) {
    48. Log.e(TAG, "ToMainPage ");
    49. }
    50. });
    51. //转控制页面
    52. control.setOnClickListener(new View.OnClickListener() {
    53. @Override
    54. public void onClick(View view) {
    55. Log.e(TAG, "To Control");
    56. Intent intent = new Intent(MainActivity.this, Conrrol.class);
    57. startActivity(intent);
    58. }
    59. });
    60. //转图表页面
    61. chart.setOnClickListener(new View.OnClickListener() {
    62. @Override
    63. public void onClick(View view) {
    64. Log.e(TAG, "To Chart");
    65. Intent intent = new Intent(MainActivity.this, DatesTable.class);
    66. startActivity(intent);
    67. }
    68. });
    69. //退出程序
    70. exit.setOnClickListener(new View.OnClickListener() {
    71. @Override
    72. public void onClick(View view) {
    73. //退出整个应用程序
    74. Log.e(TAG, "exit" );
    75. finishAffinity();
    76. System.exit(0);
    77. }
    78. });
    79. // 转查询页面
    80. querypage.setOnClickListener(new View.OnClickListener() {
    81. @Override
    82. public void onClick(View view) {
    83. Log.e(TAG, "query" );
    84. Intent intent = new Intent(MainActivity.this, QuertDate.class);
    85. startActivity(intent);
    86. }
    87. });
    88. }
    89. /**
    90. * 初始化组件
    91. */
    92. public void init() {
    93. runt = findViewById(R.id.conf);
    94. temp = findViewById(R.id.temp);
    95. humi= findViewById(R.id.humi);
    96. ls = findViewById(R.id.ls);
    97. tl = findViewById(R.id.tl);
    98. hl= findViewById(R.id.hl);
    99. ll = findViewById(R.id.ll);
    100. isc=findViewById(R.id.isc);
    101. state=findViewById(R.id.state);
    102. state.setText("连接中");
    103. mainpage=findViewById(R.id.mainpage);
    104. control=findViewById(R.id.control);
    105. chart=findViewById(R.id.table);
    106. exit=findViewById(R.id.exit);
    107. }
    108. //工作线程刷新主页数据
    109. class WorkThread extends Thread {
    110. private String _temp;
    111. private String _humi;
    112. private String _ls;
    113. //定义阈值
    114. private String TempLimit;
    115. private String HumiLimit;
    116. private String LsLimit;
    117. private String _isc; //是否自动控制
    118. private boolean cons=false;
    119. private Handler workhandler;
    120. private int time=0;
    121. public void run() {
    122. new Timer().schedule(new TimerTask() {
    123. @SuppressLint("ResourceAsColor")
    124. @Override
    125. public void run() {
    126. time++;
    127. JSONArray limits= null;
    128. JSONObject LatestDates=null;
    129. try {
    130. limits = DAO.GetLimit();
    131. LatestDates=(JSONObject) DAO.GetLatest().get(0);
    132. cons=true;
    133. TempLimit=((JSONObject)limits.get(0)).getString("val");
    134. HumiLimit=((JSONObject)limits.get(1)).getString("val");
    135. LsLimit=((JSONObject)limits.get(2)).getString("val");
    136. _isc=((JSONObject)limits.get(3)).getString("val");
    137. _temp=LatestDates.getString("msg1")+"℃";
    138. _humi=LatestDates.getString("msg2")+"%";
    139. _ls=LatestDates.getString("msg3")+"lx";
    140. if(_isc.equals("1")){
    141. _isc="开启";
    142. }
    143. else
    144. _isc="关闭";
    145. } catch (Exception e) {
    146. e.printStackTrace();
    147. cons=false;
    148. }
    149. MainActivity.this.runOnUiThread(new Runnable() {
    150. @Override
    151. public void run() {
    152. runt.setText(time+"s");
    153. tl.setText(TempLimit);
    154. hl.setText(HumiLimit);
    155. ll.setText(LsLimit);
    156. isc.setText(_isc);
    157. temp.setText(_temp);
    158. humi.setText(_humi);
    159. ls.setText(_ls);
    160. if(cons)state.setText("已连接");
    161. else {
    162. state.setText("断开连接");
    163. state.setTextColor(R.color.red);
    164. }
    165. }
    166. });
    167. }
    168. }, 0, 1000);
    169. }
    170. }
    171. }

     

     

     比如子线程判断主线程事件进行数据操作实现对设备的控制

    1. package com.example.yuezhenhao;
    2. import androidx.appcompat.app.AppCompatActivity;
    3. import android.os.Bundle;
    4. import android.os.Handler;
    5. import android.os.Looper;
    6. import android.os.Message;
    7. import android.os.StrictMode;
    8. import android.text.Editable;
    9. import android.util.Log;
    10. import android.view.View;
    11. import android.widget.Button;
    12. import android.widget.CompoundButton;
    13. import android.widget.EditText;
    14. import android.widget.Switch;
    15. import android.widget.Toast;
    16. import com.example.yuezhenhao.HttpRequest.DAO;
    17. import java.util.HashMap;
    18. import java.util.LinkedList;
    19. import java.util.List;
    20. import java.util.Map;
    21. public class Conrrol extends AppCompatActivity {
    22. private Switch dev1,dev2,dev3,dev4;
    23. private Button msgbn1,msgbn2,msgbn3,pbt1,pbt2;
    24. private EditText msg1,msg2,msg3,mtor1,mtor2;
    25. private boolean flag1=false;
    26. public Handler mHandler;
    27. private String TAG="CONTROL";
    28. @Override
    29. protected void onCreate(Bundle savedInstanceState) {
    30. super.onCreate(savedInstanceState);
    31. setContentView(R.layout.activity_conrrol);
    32. init();
    33. SubThread subThread=new SubThread();
    34. subThread.start();
    35. mHandler=new Handler(Looper.getMainLooper()){
    36. @Override
    37. public void handleMessage(Message msg) {
    38. if(msg.what == 1){
    39. Toast.makeText(Conrrol.this, "操作成功", Toast.LENGTH_SHORT).show();
    40. }
    41. else
    42. Toast.makeText(Conrrol.this, "操作失败", Toast.LENGTH_SHORT).show();
    43. }
    44. };
    45. //控制设备1-3的常规开和关
    46. dev1.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    47. @Override
    48. public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
    49. if (dev1.isChecked()) {
    50. Log.e(TAG, "ON DEV1");
    51. PackageDate(subThread.subhandler,"dev1","20");
    52. }
    53. else {
    54. Log.e(TAG, "OFF DEV1");
    55. PackageDate(subThread.subhandler,"dev1","0");
    56. }
    57. }
    58. });
    59. dev2.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    60. @Override
    61. public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
    62. if (dev2.isChecked()) {
    63. Log.e(TAG, "ON DEV2");
    64. PackageDate(subThread.subhandler,"dev2","20");
    65. }
    66. else {
    67. Log.e(TAG, "OFF DEV2");
    68. PackageDate(subThread.subhandler,"dev2","0");
    69. }
    70. }
    71. });
    72. dev3.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    73. @Override
    74. public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
    75. if (dev3.isChecked()) {
    76. Log.e(TAG, "ON DEV3");
    77. PackageDate(subThread.subhandler,"dev3","5000");
    78. }
    79. else {
    80. Log.e(TAG, "OFF DEV3");
    81. PackageDate(subThread.subhandler,"dev3","0");
    82. }
    83. }
    84. });
    85. //设置是否自动控制
    86. dev4.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
    87. @Override
    88. public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
    89. if (dev4.isChecked()) {
    90. Log.e(TAG, "自动控制开启");
    91. PkLimitAndIsc(subThread.subhandler,"4","1");
    92. } else {
    93. Log.e(TAG, "自动控制关闭");
    94. PkLimitAndIsc(subThread.subhandler,"4","0");
    95. }
    96. }
    97. });
    98. //给字段msg1设置阈值
    99. msgbn1.setOnClickListener(new View.OnClickListener() {
    100. @Override
    101. public void onClick(View view) {
    102. String val=msg1.getText().toString();
    103. PkLimitAndIsc(subThread.subhandler,"1",val);
    104. }
    105. });
    106. //给字段msg2设置阈值
    107. msgbn2.setOnClickListener(new View.OnClickListener() {
    108. @Override
    109. public void onClick(View view) {
    110. String val=msg2.getText().toString();
    111. PkLimitAndIsc(subThread.subhandler,"2",val);
    112. }
    113. });
    114. //给字段msg3设置阈值
    115. msgbn3.setOnClickListener(new View.OnClickListener() {
    116. @Override
    117. public void onClick(View view) {
    118. String val=msg3.getText().toString();
    119. PkLimitAndIsc(subThread.subhandler,"3",val);
    120. }
    121. });
    122. //电机1控制, 支持PWM和正反转
    123. pbt1.setOnClickListener(new View.OnClickListener() {
    124. @Override
    125. public void onClick(View view) {
    126. String val=mtor1.getText().toString();
    127. PackageDate(subThread.subhandler,"dev1",val);
    128. }
    129. });
    130. //电机2控制, 支持PWM和正反转
    131. pbt2.setOnClickListener(new View.OnClickListener() {
    132. @Override
    133. public void onClick(View view) {
    134. String val=mtor2.getText().toString();
    135. PackageDate(subThread.subhandler,"dev2",val);
    136. }
    137. });
    138. }
    139. /**
    140. * 基于不同id的按钮控件打包数据
    141. * 数据:控制执行部件的数据
    142. */
    143. public void PackageDate(Handler handler,String name,String val){
    144. Map[] li=new Map[2];
    145. try {
    146. li[0]=new HashMap();
    147. li[1]=new HashMap();
    148. li[0].put("name",name);
    149. li[1].put("val",val);
    150. Message msg=Message.obtain();
    151. msg.what=1;
    152. msg.obj=li;
    153. handler.sendMessage(msg);
    154. } catch (Exception e) {
    155. e.printStackTrace();
    156. }
    157. }
    158. /**
    159. * 基于不同id的按钮控件打包数据
    160. * 数据:设置阈值的数据或者是是否自动控制的标记
    161. */
    162. public void PkLimitAndIsc(Handler handler,String name,String val){
    163. Message msg=Message.obtain();
    164. Object[] obj=new Object[2];
    165. obj[0]=new Object();
    166. obj[1]=new Object();
    167. obj[0]=name;
    168. obj[1]=val;
    169. msg.what=2;
    170. msg.obj=obj;
    171. handler.sendMessage(msg);
    172. }
    173. /**
    174. * 初始化控件
    175. */
    176. public void init(){
    177. dev1=findViewById(R.id.dev1);
    178. dev2=findViewById(R.id.dev2);
    179. dev3=findViewById(R.id.dev3);
    180. dev4=findViewById(R.id.dev4);
    181. msg1=findViewById(R.id.msg1);
    182. msg2=findViewById(R.id.msg2);
    183. msg3=findViewById(R.id.msg3);
    184. msgbn1=findViewById(R.id.msg_bn1);
    185. msgbn2=findViewById(R.id.msg_bn2);
    186. msgbn3=findViewById(R.id.msg_bn3);
    187. pbt1=findViewById(R.id.pbn1);
    188. pbt2=findViewById(R.id.pbn2);
    189. mtor1=findViewById(R.id.mtor1); //两个电机的可编辑框的值的获取
    190. mtor2=findViewById(R.id.mtor2);
    191. }
    192. //子线程协调主线程发起网络请求
    193. class SubThread extends Thread {
    194. public Handler subhandler;
    195. public void run() {
    196. Looper.prepare();
    197. subhandler = new Handler(Looper.myLooper()) {
    198. @Override
    199. public void handleMessage(Message msg) {
    200. switch (msg.what) {
    201. case 1:
    202. //解包发起请求
    203. Map[] li= (Map[]) msg.obj;
    204. try {
    205. DAO.ControlDec((String) li[0].get("name"),(String) li[1].get("val"));
    206. Log.e(TAG, "success" );
    207. mHandler.sendEmptyMessage(1);
    208. } catch (Exception e) {
    209. e.printStackTrace();
    210. mHandler.sendEmptyMessage(0);
    211. }
    212. break;
    213. case 2:
    214. try {
    215. Object[] objects= (Object[]) msg.obj;
    216. DAO.SetLimitAndIsc(objects[0].toString(),objects[1].toString());
    217. Log.e(TAG, "success" );
    218. mHandler.sendEmptyMessage(1);
    219. } catch (Exception e) {
    220. e.printStackTrace();
    221. mHandler.sendEmptyMessage(0);
    222. }
    223. break;
    224. default:
    225. break;
    226. }
    227. }
    228. };
    229. Looper.loop();
    230. }
    231. }
    232. }

     

    这两点是最主要的,另外就是数据统计,比如画折线图对数据进行实时显示,还有对历史数据(传感器信息)进行查询等等。

     

     

     

    参考

    Android的handler消息收发处理——子线程与主线程(UI线程)间的通信_昊月光华的博客-CSDN博客_android获取主线程handler

     

    然后就是界面布局,我不追求美观,只要能用就行。

  • 相关阅读:
    新电脑到手如何验机?保姆级攻略来了
    Node.js-初识Node.js与内置模块
    1. 开篇辞和一些SQL语句基本概念
    特殊符号: && 和 | | 和 ?? 和 ?作用详解
    人脸特征标注——OpenCV
    LeetCode 2240. Number of Ways to Buy Pens and Pencils【数学,枚举;类欧几里得算法】1399
    Leetcode刷题_贪心相关_c++版
    【NLP学习记录】One-Hot编码
    8、Docker数据卷与数据卷容器
    BLE学习(2):广播包报文格式详解
  • 原文地址:https://blog.csdn.net/PHILICS7/article/details/126735068