这个程序是本科课程设计写的程序,因为最近在复习java和C#,故把以前的东西拿出来看看,顺便写个博客。
此程序的功能描述:通过手机端可以直接观察到温室的各项实时参数,并且可以通过曲线图直观地分析该温室最近一段时间的环境变化。用户可通过设备管理界面看到各个监控节点的工作状态,可远程控制这些监控节点的工作状态,指令会先发送至上位机上,再由上位机通过串口发至底层终端。手机端通过Android自带的SQLite功能对数据进行实时保存,方便相关技术人员进行历史数据查询。移动终端主要包括三个界面:显示界面、数据库界面、状态控制界面。
主要采用的模块有:
1.数据解析模块
数据解析模块的功能主要是接收来自上位机的数据,对数据按照格式进行解析,根据其设备号进行分类存储。首先,将读取的数据存入缓冲区,然后在缓冲区内查找数据的帧头和帧尾,如果找到了帧头和帧尾,就对帧头和帧尾之间的数据进行拆分,根据通信协议将字符数组的各位存储到对应字符串,按照数据帧格式将各个传感器的信息和ID号提取出来并保存。
该模块包含一个主要函数netDataReceived(char[] temp),该函数主要执行如下操作:1.调用String(temp, a, b)函数将对应数组位数转换成字符串形式赋值到对应TextView控件;2.调用Integer.parseInt(a)函数将字符串转换为整型,用于图表显示和数据库存储;3.执行 dbWriter.insert("wenshi", null, cv)语句,向数据表中增加最新数据。
2.网络通信模块设计
网络通信模块基于TCP协议,通过Socket s = new Socket(ip地址,端口号),创建socket对象,当连接上上位机后,上位机会调用accept()方法返回连接的套接字,此时线程池创建两个线程,分别用于接收和发送操作。之后调用getInputStream()方法获取上位机TCP套接字的输入流,存入字节数组交由数据解析模块进行处理。在设备状态显示界面,用户通过Switch、TextView控件进行指令下达,此时,套接字对象会调用输出流的write()方法向上位机发送指令。关键代码如下:
socket = new Socket(pcIp, pcport);//创建套接字对象
pcTcpOutputStream = socket.getOutputStream();//获取PC输出流
is = socket.getInputStream();//获取输入流
isr = new InputStreamReader(is);//读取输入流
br = new BufferedReader(isr);//创建缓存区
char[] temp = response.toCharArray();//保存于字符数组
new Thread(new Runnable() {//创建一个线程用来接收数据
public void run() {
while (true) {
try {
response = br.readLine();//读一行
char[] temp = response.toCharArray();
netDataReceived(temp);//交由数据处理函数
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
3.数据显示模块设计
该模块包含两个主要部分:实时数据显示部分与折线图显示部分。
本设计所涉及到的控件有TextView、LineChart。接收线程每隔2000ms将服务器发送过来的数据存储至字符数组中,并调用netDataReceived(char[] temp)函数进行数据的解析、拆分,然后分别进行处理。
对于实时数据显示部分,所有相关控件方法均放在线程中进行调用,TextView类对象调用setText(str)函数显示已经拆分好的字符串。
对于图表显示部分,则需先对LineChart类对象进行初始化和相关参数的配置,例如X轴、Y轴、限制线等参数。其次调用addEntry(Chart, "表名", color, int)函数将对应的整型数据添加至指定图表中。关键代码如下:
LineData data = chart.getData();//图表获取int数据
if (data != null) {//数据非空
ILineDataSet set = data.getDataSetByIndex(0);
if (set == null) {
set = createLineDataSet(name, color);
data.addDataSet(set);//执行打点操作
}
data.addEntry(new Entry(set.getEntryCount(), number), 0);//添加到图表中
data.notifyDataChanged();
chart.notifyDataSetChanged();
chart.setVisibleXRangeMaximum(30);//设置坐标轴范围
chart.moveViewToX(data.getEntryCount());
}
4.数据库存储模块设计
SOLite支持Windows、Linux等主流操作系统,所需内存很少,并且能够与很多程序语言相结合。由于移动设备平台的内存和外存都受到限制,因此SQLite不能执行非常复杂的数据库相关功能。根据SQLite具有的轻量级、简单易用、配置简单等特点,综上考虑,SQLite嵌入式数据库为本次设计最佳选择。
由于一个种植园具有多个温室,一个温室可能具有几十个监控节点,每一次采集都会有巨量的数据被存入数据库,这对SQLite数据库和手机设备会造成很大的负荷,故本设计中SQLite数据库只保存最近半小时的数据,对其他时间段数据进行删除。
根据Android提供的一些API类,重写SQLiteOpenHelper类的构造方法创建数据库,然后调用getWritableDatabase()方法获取对数据库的读写权限。
关键代码如下:
dbOpenHelper = new MyDBOpenHelper(getApplicationContext(), "SC_Database.db", null, 1);//调用数据库类,选择SC_Database.db数据表
dbReader = dbOpenHelper.getReadableDatabase();//读操作
dbWriter = dbOpenHelper.getWritableDatabase();//写操作
listViewAdapter = new SimpleCursorAdapter(getApplicationContext(), R.layout.item_list, result,new String[]{"device", "trwendu", "trshidu","hwendu","hshidu","guangq","dianliu",
"shijian"},new int[]{R.id.device, R.id.trwendu, R.id.trshidu,R.id.hwendu,R.id.hshidu,
R.id.guangq,R.id.dianliu,R.id.shijian},CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
listView.setAdapter(listViewAdapter);//初始化ListView控件
效果如下:

移动终端接收上位机转发的数据并实时显示出来,如左图所示,可以看到界面显示土壤温湿度、环境温湿度、光强、电流的数据,并在下方的图表区域每个2s进行一次打点,在图表上向右滑动,还可以看到历史数据。
移动终端将接收的数据存储至内部数据库中,并通过一个ListView显示出当前数据库的信息,触摸界面上下滑动可查看更久之前的数据,效果如右图所示,设备代表温室号,表明目前手机端采集的是温室1的数据,在最后的时间那一栏为采集时间,表示这条数据是什么时候采集的,方便管理员进行数据分析。


串口助手充当检测/控制终端,测试手机能否将指令发送至串口助手,根据已经配置好的虚拟串口,我在手机端的设备状态界面点击想要打开或关闭的监控节点,设置温度阈值,点击下方的发送按钮,系统便会将各个开关按钮的点击事件数据集合成一串字符串,按照帧格式要求发送至上位机,再由上位机通过串口发送至检测/控制终端。如第一张图所示,在手机界面上显示了各个设备的状态,均为关闭状态,此时我将温度下限设为11,温度上限设为35,打开顶开窗、湿帘风机、环流风机按钮,其他监控节点为关闭状态,点击“指令下达”按钮,可以看到如第二张图所示的上位机界面,上位机消息栏提示“手机发送指令”信息,并在阈值设定区域更新对应数据,此时串口助手接收栏那里显示上位机转发的数据,如第三张图所示,串口助手显示“#11350010101001#”字样,与数据帧格式进行比对为正确数据格式。
全部代码:
数据显示界面
public class WifiActivity extends AppCompatActivity {
final private int UDP_RECEIVE_BUFFER_LENGTH = 1024;
final private int SENSOR_FRAME_LENGTH = 15;
final private String TDS_LINE_COLOR = "#0000FF";
final private String TEMP_LINE_COLOR = "#00FF00";
final private String HUM_LINE_COLOR = "#FF0000";
final private String LIGHT_LINE_COLOR = "#00FFFF";
private SQLiteDatabase dbWriter;
private MyDBOpenHelper dbOpenHelper;
private int[] frameBuffer = new int[SENSOR_FRAME_LENGTH];
private int frameByteCount = 0;
private Socket socket;
private LineChart turangwenduChart;
private LineChart turangshiduChart;
private LineChart huanjingshiduChart;
private LineChart guangqiangChart;
private LineChart huanjingwenduChart;
private LineChart dianliuChart;
private Handler mMainHandler;
private TextView turangwendu;
private TextView turangshidu;
private TextView huanjingshidu;
private TextView guangqiang;
private TextView huanjingwendu;
private TextView dianliu;
String tuwen,tushi,huanwen,huanshi,guangq,dianl,shijian;
String waizheyang_state="0",dingkaichuang_state="0",
cekaichuang_state="0",shilian_state="0",zhouliufengji_state="0";
String neizheyang_state="0";
String huanliufengji_state="0",wuhua_state,guangai_state="0";
String shebei="1";
int year,month,day,hour,minute,second;
int tuwen1,tushi1,huanwen1,huanshi1,guangq1,dianl1;
String response="#00000000000000";
private String pcIp;
private String pcPort;
private int pcport;
private Handler handler;
InputStream is;
InputStreamReader isr;
BufferedReader br;
private DatagramSocket udpSocket;
private ServerSocket tcpServerSocket;
private Socket mcuTcpSocket;
private InputStream mcuTcpInputStream;
private Socket pcTcpSocket;
private OutputStream pcTcpOutputStream;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initUI();
initChart(turangwenduChart);
initChart(turangshiduChart);
initChart(huanjingshiduChart);
initChart(guangqiangChart);
initChart(huanjingwenduChart);
initChart(dianliuChart);
dbOpenHelper = new MyDBOpenHelper(getApplicationContext(), "SC_Database.db", null, 1);
dbWriter = dbOpenHelper.getWritableDatabase();
Intent intent = getIntent();
Bundle bundle =intent.getExtras();
pcIp = bundle.getString("PC_IP");
pcPort=bundle.getString("PC_PORT");
pcport = Integer.parseInt(pcPort);
tcpStart();
handler = new Handler() {
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case 0:
turangwendu.setText(tuwen);
turangshidu.setText(tushi);
huanjingshidu.setText(huanshi);
guangqiang.setText(guangq);
huanjingwendu.setText(huanwen);
dianliu.setText(dianl);
Log.d("Handler", "传感器信息已更新");
break;
case 1:
Toast.makeText(WifiActivity.this, (String) (msg.obj), Toast.LENGTH_SHORT).show();
break;
}
}
};
mMainHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case 0: {
turangwendu.setText(tuwen);
turangshidu.setText(tushi);
huanjingshidu.setText(huanshi);
guangqiang.setText(guangq);
huanjingwendu.setText(huanwen);
dianliu.setText(dianl);
break;
}
}
}
};
}
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(1, 1, 1, "设备状态");
menu.add(1, 2, 1, "返回");
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case 1:
Intent intent = new Intent();
intent.setClass(WifiActivity.this, ControlActivity.class);
Bundle bundle = new Bundle();
bundle.putString("waizheyang_state", waizheyang_state);
bundle.putString("neizheyang_state", neizheyang_state);
bundle.putString("dingkaichuang_state", dingkaichuang_state);
bundle.putString("cekaichuang_state", cekaichuang_state);
bundle.putString("shilian_state", shilian_state);
bundle.putString("zhouliufengji_state", zhouliufengji_state);
bundle.putString("huanliufengji_state", huanliufengji_state);
bundle.putString("wuhua_state", wuhua_state);
bundle.putString("guangai_state", guangai_state);
intent.putExtras(bundle);
startActivity(intent);
break;
case 2:
finish();
break;
}
return super.onOptionsItemSelected(item);
}
private void initUI() {
setContentView(R.layout.activity_wifi);
setTitle("温室监控系统");
turangwendu = findViewById(R.id.turangwendu);
turangshidu = findViewById(R.id.turangshidu);
huanjingshidu = findViewById(R.id.huanjingshidu);
guangqiang = findViewById(R.id.guangqiang);
huanjingwendu = findViewById(R.id.huanjingwendu);
dianliu = findViewById(R.id.dianliu);
turangwenduChart = findViewById(R.id.turangwenduChart);
turangshiduChart = findViewById(R.id.turangshiduChart);
huanjingshiduChart = findViewById(R.id.huanjingshiduChart);
guangqiangChart = findViewById(R.id.guangqiangChart);
huanjingwenduChart = findViewById(R.id.huanjingwenduChart);
dianliuChart = findViewById(R.id.dianliuChart);
}
private void initChart(LineChart chart) {
chart.getDescription().setEnabled(false);
chart.setTouchEnabled(true);
chart.setDragEnabled(true);
chart.setScaleEnabled(true);
chart.setDrawGridBackground(false);
chart.setPinchZoom(true);
chart.setDrawBorders(true);
chart.setBorderColor(0xA2A2A2);
LineData data = new LineData();
chart.setData(data);
Legend l = chart.getLegend();
l.setForm(Legend.LegendForm.LINE);
l.setEnabled(true);
XAxis xl = chart.getXAxis();
xl.setPosition(XAxis.XAxisPosition.BOTTOM);
xl.setDrawLabels(false);
xl.setGranularity(1f);
xl.setTextColor(Color.WHITE);
xl.setDrawGridLines(true);
xl.enableGridDashedLine(10f, 10f, 0f);
xl.setAvoidFirstLastClipping(true);
xl.setEnabled(true);
YAxis leftAxis = chart.getAxisLeft();
leftAxis.setGranularity(1f);
leftAxis.setAxisMaximum(100f);
leftAxis.setAxisMinimum(0f);
leftAxis.resetAxisMinimum();
leftAxis.resetAxisMaximum();
leftAxis.setDrawGridLines(true);
leftAxis.enableGridDashedLine(10f, 10f, 0f);
YAxis rightAxis = chart.getAxisRight();
rightAxis.setEnabled(false);
}
private void addEntry(LineChart chart, String name, String color, float number) {
LineData data = chart.getData();
if (data != null) {
ILineDataSet set = data.getDataSetByIndex(0);
if (set == null) {
set = createLineDataSet(name, color);
data.addDataSet(set);
}
data.addEntry(new Entry(set.getEntryCount(), number), 0);
data.notifyDataChanged();
chart.notifyDataSetChanged();
chart.setVisibleXRangeMaximum(30);
chart.moveViewToX(data.getEntryCount());
}
}
private LineDataSet createLineDataSet(String name, String color) {
LineDataSet set = new LineDataSet(null, name);
set.setAxisDependency(YAxis.AxisDependency.LEFT);
set.setColor(Color.parseColor(color));
set.setCircleColor(Color.WHITE);
set.setDrawCircles(false);
set.setFillAlpha(65);
set.setFillColor(ColorTemplate.getHoloBlue());
set.setHighLightColor(Color.rgb(244, 117, 117));
set.setValueTextColor(Color.WHITE);
set.setValueTextSize(9f);
set.setDrawValues(false);
return set;
}
private void netDataReceived(char[] temp) {
tuwen = new String(temp, 1, 2);
tushi = new String(temp, 3, 2);
huanwen = new String(temp, 5, 2);
huanshi = new String(temp, 7, 2);
guangq = new String(temp, 9, 4);
dianl = new String(temp, 13, 2);
waizheyang_state = new String(temp, 15, 1);
neizheyang_state = new String(temp, 16, 1);
dingkaichuang_state = new String(temp, 17, 1);
cekaichuang_state = new String(temp, 18, 1);
shilian_state = new String(temp, 19, 1);
zhouliufengji_state = new String(temp, 20, 1);
huanliufengji_state = new String(temp, 21, 1);
wuhua_state = new String(temp, 22, 1);
guangai_state = new String(temp, 23, 1);
turangwendu.setText(tuwen);
turangshidu.setText(tushi);
huanjingshidu.setText(huanshi);
guangqiang.setText(guangq);
huanjingwendu.setText(huanwen);
dianliu.setText(dianl);
tuwen1=Integer.parseInt(tuwen);
tushi1=Integer.parseInt(tushi);
huanshi1=Integer.parseInt(huanshi);
guangq1=Integer.parseInt(guangq);
huanwen1=Integer.parseInt(huanwen);
dianl1=Integer.parseInt(dianl);
addEntry(turangwenduChart, "土壤温度", TDS_LINE_COLOR, tuwen1);
addEntry(turangshiduChart, "土壤湿度", TEMP_LINE_COLOR, tushi1);
addEntry(huanjingshiduChart, "环境湿度", HUM_LINE_COLOR, huanshi1);
addEntry(guangqiangChart, "光强", LIGHT_LINE_COLOR, guangq1);
addEntry(huanjingwenduChart, "环境温度", TDS_LINE_COLOR, huanwen1);
addEntry(dianliuChart, "电流", HUM_LINE_COLOR, dianl1);
SQLiteDatabase dbWriter = dbOpenHelper.getReadableDatabase();
ContentValues cv = new ContentValues();
cv.put("shebeihao", shebei);
cv.put("trwendu", tuwen1);
cv.put("trshidu", tushi1);
cv.put("hwendu", huanwen1);
cv.put("hshidu", huanshi1);
cv.put("guangq", guangq1);
cv.put("dianliu", dianl1);
Calendar calendaryzc = Calendar.getInstance();
year = calendaryzc.get(Calendar.YEAR);
month = calendaryzc.get(Calendar.MONTH)+1;
day = calendaryzc.get(Calendar.DAY_OF_MONTH);
hour = calendaryzc.get(Calendar.HOUR_OF_DAY);
minute = calendaryzc.get(Calendar.MINUTE);
second = calendaryzc.get(Calendar.SECOND);
shijian=year+"/"+month+"/"+day+" "+hour+":"+minute+":"+second;
cv.put("shijian", shijian);
dbWriter.insert("wenshi", null, cv);
}
private void sendHandlerMessage(int what, Object msg) {
Message message = new Message();
message.what = what;
message.obj = msg;
handler.sendMessage(message);
}
private void tcpStart() {
new Thread(new Runnable() {
public void run() {
try {
socket = new Socket(pcIp, pcport);
sendHandlerMessage(1, "连接成功!");
pcTcpOutputStream = socket.getOutputStream();
isyzc = socket.getInputStream();
isrz = new InputStreamReader(isyzc);
brx = new BuffereedReader(isrz);
new Thread(new Runnable() {
public void run() {
while (true) {
try {
response = br.readLine();
char[] temp = response.toCharArray();
netDataReceived(temp);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}).start();
Message msg = Message.obtain();
msg.what = 0;
mMainHandler.sendMessage(msg);
} catch (Exception e) {
sendHandlerMessage(1, "连接失败!");
e.printStackTrace();
}
}
}).start();
}
protected void onDestroy() {
super.onDestroy();
dbWriter.close();
}
}
数据库存储部分
public class MyDBOpenHelper extends SQLiteOpenHelper {
public MyDBOpenHelper(Context context, String name,
SQLiteDatabase.CursorFactory factory, int version) {
super(context,name ,factory, version);
}
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE wenshi(" +
"device TEXT PRIMARY KEY AUTOINCREMENT," +
"trwendu INTEGER," +
"trshidu INTEGER," +
"hwendu INTEGER," +
"hshidu INTEGER," +
"guangq INTEGER," +
"dianliu INTEGER," +
"shijian TEXT);");
}
public void onUpgrade(SQLiteDatabase _db, int oldVersion, int newVersion) {
_db.execSQL("DROP TABLE IF EXISTS wenshishujuku");
onCreate(_db);
}
}
public class displaydata extends AppCompatActivity {
private SQLiteDatabase dbWriter,dbReader;
private MyDBOpenHelper dbOpenHelper;
private SimpleCursorAdapter listViewAdapter;
private ListView listView;
private String currentID="1";
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_displaydata);
dbOpenHelper = new MyDBOpenHelper(getApplicationContext(), "SC_Database.db", null, 1);
dbReader = dbOpenHelper.getReadableDatabase();
dbWriter = dbOpenHelper.getWritableDatabase();
showAll();
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView shebeihao =view.findViewById(R.id.shebeihao);
TextView trwendu = view.findViewById(R.id.trwendu);
TextView trshidu = view.findViewById(R.id.trshidu);
TextView hwendu = view.findViewById(R.id.hwendu);
TextView hshidu =) view.findViewById(R.id.hshidu);
TextView guangq = view.findViewById(R.id.guangq);
TextView dianliu = view.findViewById(R.id.dianliu);
TextView shijian = (TextView) view.findViewById(R.id.shijian);
}
});
}
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(1, 1, 1, "返回");
menu.add(1, 2, 1, "删除当前数据");
return true;
}
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case 1:
finish();
break;
case 2:
dbWriter.delete("wenshi", null, null);
showAll();
break;
}
return super.onOptionsItemSelected(item);
}
private void showAll() {
Cursor result = dbReader.query("wenshi", null, null, null, null, null, "shijian", null);
if (!result.moveToFirst()) {
Toast.makeText(getApplicationContext(), "数据表中一个数据也没有!", Toast.LENGTH_LONG).show();
}
listViewAdapter = new SimpleCursorAdapter(getApplicationContext(), R.layout.item_list, result,
new String[]{"device", "trwendu", "trshidu","hwendu","hshidu",
"guangq","dianliu","shijian"},
new int[]{R.id.shebeihao, R.id.trwendu, R.id.trshidu,R.id.hwendu,
R.id.hshidu,R.id.guangq,R.id.dianliu,R.id.shijian},
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
listView.setAdapter(listViewAdapter);
}
protected void onDestroy() {
super.onDestroy();
dbReader.close();
}
}
晚些时候我也会把工程上传到github上供大家参考。