前言:
本文相关:西安电子科技大学通信工程学院大二项目设计
内容:安卓上位机软件开发、使用MQTT通信协议进行上位机与wifi模块之间的无线通信、wifi模块与下位机f411实现串口通信。
上位机部分:Android Studio、Arduino、友善之臂smart4418、esp8266无线通信模块
使用无线通信可以避免使用官方提供的接口,提高代码的复用性,毕竟不同的开发板厂商提供的接口也不同。同时,提高作品的便携程度。
安卓软件开发
控件及布局
这里会介绍需要用到的控件Button、TextView、EditView(已button为例)和两种布局:线性布局、约束布局
Button
//声明变量
public Button btn_getcolor;
//绑定.xml中的id
btn_getcolor=findViewById(R.id.button_get_TCS_data);
//设置点击事件
btn_getcolor.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
publishmessageplus(mqtt_pub_topic, "THREE");
if(flag_c==0){
flag_c=1;
}else if (flag_c==1){
flag_c=0;
}
}
});
点击一次按钮,上位机会向wifi模块发送字符串THREE,并且改变标志位,以方便消息回传时的接收操作
LinearLayout 又称线性布局。该布局可以使放入其中的组件以水平方式或者垂直方式整齐排列,通过 android:orientation 属性指定具体的排列方式,通过 weight 属性设置每个组件在布局中所占的比重。该布局是安卓开发中最常用的布局,但是嵌套较多,代码书写较为麻烦。
我更喜欢约束布局,ConstraintLayout(约束布局)的出现是为了在Android应用布局中保持扁平的层次结构,减少布局的嵌套,为应用创建响应快速而灵敏的界面。Android Studio可以自由拖动控件,并在右侧的attributes中调节组件的位置、大小。控件需要先与四个边都进行约束,如果不这么做,会出现运行后偏在一侧的情况,当然与其他控件约束也是可以的。
声音提醒
// 声音提醒
public void showSound(int raw) {
MediaPlayer mediaPlayer = null;
mediaPlayer = MediaPlayer.create(getApplicationContext(), raw);
mediaPlayer.setVolume(1, 1);
mediaPlayer.start();
}
这里使用的是安卓原生框架的MediaPlayer类,在res文件下创建raw文件,将录音文件更改为.wmv格式放在raw里,用create方法创建实例,调节音量大小。这些代码封装在showSound方法里,用时直接调用即可
无线通信
MQTT通信协议
MQTT是一种基于TCP/IP协议的轻量级通信协议,在物联网、小型设备、移动应用等方面有较广泛的应用。该协议基于发布pub和订阅sub模式,连接同一后台服务器的两个对象,订阅他人后可以接收到别人发布的信息,类似于订阅报刊杂志,这个消息的传播可以是单向的也可以是双向。
MQTT传输的消息分为:主题(Topic)和负载(payload)两部分,Topic可以理解为消息的类型,订阅者订阅(Subscribe)后,就会收到该主题的消息内容(payload)这在Adruino代码中会体现出来
安卓部分
首先在libs中导入mqtt包 :org.eclipse.paho.client.mqttv3-1.2.0.jar
点击add as library添加到库中,添加后可以从build.gradle中看到
implementation files('libs\\org.eclipse.paho.client.mqttv3-1.2.0.jar')
然后就可以调用接口啦!
// mqtt初始化
public void Mqtt_init()
{
try {
//host为主机名,test为clientid即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientid的保存形式,默认为以内存保存
client = new MqttClient(host, mqtt_id,
new MemoryPersistence());
//MQTT的连接设置
options = new MqttConnectOptions();
//设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
options.setCleanSession(false);
//设置连接的用户名
options.setUserName(userName);
//设置连接的密码
options.setPassword(passWord.toCharArray());
// 设置超时时间 单位为秒
options.setConnectionTimeout(10);
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
options.setKeepAliveInterval(20);
//设置回调
client.setCallback(new MqttCallback() {
@Override
public void connectionLost(Throwable cause) {
//连接丢失后,一般在这里面进行重连
System.out.println("connectionLost----------");
/artReconnect();
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
//publish后会执行到这里
System.out.println("deliveryComplete---------"
+ token.isComplete());
}
@Override
public void messageArrived(String topicName, MqttMessage message)
throws Exception {
//subscribe后得到的消息会执行到这里面
System.out.println("messageArrived----------");
Message msg = new Message();
msg.what = 3; //收到消息标志位
msg.obj = topicName + "---" + message.toString();
handler.sendMessage(msg); // hander 回传
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
public void Mqtt_connect() {
new Thread(new Runnable() {
@Override
public void run() {
try {
if(!(client.isConnected()) ) //如果还未连接
{
client.connect(options);
Message msg = new Message();
msg.what = 31;
handler.sendMessage(msg);
}
} catch (Exception e) {
e.printStackTrace();
Message msg = new Message();
msg.what = 30;
handler.sendMessage(msg);
}
}
}).start();
}
public void startReconnect() {
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
if (!client.isConnected()) {
Mqtt_connect();
}
}
}, 0 * 1000, 10 * 1000, TimeUnit.MILLISECONDS);
}
这里涉及到多线程,因为安卓中必须遵循单线程模式,所有耗时操作必须在子线程中进行,否则会导致app闪退崩溃。通过msg.what定义标志位,在handler里进行分类操作
消息回传处理
//
handler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 1: //开机校验更新回传
break;
case 2: // 反馈回传
break;
case 3: //MQTT 收到消息回传 UTF8Buffer msg=new UTF8Buffer(object.toString());
//String C_val = msg.obj.toString().substring(msg.obj.toString).indexOf("color:") + 6,msg.obj.toString().indexOf("}"));
//String D_val = msg.obj.toString().substring(msg.obj.toString().indexOf("distance:") + 9,msg.obj.toString().indexOf("]"));
String C_val = msg.obj.toString().substring(12,20);
String D_val = msg.obj.toString().substring(20,31);
//String val = msg.obj.toString();
if(flag_c==1){
text_color.setText(C_val);
}else {text_color.setText("空");}
if(flag_d==1){
text_distance.setText(D_val);
}else{text_distance.setText("空");}
break;
case 30: //连接失败
Toast.makeText(MaintenanceActivity.this, "连接失败", Toast.LENGTH_SHORT).show();
break;
case 31: //连接成功
//Toast.makeText(MaintenanceActivity.this, "连接成功", Toast.LENGTH_SHORT).show();
try {
client.subscribe(mqtt_sub_topic, 1);
} catch (MqttException e) {
e.printStackTrace();
}
break;
default:
break;
}
}
};
该代码会爆黄,可能是内存泄漏导致,不过并不影响,可以通过加@SuppressLint(“HandlerLeak”)消除
发送消息,相当于重写了client.publish方法,设置message的payload属性为字节数组,方便Arduino操作
// 发送消息的方法
public void publishmessageplus(String topic,String message2)
{
if (client == null || !client.isConnected()) {
return;
}
MqttMessage message = new MqttMessage();
message.setPayload(message2.getBytes());
try {
client.publish(topic,message);
} catch (MqttException e) {
e.printStackTrace();
}
}
需要注意的是:在其他界面不能声明通过类然后调用该界面的方法,需要重新定义handler和方法
Arduino部分
arduino不显式地使用main作为入口函数,基本的函数时setup()和loop(),在setup中执行初始化,再进入loop中循环,除此以外可以自定义函数或者使用库函数,本例中接收消息并通过串口发送给411就是在PubSubClient.h库中的回调函数callback中执行的。
// 接收信息
void callback(char* topic, byte* payload, unsigned int length) {
String msg="";
String LED_set = "";
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
msg+= (char)payload[i];
}
Serial.println(msg);
if(msg=="ONE"){
Serial.print("ONE");
}else if(msg=="TWO"){
Serial.print("TWO");//发送数据给411
}else if(msg=="THREE"){
//Serial.print(sendJson("THREE"));
Serial.print("THREE");
}else if(msg=="FOUR"){
Serial.print("FOUR");
}else if(msg=="FIVE"){
Serial.print("FIVE");
}else if(msg=="SIX"){
Serial.print("SIX");
}else if(msg=="SEVEN"){
Serial.print("SEVEN");
}
}
将收到的信息存储到msg中,判断后执行if中语句
回传信息
//
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop();
if(Serial.available())
{
comdata = "";//缓存清零
while (Serial.available() > 0)//循环串口是否有数据
{
comdata += char(Serial.read());//叠加数据到comdata
delay(2);//延时等待响应
}
if(comdata!=""){
mark=1;
//Serial.println(comdata);
}
else{mark=0;}
}
if(mark==1)
{
Serial.println(comdata);
String json = ""+comdata;
client.publish(Mqtt_pub_topic,json.c_str());
digitalWrite(2,HIGH);
delay(500);
digitalWrite(2,LOW);
delay(500);
}
}
接收到411传来的信息并通过publish发送给上位机,使用板载小灯的亮灭来提醒8266是否接收到该信息。
到这里就结束了,理解比较浅陋,如果文章中有什么不准确或者需要改进的地方,还请大佬不吝赐教。