物联网专业课程设计:温室监控系统——移动终端篇(LineChart、SQLite、socket套接字)(包含源码)

这个程序是本科课程设计写的程序,因为最近在复习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 Socketip地址,端口号),创建socket对象,当连接上上位机后,上位机会调用accept()方法返回连接的套接字,此时线程池创建两个线程,分别用于接收和发送操作。之后调用getInputStream()方法获取上位机TCP套接字的输入流,存入字节数组交由数据解析模块进行处理。在设备状态显示界面,用户通过SwitchTextView控件进行指令下达,此时,套接字对象会调用输出流的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.数据显示模块设计

该模块包含两个主要部分:实时数据显示部分与折线图显示部分。

本设计所涉及到的控件有TextViewLineChart。接收线程每隔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支持WindowsLinux等主流操作系统,所需内存很少,并且能够与很多程序语言相结合。由于移动设备平台的内存和外存都受到限制,因此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上供大家参考。


版权声明:本文为qq_40409189原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。