总算最后时间把【智能水电表】的项目啃下来了,总体来说这个项目技术难点不是特别多,主要是一个实现串口通讯的问题,其次就是水电表数据协议的解析问题。目前已经开始在部署阶段了,利用休息时间将内容整理如下:
1、水电表部署在家居中正常使用(当然水电表不是普通的家庭用表,而是智能家居用表,具有无线通信功能);
2、水电表通过无线通信协议将数据发送到家庭网关中;
3、家庭网关通过串口与PC机相连,通过串口通讯发给PC机接收;
4、PC机通过串口接收到网关传来的数据,进行解析显示,并存储在MySQL数据中(如何配置数据库已经在前面的文章给出,http://blog.csdn.net/rocket5725/archive/2010/01/05/5137152.aspx);
5、JSP页面读取MySQL中的数据,并显示;
6、烧录机顶盒内核,将JSP页面显示在电视机中。
本项目的技术难点主要是获取串口数据,并依据现有的通信协议对数据包进行解析并显示。之前有做过类似的串口编程用于WSN,但是现在才发现代码冗余量太大,而且不易于扩展,现在将总体过程整理如下:
该步骤可以按照如下方式创建,当然也可以直接在工具箱中拖进SerialPort。
using System.IO.Ports; SerialPort serialport = new SerialPort();
窗口首次加载时,我们将获取计算机上所有可用的串口名称并将这些名称添加到ComboBox控件里面。双击窗体实现Form1_Load事件处理。
private void Form1_Load(object sender, EventArgs e) { string[] portNames = SerialPort.GetPortNames(); for (int i = 0; i < portNames.Length - 1; i++) { this.cbbPorts.Items.Add(portNames[i]); } this.cbbPorts.Items.Add(this.serialPort1.PortName); this.btdisconn.Enabled = false; }
按照以下的方法经常会出现问题(比如不能获得正确的串口名称),后面通过查阅资料得出了以下的一个解决方案,首先需要引用Microsoft.VisualBasic,用以下遍历SerialPortNames,获得所有的串口:
private void Form1_Load(object sender, EventArgs e) { this.timer1.Enabled = true; serialport.DataReceived+=new SerialDataReceivedEventHandler(serialport_DataReceived); Computer pc = new Computer(); string[] portNames = SerialPort.GetPortNames(); foreach(string s in pc.Ports.SerialPortNames) { this.cbbPorts.Items.Add(s); } this.btdisconn.Enabled = false; }
注意还要添加DataReceived 事件:
this.serialPort1.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(this.serialPort1_DataReceived);
选择串口名后,用户点击【连接】按钮以打开所选择的端口,代码实现如下:
private void btconn_Click(object sender, EventArgs e) { if (this.serialPort1.IsOpen) { this.serialPort1.Close(); } try { this.serialPort1.Encoding = System.Text.Encoding.Unicode; this.serialPort1.Open(); this.lbMsg.Text = this.serialPort1.PortName.ToString() + "Connected."; this.btconn.Enabled = false; this.btdisconn.Enabled = true; this.rtbData.BeginInvoke(new myDelegate(updateTextBox)); this.lvdata.BeginInvoke(new myDelegate(updateListView)); } catch (Exception ex) { //MessageBox.Show(ex.ToString()); return; } }
断开串口连接只需要调用一个方法即可。
private void btdisconn_Click(object sender, EventArgs e) { try { this.serialPort1.Close(); this.lbMsg.Text = this.serialPort1.PortName + " Disconnected."; this.btconn.Enabled = true; this.btdisconn.Enabled = false; } catch (Exception ex) { //MessageBox.Show(ex.ToString()); return; } }
SerialPort类的一个良好的特性是,无需不断的查询数据是否已经到达,而只需处理DataReceived事件,它将在检测到数据到达时自动触发,不过,因为事件运行在独立的线程里,任何试图直接更新主窗体的尝试都会引发错误,因此,需要使用一个代理来更新主线程里的控件(这个尤为重要,否则一切都白搭)。
private void serialport_DataReceived(Object sender, SerialDataReceivedEventArgs e) { this.rtbData.BeginInvoke(new myDelegate(updateTextBox)); this.lvdata.BeginInvoke(new myDelegate(updateListView)); }
定义代理如下所示:
public delegate void myDelegate(); public void updateTextBox() { try { //求出需要读取的Bytes数 //int bytesToRead = serialport.BytesToRead; int bytesToRead = this.serialPort1.BytesToRead; //声明char数组 byte[] ch = new byte[bytesToRead]; //char[] ch1 = new char[bytesToRead]; int bytesRead = 0; //将读取到的bytes存储在ch数组中 //bytesRead = serialport.Read(ch, 0, bytesToRead); bytesRead = this.serialPort1.Read(ch, 0, bytesToRead); //bytesRead = this.serialPort1.Read(ch1, 0, bytesToRead); //将ch数组转换为string类型 str = this.ByteArrayToHexString(ch).Replace(" ", "").Trim(); //str=new string(ch1, 0, bytesRead); //测试代码 //str = "5353535342621708005262000704000000000000E9F245"; //将string类型放置在文本框中 this.rtbData.AppendText(str); //MessageBox.Show(str.Length.ToString()); this.rtbData.ScrollToCaret(); string rtbstr = this.rtbData.Text.ToString(); if ((rtbstr.Length >= 46) && (rtbstr.IndexOf("5353535342") < rtbstr.Length)) { string strnew = rtbstr.Substring(rtbstr.IndexOf("5353535342"), 46); if (strnew.Substring(44, 2).Trim().ToString() == "45") { DataAnalyse(strnew); this.rtbData.BeginInvoke(new myDelegate(clearTextBox)); } } else { this.rtbData.BeginInvoke(new myDelegate(clearTextBox)); } DataStore(); } catch (Exception ex) { //MessageBox.Show(ex.ToString()); return; } } public void updateListView() { try { if (this.TableID.ToString().Trim() != "") { ListViewItem list = new ListViewItem(); list.Text = TableID; list.SubItems.Add(TableID); list.SubItems.Add(ReadFlag); list.SubItems.Add(TableType); list.SubItems.Add(TableState); list.SubItems.Add(TableNum); list.SubItems.Add(PreNum); list.SubItems.Add(CRCNum); list.SubItems.Add(date); list.SubItems.Add(DataTail); this.lvdata.Items.Add(list); } } catch (Exception ex) { //MessageBox.Show(ex.Message.ToString()); //throw; return; } }
默认情况下,SerialPort类只传输ASCII字符,这是通过SerialPort类的Encoding属性设置的,如果你想传输另一种语言,就需要设置SerialPort类的Encoding属性为Unicode,以保证数据的正常发送与接收。
要通过串口向接收者发送数据,使用SerialPort类的Write()方法即可。
private void btrefresh_Click(object sender, EventArgs e) { try { this.serialPort1.Write(this.tbsend.Text.ToString()+ Environment.NewLine); tbsend.Text = string.Empty; } catch (Exception ex) { } }
由于今天图片上传功能暂时关闭,所以不能上传图片,所有的代码如下所示:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO.Ports; using MySQLDriverCS; using System.Data.Odbc; namespace SerialComm { public partial class frmSerial : Form { SerialPort serialport = new SerialPort(); string DataHead, TableID,ReadFlag,TableType,TableState,TableNum,PreNum,CRCNum,DataTail; string date; string str = "5353535342621708005262000704000000000000E9F245"; SQLHelper sqlhelper = new SQLHelper(); public frmSerial() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { string[] portNames = SerialPort.GetPortNames(); for (int i = 0; i < portNames.Length - 1; i++) { this.cbbPorts.Items.Add(portNames[i]); } this.cbbPorts.Items.Add(this.serialPort1.PortName); this.btdisconn.Enabled = false; } private void btconn_Click(object sender, EventArgs e) { if (this.serialPort1.IsOpen) { this.serialPort1.Close(); } try { this.serialPort1.Encoding = System.Text.Encoding.Unicode; this.serialPort1.Open(); this.lbMsg.Text = this.serialPort1.PortName.ToString() + "Connected."; this.btconn.Enabled = false; this.btdisconn.Enabled = true; this.rtbData.BeginInvoke(new myDelegate(updateTextBox)); this.lvdata.BeginInvoke(new myDelegate(updateListView)); } catch (Exception ex) { //MessageBox.Show(ex.ToString()); return; } } //字节数组转换十六进制 private string ByteArrayToHexString(byte[] data) { StringBuilder sb = new StringBuilder(data.Length * 3); foreach (byte b in data) sb.Append(Convert.ToString(b, 16).PadLeft(2, '0').PadRight(3, ' ')); return sb.ToString().ToUpper(); } private void btdisconn_Click(object sender, EventArgs e) { try { this.serialPort1.Close(); this.lbMsg.Text = this.serialPort1.PortName + " Disconnected."; this.btconn.Enabled = true; this.btdisconn.Enabled = false; } catch (Exception ex) { //MessageBox.Show(ex.ToString()); return; } } private void serialport_DataReceived(Object sender, SerialDataReceivedEventArgs e) { this.rtbData.BeginInvoke(new myDelegate(updateTextBox)); this.lvdata.BeginInvoke(new myDelegate(updateListView)); } public delegate void myDelegate(); public void updateTextBox() { try { //求出需要读取的Bytes数 //int bytesToRead = serialport.BytesToRead; int bytesToRead = this.serialPort1.BytesToRead; //声明char数组 byte[] ch = new byte[bytesToRead]; //char[] ch1 = new char[bytesToRead]; int bytesRead = 0; //将读取到的bytes存储在ch数组中 //bytesRead = serialport.Read(ch, 0, bytesToRead); bytesRead = this.serialPort1.Read(ch, 0, bytesToRead); //bytesRead = this.serialPort1.Read(ch1, 0, bytesToRead); //将ch数组转换为string类型 str = this.ByteArrayToHexString(ch).Replace(" ", "").Trim(); //str=new string(ch1, 0, bytesRead); //测试代码 //str = "5353535342621708005262000704000000000000E9F245"; //将string类型放置在文本框中 this.rtbData.AppendText(str); //MessageBox.Show(str.Length.ToString()); this.rtbData.ScrollToCaret(); string rtbstr = this.rtbData.Text.ToString(); if ((rtbstr.Length >= 46) && (rtbstr.IndexOf("5353535342") < rtbstr.Length)) { string strnew = rtbstr.Substring(rtbstr.IndexOf("5353535342"), 46); if (strnew.Substring(44, 2).Trim().ToString() == "45") { DataAnalyse(strnew); this.rtbData.BeginInvoke(new myDelegate(clearTextBox)); } } else { this.rtbData.BeginInvoke(new myDelegate(clearTextBox)); } DataStore(); } catch (Exception ex) { //MessageBox.Show(ex.ToString()); return; } } public void updateListView() { try { if (this.TableID.ToString().Trim() != "") { ListViewItem list = new ListViewItem(); list.Text = TableID; list.SubItems.Add(TableID); list.SubItems.Add(ReadFlag); list.SubItems.Add(TableType); list.SubItems.Add(TableState); list.SubItems.Add(TableNum); list.SubItems.Add(PreNum); list.SubItems.Add(CRCNum); list.SubItems.Add(date); list.SubItems.Add(DataTail); this.lvdata.Items.Add(list); } } catch (Exception ex) { //MessageBox.Show(ex.Message.ToString()); //throw; return; } } public void clearTextBox() { this.rtbData.Text = ""; } private void DataStore() { if (this.TableID.Trim() != "") { try { string sql = "insert into tableinfo(TableID,ReadTableFlag,TableType,TableState,TableNum,PreNum,CRCNum" // + ",ReadTime" + ") values('" + TableID + "','" + ReadFlag + "','" + TableType + "','" + TableState + "','" + TableNum + "','" + PreNum + "','" + CRCNum //+ "','" + date + "')"; sqlhelper.RunMySQL(sql); } catch (Exception ex) { //MessageBox.Show(ex.ToString()); return; } } else { } } private void DataAnalyse(string str1) { DataHead = str.Substring(0, 10).Replace(" ", "").Trim(); TableID = str.Substring(10, 8).Replace(" ", "").Trim(); ReadFlag = str.Substring(18, 2).Replace(" ", "").Trim(); TableType = str.Substring(20, 2).Replace(" ", "").Trim(); TableState = str.Substring(22, 2).Replace(" ", "").Trim(); TableNum = str.Substring(24, 8).Replace(" ", "").Trim(); PreNum = str.Substring(32, 8).Replace(" ", "").Trim(); CRCNum = str.Substring(40, 4).Replace(" ", "").Trim(); DataTail = str.Substring(44, 2).Replace(" ", "").Trim(); DateTime dtNow = DateTime.Now; dtNow = new DateTime(dtNow.Year, dtNow.Month, dtNow.Day, dtNow.Hour, dtNow.Minute, dtNow.Second); date = dtNow.ToString(); } private void 退出XToolStripMenuItem_Click(object sender, EventArgs e) { Application.Exit(); } private void serialPort1_DataReceived(object sender, SerialDataReceivedEventArgs e) { this.rtbData.BeginInvoke(new myDelegate(updateTextBox)); this.lvdata.BeginInvoke(new myDelegate(updateListView)); } private void frmSerial_FormClosing(object sender, FormClosingEventArgs e) { Application.Exit(); } private void frmSerial_FormClosed(object sender, FormClosedEventArgs e) { Application.Exit(); } private void btrefresh_Click(object sender, EventArgs e) { try { this.serialPort1.Write(this.tbsend.Text.ToString()+ Environment.NewLine); tbsend.Text = string.Empty; } catch (Exception ex) { } } } public class SQLHelper { MySQLConnection conn = null; MySQLCommand commn = null; public MySQLConnection GetConnect() { conn = new MySQLConnection(new MySQLConnectionString("localhost", "wwater", "root", "123").AsString); return conn; } public int RunMySQL(string sql) { int count = 0; try { conn = GetConnect(); conn.Open(); commn = new MySQLCommand(sql, conn); count = commn.ExecuteNonQuery(); return count; } catch (Exception ex) { //MessageBox.Show(ex.ToString()); return 0; } finally { conn.Close(); } } } }