您的位置:首页 > 其它

智能机器人自动巡检系统的设计与实现

2019-07-11 16:26 567 查看

自定义View系列教程00–推翻自己和过往,重学自定义View
自定义View系列教程01–常用工具介绍
自定义View系列教程02–onMeasure源码详尽分析
自定义View系列教程03–onLayout源码详尽分析
自定义View系列教程04–Draw源码分析及其实践
自定义View系列教程05–示例分析
自定义View系列教程06–详解View的Touch事件处理
自定义View系列教程07–详解ViewGroup分发Touch事件
自定义View系列教程08–滑动冲突的产生及其处理

探索Android软键盘的疑难杂症
深入探讨Android异步精髓Handler
详解Android主流框架不可或缺的基石
站在源码的肩膀上全解Scroller工作机制

Android多分辨率适配框架(1)— 核心基础
Android多分辨率适配框架(2)— 原理剖析
Android多分辨率适配框架(3)— 使用指南

Android程序员C语言自学完备手册
讲给Android程序员看的前端系列教程(图文版)
讲给Android程序员看的前端系列教程(视频版)

版权声明

项目概述

智能机器人可按照预设路线进行自动巡检工作,也可根据现场指令临时改变行进路线。在巡检过程中机器人通过传感器可采集:温度、湿度、光照、重力、距离、压力、红外、声音等信息并将其传送至服务器。服务器接收到数据后对其进行清洗、分析、重组并将整理后的数据发送至客户端展示。与此同时,客户端可通过服务器发送指令信息控制智能机器人的相关操作。

该系统可在两种模式下工作,现对其分别介绍。

RSC工作模式

在该模式中工作原理如下:

  • 智能机器人通过传感器采集数据(如:温度、湿度等)
  • 智能机器人将采集到的数据发送至服务器
  • 服务器将传感器数据发送至客户端
  • 客户端将控制信息(如:前进、后退等)发送至服务器
  • 服务器将控制信息转发至机器人

在该工作模式中:

  • 智能机器人与服务器之间采用TCP通信
  • 服务器与客户端之间采用UDP通信

RSA工作模式

在该模式中工作原理如下:

  • 智能机器人通过传感器采集数据(如:温度、湿度等)
  • 智能机器人将采集到的数据发送至服务器
  • 服务器将传感器数据发送至手机APP
  • 手机APP将控制信息(如:前进、后退等)发送至服务器
  • 服务器将控制信息转发至机器人

在该工作模式中:

  • 智能机器人与服务器之间采用TCP通信
  • 服务器与手机APP之间采用UDP通信

项目源码

嗯哼,再了解了项目基本情况后我们先来学习相关基础知识再进行项目开发。

socket

如上图,在七个层级关系中我们将socket归属于传输层。socket是基于应用服务与TCP/IP通信之间的一个抽象,它将TCP/IP协议里面复杂的通信逻辑进行了分装。socket是网络程序之间双向通信的最后终结点,它由地址(IP)和端口号(PORT)组成。

对于socket编程我们有两种通信协议可以选择:

  • UDP(User Data Protocol 用户数据报协议)
  • TCP(Transfer Control Protocol 传输控制协议)

UDP

概述

UDP协议是一种对等通信的实现,发送方只需要知道接收方的IP和Port,就可以直接向它发送数据,不需要事先连接。每个程序都可以作为服务器,也可作为客户端。UDP是一种无连接的传输协议,它以数据报(DatagramPacket)作为数据传输的载体。数据报的大小是受限制的,每个数据报的大小限定在64KB以内。

使用UDP协议进行数据传输时,需将要传输数据定义为数据报(DatagramPacket),在数据报中指明数据所要达到的Socket再将数据报发送出去。在接收到发送方的数据报(DatagramPacket)时,不仅可以获得数据还可获得发送方的IP和Port,这样就可以向发送方发送数据。因此,从本质上而言发送方与接收方两者是对等的。

UDP中每个数据报都是一个独立的信息,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。这种传输方式是无序的,也不能确保绝对的安全可靠,但它很简单也具有较高的效率,这与通过邮局发送邮件的情形非常相似。

但是由于UDP的特性:它不属于连接型协议,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。

实现

UDP通信的Socket使用DatagramSocket类实现,数据报使用DatagramPacket类实现。

示例

发送方

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/*
* 利用UDP协议发送数据:
* 第一步:创建发送端Socket对象
* 第二步:创建数据,并把数据打包
* 第三步:调用Socket对象的发送方法发送数据包
* 第四步:释放资源
*
* 本文作者:谷哥的小弟
* 博客地址:https://blog.csdn.net/lfdfhl
*/
public class SendMessage {

public static void main(String[] args) throws IOException {
// 创建发送端Socket对象
DatagramSocket datagramSocket = new DatagramSocket();
// 准备数据
String message = new String("大家好");
byte[] messageByte = message.getBytes();
int len = messageByte.length;
// 通信地址
InetAddress address = InetAddress.getLocalHost();
int port=10088;
// 打包数据
DatagramPacket datagramPacket = new DatagramPacket(messageByte, len, address, port);
// 发送数据
datagramSocket.send(datagramPacket);
// 关闭socket
datagramSocket.close();
}

}

接收方

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/*
* 利用UDP协议接收数据:
* 第一步:创建接收端Socket对象
* 第二步:创建一个数据包用于接收数据
* 第三步:调用Socket对象的接收方法接收数据
* 第四步:解析数据包,并显示在控制台
* 第五步:释放资源
*
* 本文作者:谷哥的小弟
* 博客地址:https://blog.csdn.net/lfdfhl
*/
public class ReceiveMessage {

public static void main(String[] args) throws IOException {
int port=10088;
DatagramSocket datagramSocket=new DatagramSocket(port);
byte [] receiveByte=new byte[1024*10];
DatagramPacket datagramPacket=new DatagramPacket(receiveByte, receiveByte.length);
datagramSocket.receive(datagramPacket);
byte[] data = datagramPacket.getData();
int length = datagramPacket.getLength();
String receivedData=new String(data,0,length);
InetAddress address = datagramPacket.getAddress();
String ip = address.getHostAddress();
System.out.println("接收到来自:"+ip+"的数据,内容为:"+receivedData);
datagramSocket.close();
}

}

TCP

概述

TCP是一种面向连接的保证可靠传输的协议。通过TCP协议传输,得到的是一个顺序的无差错的数据流。发送方和接收方的成对的两个socket之间必须建立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以进行双向数据传输并且双方都可以进行发送或接收操作。TCP通讯类似于打电话,必须双方把电话接通后才能进行通话,任何一方断线都会造成无法进行通话,须再次连接。

TCP协议用来控制两个网络设备之间的点对点通信,两端设备按作用分为客户端和服务端。服务端为客户端提供服务,通常等待客户端的请求消息,有客户端请求到达后,及时提供服务和返回响应消息;客户端向服务端主动发出请求,并接收服务的响应消息。

实现

TCP通信过程中采用Socket和ServerSocket实现。无论一个TCP通信程序的功能多么齐全、程序多么复杂,其基本原来和结构都是类似的,都包括以下四个基本步骤:

第一步:
在服务端指定一个端口号来创建ServerSocket,并使用accept方法进行侦听,这将阻塞服务器线程,等待用户请求。

第二步:
在客户端指定服务器的主机IP和端口号来创建Socket,并连接到服务端ServerSocket,现在服务端的accept方法将被唤醒,同时返回一个和客户端通信的Socket。

第三步:
在客户端和服务端分别使用Socket来获得网络通信的输入/输出流,并按照一定的通信协议对Socket进行读/写操作。

第四步:
在通信完成后,在客户端和服务端中分别关闭Socket

示例

客户端

import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

/*
* 利用TCP协议发送数据:
* 第一步:创建发送端的Socket对象
* 第二步:获取输出流,写数据
* 第三步:释放资源
*
* 本文作者:谷哥的小弟
* 博客地址:https://blog.csdn.net/lfdfhl
*/
public class Client {
public static void main(String[] args) throws Exception {
InetAddress inetAddress=InetAddress.getLocalHost();
String ip = inetAddress.getHostAddress();
int port=10088;
Socket socket=new Socket(ip,port);
OutputStream outputStream =socket.getOutputStream();
outputStream.write("您好".getBytes());
socket.close();
}
}

服务端

package com.tcp1;

import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/*
* 利用TCP协议接收数据:
* 第一步:创建接收端的Socket对象
* 第二步:监听客户端连接。返回一个对应的Socket对象
* 第三步:获取输入流,读取数据显示在控制台
* 第四步:释放资源
*
* 本文作者:谷哥的小弟
* 博客地址:https://blog.csdn.net/lfdfhl
*/
public class Server {

public static void <
4000
span class="token function">main(String[] args) throws IOException {
int port=10088;
ServerSocket serverSocket=new ServerSocket(port);
Socket socket = serverSocket.accept();
InputStream inputStream = socket.getInputStream();
byte [] b=new byte[1024];
int len = inputStream.read(b);
String message=new String(b,0,len);
String ip = socket.getInetAddress().getHostAddress();
System.out.println("收到来自"+ip+"的消息,内容为:"+message);
socket.close();
serverSocket.close();
}

}

服务端开发



服务端主要功能

  • 监控系统的整体运行状况
  • 连接机器人利用socket与其进行TCP通信
  • 使用DatagramSocket与客户端或APP进行UDP通信
  • 使用ReceiveRobotTCPMessageRunnableImpl接收来自机器人的消息并将消息转发至客户端
  • 使用ReceiveClientUDPMessageRunnableImpl接收来自客户端或APP的消息并将消息转发至机器人

服务端代码实现

HandleClientConnect如下:

import javax.swing.JTextArea;
import cn.com.frame.MonitorFrame;
import cn.com.tools.Client;
import cn.com.tools.Config;

/**
* 本文作者:谷哥的小弟
* 博客地址:http://blog.csdn.net/lfdfhl
*/
public class HandleClientConnect implements HandleMessage{
@Override
public void handle(String message) {
//获取客户端的IP和端口号
String[] stringArray = message.split(",");
String ip=stringArray[1];
String port=stringArray[2];
Client client = new Client();
client.setClientIP(ip);
client.setClientPort(Integer.parseInt(port));
//将客户端保存至ClientList
Config.getClientArrayList().add(client);
//显示消息
MonitorFrame monitorFrame = MonitorFrame.getInstance();
JTextArea textArea = monitorFrame.getTextArea();
String oldText = textArea.getText();
String newText=oldText+Config.NEWLINE+"服务端收到"+ip+":"+port+"发送的消息"+stringArray[0];
textArea.setText(newText);
}

}

SimpleFactory如下:

/**
* 本文作者:谷哥的小弟
* 博客地址:http://blog.csdn.net/lfdfhl
*/
public class SimpleFactory {
public static HandleMessage createMessageHandler(String type) {
HandleMessage handleMessage = null;
switch (type) {
case "0":
//温度湿度
handleMessage = new HandleTemperatureAndHumidity();
break;
case "9":
//机器人控制消息
handleMessage = new HandleCommand();
break;
case "A":
//客户端登录
handleMessage = new HandleClientConnect();
break;
}

return handleMessage;
}
}

ReceiveClientUDPMessageRunnableImpl如下:

/**
* 本文作者:谷哥的小弟
* 博客地址:http://blog.csdn.net/lfdfhl
*/
public class ReceiveClientUDPMessageRunnableImpl implements Runnable {
@Override
public void run() {
while(true){
byte[] buf = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buf, 1024);
try {
//接收客户端消息
Config.getDatagramSocket().receive(datagramPacket);
//处理接收到的客户端消息
byte[] data=datagramPacket.getData();
int length=datagramPacket.getLength();
String message = new String(data,0,length );
//获取客户端IP和端口号
String clientIP = datagramPacket.getAddress().getHostAddress();
int clientPort = datagramPacket.getPort();
System.out.println("服务端收到来自客户端"+clientIP+":"+clientPort+"的消息:"+message);
//组拼形式:911,127.0.0.1,9999
message=message+","+clientIP+","+clientPort;
//机器人控制信息均以9开头,登录信息以A开头
String type=message.substring(0,1);
HandleMessage handleMessage = SimpleFactory.createMessageHandler(type);
handleMessage.handle(message);
} catch (IOException e) {
e.printStackTrace();
}
}

}

}

ReceiveRobotTCPMessageRunnableImpl如下:

import java.io.IOException;
import java.io.InputStream;
import cn.com.handleMessage.HandleMessage;
import cn.com.handleMessage.SimpleFactory;
import cn.com.tools.Config;
/**
* 本文作者:谷哥的小弟
* 博客地址:http://blog.csdn.net/lfdfhl
*/
public class ReceiveRobotTCPMessageRunnableImpl implements Runnable {
@Override
public void run() {
while(true){
try {
InputStream inputStream = Config.getSocket().getInputStream();
byte[] buf = new byte[1024];
int len = inputStream.read(buf);
String message = new String(buf,0,len);
//System.out.println("服务端接收到来自机器人的消息:"+message);
HandleMessage handleMessage = SimpleFactory.createMessageHandler(message.substring(0,1));
handleMessage.handle(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

客户端开发


客户端主要功能

  • 与服务端建立连接
  • 接收并显示服务端发送的智能机器人所采集到的数据
  • 将对智能机器人的控制信息发送至服务器

客户端代码实现

ReceiveMessageRunnableImpl如下:

package cn.com.receiveMessage;

import java.io.IOException;
import java.net.DatagramPacket;
import javax.swing.JTextField;
import cn.com.frame.CommandFrame;
import cn.com.tools.Config;
/**
* 本文作者:谷哥的小弟
* 博客地址:http://blog.csdn.net/lfdfhl
*/
public class ReceiveMessageRunnableImpl implements Runnable {
@Override
public void run() {
while(true){
try {
byte[] buf = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buf, 1024);
//接收服务端的温湿度数据
Config.getDatagramSocket().receive(datagramPacket);
//处理温湿度数据
byte[] data=datagramPacket.getData();
int length=datagramPacket.getLength();
String message = new String(data, 0,length);
//解析消息,例如:062000025001
String humidity = message.substring(1, 3);
String temperature = message.substring(7,9);
//显示消息
CommandFrame commandFrame = CommandFrame.getInstance();
JTextField temperatureTextField = commandFrame.getTemperatureTextField();
JTextField humidityTextField = commandFrame.getHumidityTextField();
temperatureTextField.setText(temperature);
humidityTextField.setText(humidity)<
4000
/span>;
} catch (IOException e) {
e.printStackTrace();
}
}
}

}

Config如下:

package cn.com.tools;

import java.net.DatagramSocket;
/**
* 本文作者:谷哥的小弟
* 博客地址:http://blog.csdn.net/lfdfhl
*/
public class Config {
private static String serverIP;
private static int serverPort;
private static int clientPort;
private static DatagramSocket datagramSocket;

public static String getServerIP() {
return serverIP;
}

public static void setServerIP(String serverIP) {
Config.serverIP = serverIP;
}

public static int getServerPort() {
return serverPort;
}

public static void setServerPort(int serverPort) {
Config.serverPort = serverPort;
}

public static int getClientPort() {
return clientPort;
}

public static void setClientPort(int clientPort) {
Config.clientPort = clientPort;
}

public static DatagramSocket getDatagramSocket() {
return datagramSocket;
}

public static void setDatagramSocket(DatagramSocket datagramSocket) {
Config.datagramSocket = datagramSocket;
}

}

APP开发

APP主要功能

  • 与服务端建立连接
  • 接收并显示服务端发送的智能机器人所采集到的数据
  • 将对智能机器人的控制信息发送至服务器

APP代码实现

ConnectActivity如下:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
/**
* 本文作者:谷哥的小弟
* 博客地址:http://blog.csdn.net/lfdfhl
*/
public class ConnectActivity extends Activity {
private EditText mServerIPEditText;
private EditText mServerPortEditText;
private EditText mLocalPortEditText;
private Button mConnectButton;
private ClickListenerImpl mClickListenerImpl;
private String serverIP;
private int serverPort;
private int localPort;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_connect);
mServerIPEditText = (EditText) findViewById(R.id.serverIPEditText);
mServerPortEditText = (EditText) findViewById(R.id.serverPortEditText);
mLocalPortEditText = (EditText) findViewById(R.id.localPortEditText);
mConnectButton = (Button) findViewById(R.id.connectButton);
mClickListenerImpl = new ClickListenerImpl();
mConnectButton.setOnClickListener(mClickListenerImpl);
}

private class ClickListenerImpl implements View.OnClickListener {
@Override
public void onClick(View v) {
serverIP = mServerIPEditText.getText().toString();
serverPort = Integer.parseInt(mServerPortEditText.getText().toString());
localPort = Integer.parseInt(mLocalPortEditText.getText().toString());

Config.setServerIP(serverIP);
Config.setServerPort(serverPort);
Config.setLocalPort(localPort);

ConnectRunnable connectRunnable=new ConnectRunnable();<
8000
/span>
Thread connectThread = new Thread(connectRunnable);
connectThread.start();

Intent intent = new Intent();
intent.setClass(ConnectActivity.this, ControlActivity.class);
startActivity(intent);
}

}

private class ConnectRunnable implements Runnable{
@Override
public void run() {
try {
DatagramSocket datagramSocket = new DatagramSocket(localPort);
Config.setDatagramSocket(datagramSocket);
// 向服务端发送"A0"
byte[] buff = "A0".getBytes();
int length = buff.length;
InetAddress serverAddress = InetAddress.getByName(serverIP);
DatagramPacket datagramPacket = new DatagramPacket(buff, length, serverAddress, serverPort);
datagramSocket.send(datagramPacket);
} catch (SocketException e) {
e.printStackTrace();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

}

}

ControlActivity如下:

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
/**
* 本文作者:谷哥的小弟
* 博客地址:http://blog.csdn.net/lfdfhl
*/
public class ControlActivity extends Activity {
private TextView temperatureTextView;
private TextView humidityTextView;
private ClickListenerImpl mClickListenerImpl;
private ReceiveRunnable mReceiveRunnable;
private SendRunnable mSendRunnable;
private Button mForwardButton;
private Button mStopButton;
private Button mBackButton;
private Button mRightButton;
private Button mLeftButton;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_control);
temperatureTextView = (TextView) findViewById(R.id.temperatureValueTextView);
humidityTextView = (TextView) findViewById(R.id.humidityValueTextView);

mReceiveRunnable= new ReceiveRunnable();
Thread receiveThread = new Thread(mReceiveRunnable);
receiveThread.start();

mClickListenerImpl = new ClickListenerImpl();
mForwardButton = (Button) findViewById(R.id.forwardButton);
mForwardButton.setOnClickListener(mClickListenerImpl);
mStopButton = (Button) findViewById(R.id.stopButton);
mStopButton.setOnClickListener(mClickListenerImpl);
mBackButton = (Button) findViewById(R.id.backButton);
mBackButton.setOnClickListener(mClickListenerImpl);
mRightButton = (Button) findViewById(R.id.rightButton);
mRightButton.setOnClickListener(mClickListenerImpl);
mLeftButton = (Button) findViewById(R.id.leftButton);
mLeftButton.setOnClickListener(mClickListenerImpl);
}

private class ClickListenerImpl implements OnClickListener {

@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.forwardButton:
mSendRunnable = new SendRunnable("911");
break;
case R.id.backButton:
mSendRunnable = new SendRunnable("921");
break;
case R.id.leftButton:
mSendRunnable = new SendRunnable("941");
break;
case R.id.rightButton:
mSendRunnable = new SendRunnable("931");
break;
case R.id.stopButton:
mSendRunnable = new SendRunnable("981");
break;
default:
break;
}
Thread sendThread = new Thread(mSendRunnable);
sendThread.start();
}

}

//向服务端发送控制消息
private class SendRunnable implements Runnable {
private String controlInfo;

private SendRunnable(String controlInfo) {
this.controlInfo = controlInfo;
}

@Override
public void run() {
byte[] buff = controlInfo.getBytes();
try {
int length = buff.length;
String serverIP = Config.getServerIP();
InetAddress serverInetAddress = InetAddress.getByName(serverIP);
int serverPort = Config.getServerPort();
DatagramPacket datagramPacket = new DatagramPacket(buff,length, serverInetAddress, serverPort);
Config.getDatagramSocket().send(datagramPacket);
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}

}

//接收服务端发送的数据
private class ReceiveRunnable implements Runnable {
@Override
public void run() {
while (true) {
while (true) {
byte[] buff = new byte[1024];
DatagramPacket datagramPacket = new DatagramPacket(buff,1024);
try {
Config.getDatagramSocket().receive(datagramPacket);
int length = datagramPacket.getLength();
if (length > 0) {
String message = new String(buff, 0,length);
String type = message.substring(0, 1);
if (type.equals("0")) {
final String humidity = message.substring(1, 3);
final String temperature = message.substring(7, 9);

runOnUiThread(new Runnable() {
@Override
public void run() {
temperatureTextView.setText(temperature);
humidityTextView.setText(humidity);
}
});
}
}

} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}

总结

智能机器人自动巡检系统核心技术为Socket通信。研发过程中请注意以下几点:

  • 通信协议的制定与研发。系统中软件与硬件通过预先制定的协议进行通信,所以通信协议务必准确、严谨并尽量简洁且可扩展
  • 多线程在Socket通信中的应用。例如,在App中开启两个线程分别用于发送消息和接受消息,各司其职
  • 为方便研发可考虑利用机器人模拟器代替实物;待主要工功能完成后再使用实物进行联调
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐