一谈到智能家居设备,特别是智能家电,都默认为他们使用WIFI与移动设备(如智能手机)交互。确实,这种方式优点很多,速度快、容量大、传输距离较远。特别是现代家庭,几乎都有 WIFI无线通信网络。硬件成本也大幅下降,可以预测,未来的智能家电,特别是对能耗没有要求的设备(如智能空调、智能冰箱、智能电视),应该是上上之选。蓝牙通信,实质没有变化,用的2.4G频率,只是协议复杂,稳定性较好一点,但传输距离不如人意,如果是别墅豪宅使用,通信就有些问题。
对能耗要求高的设备系统,基本采用 ZigBee网络组网,最后通过协调器对外交互。现在的通用做法是,把协调器和网关设备集成在一起,把协调器的串口通信转换为TCP/IP方式。这样,对外而言,整个ZigBee网络的设备,看成是一个WIFI智能设备系统。当然,在SHP平台上,也可以直接把协调器的串口通信数据送到家庭服务器,由监控程序SHM负责处理。
还有很多的智能设备,可能是通过有线的RS485方式通信,简单可靠,能降低设备成本。如智能电表、智能水表、气表。虽说是智能设备,其实“智能化”程度并不高。大部分采用主从通信方式。其状态的获取,得靠监控程序主动去联系获取。为此, SHP专门为这些非智能化设备的接入提供了通用的处理机制。参见ISmartHome中的InitComm接口说明。
现代家庭,拥有的电脑不止一台,平板、手机可能人手一个。在电脑上,有许多和家庭有关的应用,如家庭影院系统,背景音乐系统,家庭理财软件,这些基本是纯的软件系统。能否把它们也设计成一个设备,统一纳入SHP的管理之下。如果可行,你能想象,那是多么的爽心:用一个手机,在一个APP中就能随心所欲的监控这些软件。还可以与家人聊天,了解正在播放什么电影、播放哪些音乐。 SHP可以接入虚拟设备系统。方法很简单,为这些“设备”编写一个“驱动程序”,使用TCP/IP或进程间通信(IPC)的方式与监控程序SHM交互。使用IPC通信的方式,主要是虚拟设备系统与服务器运行在同一台电脑上运行的情况下使用,比如你也不想专门配一台电脑来做背景音乐系统吧,把音乐系统安装在服务器电脑上即可。
三种主要通信方式在SHP上都得到了实现,并可以配置。下图是有关窗口通信的配置界面。
这是对一个非智能设备通信的设置。采用了主从通信方式,更新周期是5秒。这个串口通信对象会传递给驱动程序使用。该对象的定义如下:
namespace SmartHomeMonitor
{
public class CommParameter
{
#region 属性定义
public string BaudRate { get; set; }
public string DataBits { get; set; }
public string ReadTimeout { get; set; }
public string Parity { get; set; }
public string StopBits { get; set; }
public string Handshake { get; set; }
public string ComportName { get; set; }
public bool DTR { get; set; }
public bool RTS { get; set; }
//public bool bMasterSlave { get; set; } //主从通信方式否
//public int Interval { get; set; } //通信间隔时间
private string FileName; //配置文件名
#endregion
public CommParameter() //缺省构造函数
{
FileName = "commport.xml";
//bMasterSlave = true;
//Interval = 30; //半分钟通信一次
InitPara();
}
public CommParameter(string _FileName) //带参数,指定参数文件
{
FileName=_FileName;
InitPara();
LoadParameters();
}
private void InitPara() //初始化串口通信参数
{
BaudRate = "9600";
DataBits = "8";
ReadTimeout = "-1";
Parity = "无";
StopBits = "1";
Handshake = "无";
ComportName = "COM1";
DTR = false;
RTS = false;
}
public void LoadParameters() //从文件载入参数
{
if (!File.Exists(FileName)) return; //配置文件不存在
CommParameter tmp = new CommParameter(); //不带参数的构造函数
//配置文件存在
XmlSerializer xmlSerializer = new XmlSerializer(typeof(CommParameter));
try
{
using (FileStream stream = File.Open(FileName, FileMode.Open))
{
tmp = xmlSerializer.Deserialize(stream) as CommParameter;
//复制过去
this.BaudRate = tmp.BaudRate;
this.ComportName = tmp.ComportName;
this.DataBits = tmp.DataBits;
this.DTR = tmp.DTR;
this.Handshake = tmp.Handshake;
this.Parity = tmp.Parity;
this.StopBits = tmp.StopBits;
this.ReadTimeout = tmp.ReadTimeout;
this.RTS = tmp.RTS;
return ;
}
}
catch
{
return;
}
}
public void Save() //保存通信参数信息到文件
{
using (FileStream stream = File.Create(FileName))
{
XmlSerializer ser = new XmlSerializer(typeof(CommParameter));
ser.Serialize(stream, this);
stream.Close();
}
}
public static Parity GetParity(string Paritystring)
{
if (Paritystring == "无") return System.IO.Ports.Parity.None;
else if (Paritystring == "奇") return System.IO.Ports.Parity.Odd;
else if (Paritystring == "偶") return System.IO.Ports.Parity.Even;
else if (Paritystring == "掩码") return System.IO.Ports.Parity.Mark;
else if (Paritystring == "空格") return System.IO.Ports.Parity.Space;
else return System.IO.Ports.Parity.None;
}
public static StopBits GetStipBits(string cbStopBitsText)
{
if (cbStopBitsText == "1") return System.IO.Ports.StopBits.One;
else if (cbStopBitsText == "2") return System.IO.Ports.StopBits.Two;
else if (cbStopBitsText == "1.5") return System.IO.Ports.StopBits.OnePointFive;
else return System.IO.Ports.StopBits.One;
}
public static Handshake GetHandshake(string cbHandshakeText)
{
if (cbHandshakeText == "无") return System.IO.Ports.Handshake.None;
else if (cbHandshakeText == "XOnXOff") return
System.IO.Ports.Handshake.XOnXOff;
else if (cbHandshakeText == "请求发送") return
System.IO.Ports.Handshake.RequestToSend;
else if (cbHandshakeText == "请求发送XONXOFF") return
System.IO.Ports.Handshake.RequestToSendXOnXOff;
else return System.IO.Ports.Handshake.None;
}
}
}
由于串口设备,大部分是单片机系统,实现复杂的数据格式通信有困难,所以它与SHM的交互,信息的解释,都由其驱动程序做转换,因此都是专用的数据格式。
对于智能门锁这样的设备,使用TCP/IP通信协议工作。我们对其通信端口做了设置。另外,为了防止未授权设备随意访问监控程序SHM,还设置了密码和账号验证,在PNP章节在介绍这个机制的实现。连接后必须验证密码,否则断开连接。下图是对智能门锁TCP/IP通信的设置。由于未使用串口通信,我们取消了他的串口通信方式。设备信息,门锁会自动发送过来,所以“主从通信方式”也取消了。
由于此类通信,是未来的主流。SHP专门写了一个对象来帮助简化TCP/IP通信。以下是该对象的部分内容(内容较多,省略了)。
using HomeLibrary.ShareMemory;
using HomeLibrary.Json;
using HomeLibrary.Type;
namespace SmartHomeMonitor
{
public delegate void OnTextReceived(TextReceivedEventArgs<string> e);
public delegate void OnDataReceived(TextReceivedEventArgs<byte[]> e);
public class TextReceivedEventArgs<T> : EventArgs
{
public TextReceivedEventArgs(ConnectClient tcpClient, T datagram)
{
TcpClient = tcpClient;
Datagram = datagram;
}
public ConnectClient TcpClient { get; private set; }
public T Datagram { get; private set; }
}
public class ConnectClient
{
int MaxBufferSize = 0x100000; //1M
public event OnTextReceived OnTcpTextReceived;
public event OnDataReceived OnTcpDataReceived;
public bool Connected { get { return m_tcpClient.Client.Connected; } }
List<ConnectClient> Clients;
public TcpClient m_tcpClient;
public Int32 flag;
public string IP;
public string rights; //权限字符串,可用于各种目的
public string User; //登记用户名
public bool canComunication; //是否可以正常通信了
public int illComunication; //非法通信次数,当减为0时,断开连接;
NetworkStream ns;
Encoding encoding = Encoding.Default;
public ConnectClient(TcpClient tc, int TimeoutMs, int _Flag,Encoding encoding,
List<ConnectClient> Clients)
{ …… }
......
}
}
为了规范各厂家设备数据通信格式,我们设计了有一个通用通信数据对象stringJson,它比目前在互联网通信中流行的Json 结构简单,但更加灵活。其实质是维护着一个字典结构Dictionary<string, byte[]>, 由于“值”是字节数组,可以存储任意内容。该对象提供了很多方法来简化使用字典对象。下面的是它的全部内容,免费开放。
namespace HomeLibrary.Json
{
public class stringJson
{
public Dictionary<string, byte[]> mDictionary = null;
Int32 flag = 0xAA11; //某公司通信的标志,不是的,不处理
public stringJson()
{
mDictionary = new Dictionary<string, byte[]>();
flag = SmartHomeChannel.SHFLAG; //我们智能家居系统的标志
}
public stringJson(Int32 _flag)
{
mDictionary = new Dictionary<string, byte[]>();
flag = _flag;
}
public void Clear()
{
if (mDictionary != null)
mDictionary.Clear();
}
public void AddNameValume(string name,string valume)
{
mDictionary[name] = Encoding.UTF8.GetBytes(valume);
}
public void AddNameValume(string name, char[] valume)
{
mDictionary[name] = Encoding.UTF8.GetBytes(valume);
}
public void AddNameValume(string name, Stream valume)
{
byte[] buffer=new Byte[valume.Length];
valume.Read(buffer,0,(int)valume.Length);
mDictionary[name] = buffer;
}
public void AddNameValume(string name, byte[] valume)
{
mDictionary[name] = valume;
}
public void DeleteName(string Name)
{
if (mDictionary.Keys.Contains(Name))
mDictionary.Remove(Name);
}
public string GetValume(string Name)
{
if (mDictionary.Keys.Contains(Name))
{
char[] s = Encoding.UTF8.GetChars(mDictionary[Name]);
return new string(s);
}
else return null;
}
public byte[] GetValumeArray(string Name)
{
if (mDictionary.Keys.Contains(Name))
return mDictionary[Name];
else return null;
}
public string[] GetNames()
{
return mDictionary.Keys.ToArray();
}
public override string ToString()
{
string result = "";
foreach (string Name in mDictionary.Keys)
{
result += Name + "=" + GetValume(Name) + "\r\n";
}
return result;
}
//根据编码返回便于发送的字节数组
public byte[] GetBytes()
{
MemoryStream stream = new MemoryStream();
BinaryWriter bw = new BinaryWriter(stream,Encoding.UTF8); //二进制写入:2015-5修改by吴志辉
//---先写入标志
bw.Write(flag); // 4字节
bw.Write((int)0); // 先写入一个占位符,表示整个字节流的长度 4字节
//---首先写入字典的条目数
bw.Write(mDictionary.Count); //先写入长度 4字节
foreach (string Name in mDictionary.Keys)
{
bw.Write(Name); //先写入键名
byte[] buffer = mDictionary[Name]; //键值
bw.Write(buffer.Length); //先写入键值的长度
stream.Write(buffer, 0, buffer.Length); //再写入数据
}
//byte[] result = stream.ToArray();
bw.Seek(sizeof(Int32), SeekOrigin.Begin);
bw.Write((int)stream.Length); // 最后先写入整个字节流的长度 4字节
bw.Seek((int)stream.Length, SeekOrigin.Begin); //必须重新定位到最后!
byte[] result = stream.ToArray();
bw.Close();
bw.Dispose();
stream.Dispose();
return result;
}
public void ReadFromBytes(byte[] buffer, Int32 _flag)
{
mDictionary.Clear();
if (buffer.Length < 8) return; //有问题,至少8个字节长度
MemoryStream stream = new MemoryStream();
stream.Write(buffer, 0, buffer.Length); //转移到流中处理
stream.Position = 0;
BinaryReader br = new BinaryReader(stream, Encoding.UTF8); //二进制流读取:2015-5修改by吴志辉
//---首先读取标志
int len = br.ReadInt32();
#region 合法性检查
if (_flag != len)
{
br.Close();
br.Dispose();
stream.Dispose();
return; //非系统通信数据或乱码、断码
}
len = br.ReadInt32(); //总的字节数
if (buffer.Length < len) //长度不对,直接退出
{
br.Close();
br.Dispose();
stream.Dispose();
return; //非系统通信数据或乱码、断码
}
#endregion
int counts = br.ReadInt32(); //---首先读取字节中包含字典的条目数counts
int count = 0;
try
{
while (count < counts)
{
count++;
string Name = br.ReadString(); //键名
len = br.ReadInt32(); //键值的长度
byte[] destBuffer = br.ReadBytes(len);
mDictionary[Name] = destBuffer; //添加一个项
}
}
catch { }
br.Close();
br.Dispose();
stream.Dispose();
}
public static stringJson ConvertBytesTostringJson(byte[] buffer,Int32 _flag)
{
if (buffer.Length<8) return null; //有问题,至少8个字节长度
MemoryStream stream = new MemoryStream();
stream.Write(buffer, 0, buffer.Length); //转移到流中处理
stream.Position = 0;
BinaryReader br = new BinaryReader(stream, Encoding.UTF8); //二进制流读取:2015-5修改by吴志辉
//---首先读取标志
int len = br.ReadInt32();
#region 合法性检查
if (_flag != len)
{
br.Close();
br.Dispose();
stream.Dispose();
return null; //非系统通信数据或乱码、断码
}
len = br.ReadInt32(); //总的字节数
if (buffer.Length< len) //长度不对,直接退出
{
br.Close();
br.Dispose();
stream.Dispose();
return null; //非系统通信数据或乱码、断码
}
#endregion
int counts = br.ReadInt32(); //---首先读取字节中包含字典的条目数counts
int count = 0;
stringJson json = new stringJson(_flag);
try
{
while (count < counts)
{
count++;
string Name = br.ReadString(); //键名
len = br.ReadInt32(); //键值的长度
byte[] destBuffer = br.ReadBytes(len);
json.mDictionary[Name] = destBuffer; //添加一个项
}
}
catch { }
br.Close();
br.Dispose();
stream.Dispose();
return json; //返回stringJson结构
}
}
}
在大量的网络通信任务中都使用该数据结构,如果您对SHP感兴趣,必须了解其使用方法。在进程间通信,也使用了该对象。
本节内容较多了,介绍到此。下节继续介绍SHP的进程间通信机制。