专栏首页施炯的IoT开发专栏移动物联网 之 家电节能 (2)

移动物联网 之 家电节能 (2)

    本系列文章结合时下正热的“物联网”概念,介绍实现“家电节能”的一套解决方案。本部分讲述 “家电节能”的具体实现方法。

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数据库,然后通过曲线图,实时显示采集的数据量。界面如下:

关键代码如下:

打开串口部分

代码

//Error Check             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 = "打开串口";             }

串口数据处理部分

代码

string str = ByteArrayToHexString(message); //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端关键代码:

代码

public void   OnTimedEvent(object   source,   ElapsedEventArgs   e)          {             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事件;          }

其他设备端关键代码:

代码

rivate void server_Load(object sender, EventArgs e)         {             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

参考链接:

C# WinForm开发系列 – ZedGraph

http://baike.baidu.com/view/117166.htm

声明和致谢:本项目由北京邮电大学微软技术俱乐部成员施炯、曾阳和叶周全完成,感谢微软亚洲研究院对本项目的资金支持,感谢MSRA UR李贝、张静和王春晖。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Memory Information on Windows Mobile

        Windows Mobile设备跟桌面PC比起来,资源是相当受限的。目前最新的设备一般也就128M RAM+256M Flash,再外扩4G的存储卡。但...

    ShiJiong
  • 扩展Windows Mobile模拟器存储空间的方法

        在Windows Mobile应用程序开发的初期,可以使用SDK自带的模拟器来进行调试,这给我们开发人员提供了一种方便的途径。一般的应用程序,占用空间的...

    ShiJiong
  • GSM Communication on EBox4300--(2)

      上一篇GSM Communication on EBox4300--(1)主要是关于EBox4300上实现GSM通信的框架。这次就讲讲如何设计包含西门子TC...

    ShiJiong
  • 清华北大留不住,高中毕业去美国读AI本科值不值?

    “清华北大也没有什么不好,”Jim说:“但我想趁年轻的时候多出去看看世界,多接触一下不同的文化和学术氛围。”

    新智元
  • Elasitcsearch底层系列之 Node启动过程源码解析

    Elasticsearch 是一款开源的分布式搜索引擎,提供了近实时的查询能力和强大的聚合分析能力。与Elastic官方提供的其他组件(Beats、Logsta...

    morningchen
  • POI 操作word

    关于POI 操作word的基础知识在这个博客(http://elim.iteye.com/blog/2049110)中有非常清晰的解释,在这里我就不多解释了 

    凯哥Java
  • Django Rest Swagger生成api文档

    Swagger能成为最受欢迎的REST APIs文档生成工具之一,有以下几个原因:

    程序员同行者
  • Java设计模式学习记录-适配器模式

    之前已经将五个创建型设计模式介绍完了,从这一篇开始介绍结构型设计模式,适配器模式就是结构型模式的一种,适配器要实现的效果是把“源”过渡到“目标”。

    Jimoer
  • 用户管理模块之用户注册

    爱撒谎的男孩
  • 2. 变量声明与类型推断

    本文介绍 Kotlin 变量声明涉及的相关知识点。首先我们来回顾一下 Java 局部变量声明的几个例子(成员变量的修饰符先不讨论):

    sickworm

扫码关注云+社区

领取腾讯云代金券