本系列文章结合时下正热的“物联网”概念,介绍实现“家电节能”的一套解决方案。本部分讲述 “家电节能”的具体实现方法。
1. 系统结构
系统包括Sensor Node、Access Node和Server这三个主要组成部分。各部分的主要功能如下:
a. Sensor Node
负责电量采集,包括电压、电流和功耗等物理量,将模拟量转换为数字量,传送给Access Node;同时,Sensor Node可以接收Access Node发送的控制信息,对设备进行控制。
b. Access Node
负责接收Sensor Node发送的信息,并将这些信息发送给Server;同时,Access Node可以接收Server的控制信息,转发给对应的Access Node。
c. Server
提供UI,负责参数采集命令,将接收到的数据存入本地数据库;接收用户对各Sensor Node的阈值设置和控制指令。同时,Server也提供接口,供其他互联网设备访问。
2. 系统实现
2.1 组网方式
基于目前短距离无线通信的现状,Zigbee和RF具有各自的技术特点。Zigbee通信距离中等,抗干扰能力强,性能可靠稳定;采用IEEE802.15.4,处于全球公用的2.4G频段;组网灵活、方便,星型、树型或者Mesh网络;低功耗,低复杂度;多信道、多速率。RF通信距离远,穿透性好,抗干扰能力强;微发射功率,微功耗。 因此,我们使用了基于Zigbee和RF射频的家电节能整体解决方案。系统采用分布式网络,底层电量采集使用RF射频通信,上层使用Zigbee进行组网。由于Zigbee的穿透能力不强,所以在部署的时候,尽量将Zigbee模块放在视距范围内,不要有墙体阻隔。
从网络规模上来看,可以分为小型网络和中大型网络这两种。在小型网络中,主机只需要插座的地址就可以与插座通信;Zigbee采用广播通信方式,实现主机与插座信息的透明传输;整个网络最多包含254个插座。 在中大型网络中,采用主从通信模式,以Zigbee节点作为中继,实现主机和插座之间的信息传递;每个Zigbee节点可以包含254个插座,而整个网络可以包含多个Zigbee节点组成的子网络。
2.2 Sensor Node和Access Node
Sensor Node负责电量采集,内部包括AD模块,将模拟量转换为数字量,通过无线的方式传送给Access Node。Access Node起到一个透明传输的作用,将信息传送给Server端。由于目前大多数家电的控制接口都不公开,因此,比较通用的解决方法是将Sensor Node嵌于插座中,通过插座来检测用电情况。
2.3 Server
Server可以采用成本比较低的嵌入式设备,也可以采用PC机。在我们的项目中,我们使用了PC机,利用PC机的USB口和Access Node通信。需要说明的是,PC机的USB口是通过USB转串口模块和Access Node进行串口通信的。
Server通过串口发送数据采集指令,经Access Node转发给Sensor Node。得到数据以后,存入到本地Access数据库,然后通过曲线图,实时显示采集的数据量。界面如下:
关键代码如下:
打开串口部分
StringBuilder strErrorMsg = new StringBuilder();
if (textBoxComNo.TextLength == 0)
{
strErrorMsg.Append("请输入串口号!\n");
}
if (textBoxDataRate.TextLength == 0)
{
strErrorMsg.Append("请输入串口速率!\n");
}
if (strErrorMsg.Length > 0)
{
MessageBox.Show(strErrorMsg.ToString(), "Error");
return;
}
//State check
if (!PortOpen)
{
m_port = "COM" + this.textBoxComNo.Text;
m_serialPort = new System.IO.Ports.SerialPort(m_port, Int32.Parse(this.textBoxDataRate.Text.Trim()));
m_serialPort.Parity = Parity.None;
m_serialPort.StopBits = StopBits.One;
m_serialPort.ReceivedBytesThreshold = 11;
interfaceUpdataHandle = new HandleInterfaceUpdataDelegate(ReceivedDataProcess);
m_serialPort.DataReceived += new SerialDataReceivedEventHandler(this.m_serialPort_DataReceived);
try
{
m_serialPort.Open();
}
catch (Exception e1)
{
System.Console.WriteLine(e1.Message);
}
PortOpen = true;
this.buttonOpenCom.Text = "关闭串口";
}
else
{
if (StartInt)
{
MessageBox.Show("请先停止采集!", "Error");
return;
}
m_serialPort.Close();
PortOpen = false;
this.buttonOpenCom.Text = "打开串口";
}
串口数据处理部分
//check to claer the message window
if (listcounter == 23)
{
listcounter = 0;
this.textBoxComMessage.Text = "";
}
//display on message window
this.textBoxComMessage.Text += "RX:" + str + "\r\n";
listcounter++;
if (message[2] == 6)
{
str = BitConverter.ToString(message).Replace("–", "");
pSave.Number = Hex2Ten(str.Substring(0, 2));
pSave.Voltage = Hex2Ten(str.Substring(6, 4)) + "0";
pSave.Current = Hex2Ten(str.Substring(10, 4));
pSave.Power = Hex2Ten(str.Substring(14, 4));
}
else if (message[2] == 4)
{
//add to database
str = BitConverter.ToString(message).Replace("–", "");
string stemp = Hex2Ten(str.Substring(6, 8));
int itemp = Int32.Parse(stemp);
float b = 3200;
float ftemp = (float)itemp / b;
pSave.Consum = ftemp.ToString();
pSave.Infotime = System.DateTime.Now;
int id = ProductDao.insert(pSave);
//统计数据库数据并显示
Page page = PageQueryDao.getProducts(0);
this.labelMsg.Text = String.Format("记录数: {0:d}, \n每页{1:d}条记录, {2:d}/{3:d}页",
page.TotalRecord, page.PageSize, page.CurPageIndex + 1, page.TotalPage);
totalPage = page.TotalPage;
DisplayChart(pSave);
}
图像实时显示部分采用ZedGraph。ZedGraph是用于创建任意数据的二维线型、条型、饼型图表的一个类库,也可以作为Windows窗体用户控件和ASP.NET网页控件。这个类库具有高度的适应性,几乎所有式样的图表都能够被创建。这个类库的用法在于通过提供所有图表属性的省缺值来保持使用性的简单。这个类库包含了基于要绘制的数值范围内的可选择适当度量范围和跨度的代码。关于如何使用ZedGraph,可以参考园子里peterzb的Blog:C# WinForm开发系列 – ZedGraph。
为了便于网络上其他设备对Server的数据访问,Server端程序每隔一定时间将数据通过socket发送到其他设备。
Server端关键代码:
{
mdbget();
BeginSend();
}
public void send_Click(object sender, EventArgs e)
{
BeginSend();
}
public void BeginSend()
{
string ip = "127.0.0.1";;
string port = "2000";
IPAddress serverIp = IPAddress.Parse(ip);
int serverPort = Convert.ToInt32(port);
IPEndPoint iep = new IPEndPoint(serverIp, serverPort);
byte[] byteMessage;
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(iep);
byteMessage = Encoding.GetEncoding("gb2312").GetBytes(sendtext);
socket.Send(byteMessage);
socket.Shutdown(SocketShutdown.Both);
socket.Close();
}
private void getmdb_Click(object sender, EventArgs e)
{
this.sensorTableAdapter.Fill(this.sensorDataSet.sensor);
mdbget();
}
public void mdbget()
{
String connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=c:\\Debug\\sensor.mdb";
OleDbConnection connection = new OleDbConnection(connectionString);
connection.Open();
string SQL = "select * from sensor";
OleDbCommand cmd = new OleDbCommand(SQL, connection);
OleDbDataReader rs = cmd.ExecuteReader();
int i = 0;
sendtext = "";
while (rs.Read())
{
i++;
sendtext += (rs[0].ToString() + "," + rs[1].ToString() + "," + rs[2].ToString() + "," + rs[3].ToString() + "," + rs[4].ToString() + "," + rs[5].ToString() + "," + rs[6].ToString() + "。");
}
sendtext = i.ToString() + "。" + sendtext.ToString();
rs.Close();
connection.Close();
}
private void client_Load(object sender, EventArgs e)
{
// TODO: 这行代码将数据加载到表“sensorDataSet.sensor”中。您可以根据需要移动或移除它。
this.sensorTableAdapter.Fill(this.sensorDataSet.sensor);
System.Timers.Timer aTimer = new System.Timers.Timer(30000); //实例化Timer类,设置间隔时间为10000毫秒;
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent); //到达时间的时候执行事件
// Only raise the event the first time Interval elapses.
aTimer.AutoReset = true; //设置是执行一次(false)还是一直执行(true);
aTimer.Enabled = true; //是否执行System.Timers.Timer.Elapsed事件;
}
其他设备端关键代码:
{
openserver();
System.Timers.Timer aTimer = new System.Timers.Timer(30000); //实例化Timer类,设置间隔时间为xx毫秒;
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent); //到达时间的时候执行事件
// Only raise the event the first time Interval elapses.
aTimer.AutoReset = true; //设置是执行一次(false)还是一直执行(true);
aTimer.Enabled = true; //是否执行System.Timers.Timer.Elapsed事件;
}
public void OnTimedEvent(object source, ElapsedEventArgs e)
{
savedata();
}
private void open_Click(object sender, EventArgs e)
{
openserver();
}
public void openserver()
{
open.Enabled = false;
try
{
mythread = new Thread(new ThreadStart(BeginListen));
mythread.Start();
}
catch (System.Exception er)
{
MessageBox.Show(er.Message, "完成", MessageBoxButtons.OK, MessageBoxIcon.Stop);
}
}
private void BeginListen()
{
string host = "127.0.0.1";
IPAddress ServerIp = IPAddress.Parse(host);
IPEndPoint iep = new IPEndPoint(ServerIp,2000);
socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
byte[] recvBytes = new byte[65536];
this.label1.Text = iep.ToString();
socket.Bind(iep);
while (true)
{
try
{
socket.Listen(5);
Socket newSocket = socket.Accept();
int bytes;
bytes = newSocket.Receive(recvBytes, recvBytes.Length, 0);
string sTime = DateTime.Now.ToShortTimeString();
string msg = sTime + ":" + "Message from:";
msg += newSocket.RemoteEndPoint.ToString()+":" +Encoding.GetEncoding("gb2312").GetString(recvBytes, 0, bytes);// Encoding.ASCII.GetString(recvBytes, 0, bytes);
textBox1.Text = "";
textBox1.Text = Encoding.GetEncoding("gb2312").GetString(recvBytes, 0, bytes);
this.listBox1.Items.Add(msg);
}
catch (SocketException ex)
{
this.label1.Text += ex.ToString();
}
}
}
private void save_Click(object sender, EventArgs e)
{
savedata();
}
public void savedata()
{
string info = textBox1.Text.ToString();
if (info != "")
{
string[] record = info.Split('。');
int i;
int all = int.Parse(record[0].ToString());
//StreamWriter sw = new StreamWriter("data.txt", false);
String connectionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=d:\\sensor.mdb";
OleDbConnection connection = new OleDbConnection(connectionString);
connection.Open();
string sql;
sql = "delete * from sensor";
OleDbCommand cmd = new OleDbCommand(sql, connection);
cmd.ExecuteNonQuery();
for (i = 1; i <= all; i++)
{
string[] str = record[i].Split(',');
sql = "insert into sensor values (" + str[0] + ",'" + str[1] + "','" + str[2] + "','" + str[3] + "','" + str[4] + "','" + str[5] + "','" + str[6] + "')";
cmd = new OleDbCommand(sql, connection);
cmd.ExecuteNonQuery();
}
connection.Close();
// MessageBox.Show("执行成功");
}
/* else
MessageBox.Show("还没有数据,无法存储");*/
}
Server端软件视频已经上传至youku,地址:http://v.youku.com/v_show/id_XMTQwNDcwNDQ4.html。
参考链接:
http://baike.baidu.com/view/117166.htm
声明和致谢:本项目由北京邮电大学微软技术俱乐部成员施炯、曾阳和叶周全完成,感谢微软亚洲研究院对本项目的资金支持,感谢MSRA UR李贝、张静和王春晖。