工业4.0通信协议学习之Hermes
前言
随着大数据和工业4.0的发展,对于设备生产商而言,所生产的设备需要支持各种通信协议,因此,设备开发的软件工程师也需要学习各种通信协议。本文用于记录Hermes协议的学习。
一、Hermes协议适用范围
Hermes通信协议用于在表面贴装技术 (SMT) 生产线上处理电路板传输和相关数据。该协议是SMEMA协议的升级版和替代版,因此主要用于相邻机器(M2M)之间的信息传输,使用TCP协议进行连接。
二、如何通信
对于设备软件开发而言,主要关注的点是与谁通信,如何通信,交互什么数据,什么时候交互?
1. 与谁通信
前面介绍了Hermes协议用于相邻机器之间的信息交互,因此对于一台设备而言,主要是与上下游机进行通信,在Hermes协议中把这定义为Horizontal Channel;另一个是与Supervisory System通信,在Hermes协议中叫做Vertical Channel。
2. 如何通信
1) 与上游机通信
与上游机通信时,上游机的每个轨道是一个TCP Server,它会提供IP和端口号,我们设备只需要监听该端口号就可以实现通信。
2) 与下游机通信
与下游机通信时,我们的设备的每个轨道作为一个TCP Server,提供IP和端口号,供下游机连接。
3) 与Supervisory System通信
与Supervisory System通信时,我们设备作为TCP Client,去监听Supervisory System提供的Server。
3. 交互什么数据
在Hermes协议中定义了很多消息(Message),如CheckAlive、ServiceDescription、Notification等等。这些消息包含了设备信息、生产信息等等,通过Hermes协议进行传输,并控制生产,哪些消息是传到上游的,哪些是传到下游的,协议中做了详细规定。
注意,协议已经规定了消息传输的格式为XML格式,因此所有数据必须生成XML格式之后才能进行传输,如下图为CheckAlive消息的格式。
4. 什么时候交互
这里要Hermes协议中非常重要的一个部分,Hermes状态机,如下图,所有的消息都是在状态切换时才会发送。
每个TCP连接都会有一个状态机,如图中所示,Hermes状态分为9种:NotConnected、ServiceDescription、NotAvaliableNotReady、BoardAvaliable、AvaliableAndReady、MachineReady、TransportStopped、Transporting、TransportFinished。每个状态切换时都有先提条件,例如从NotConnected–>ServiceDescriptionDownStream,需要当前Hermes状态为NotConnected,且当前正好本机与上游机Lane 1的Socket连接通讯成功,那么此时该连接的Hermes状态变成ServiceDescriptionDownStream,此时根据上图状态机,本机需要发送ServiceDescription消息给上游机Lane 1。
除了与上下游机的Hermes状态机外,Hermes协议还规定了本机与SuperVisory System连接的状态机,如下图所示:
三、代码示例
代码只是简单列了一下,收到消息该做哪些动作,并没有写具体操作,真正用在项目中还需要根据协议进行补充和修改。
1.上下游状态机主要代码示例
using Prism.Mvvm;
namespace HermesLibrary.Models
{
public class HermesStatus : BindableBase
{
public HermesStatus()
{
}
public bool bFromUp { get; set; }
public bool bFrontLane { get; set; }
private EHermesState _State;
public EHermesState HermesState
{
get { return _State; }
set
{
_State = value;
RaisePropertyChanged();
}
}
private bool _bSocketConnected;
public bool bSocketConnected
{
get
{
return _bSocketConnected;
}
set
{
_bSocketConnected = value;
RaisePropertyChanged();
if (_bSocketConnected)
{
if (HermesState == EHermesState.eHERMES_STATE_NOT_CONNECTED)
{
HermesState = EHermesState.eHERMES_STATE_SOCKET_CONNECTED;
if (bFromUp)
{
bServiceDescriptionDownstream = true;
CallStatusChangedToUp(HermesState, bFrontLane);
}
else
CallStatusChangedToDown(HermesState, bFrontLane);
}
}
else
{
HermesState = EHermesState.eHERMES_STATE_NOT_CONNECTED;
if(bFromUp)
CallStatusChangedToUp(HermesState, bFrontLane);
else
CallStatusChangedToDown(HermesState, bFrontLane);
}
}
}
private bool _bServiceDescriptionDownstream = false;
public bool bServiceDescriptionDownstream
{
get { return _bServiceDescriptionDownstream; }
set
{
_bServiceDescriptionDownstream = value;
RaisePropertyChanged();
if (HermesState == EHermesState.eHERMES_STATE_NOT_CONNECTED)
{
HermesState = EHermesState.eHERMES_STATE_SERVICE_DESCRIPTION_DOWNSTREAM;
if (bFromUp)
CallStatusChangedToUp(HermesState, bFrontLane);
}
}
}
private bool _bServiceDescriptionUpstream = false;
public bool bServiceDescriptionUpstream
{
get { return _bServiceDescriptionUpstream; }
set
{
_bServiceDescriptionUpstream = value;
RaisePropertyChanged();
if (_bServiceDescriptionUpstream && HermesState == EHermesState.eHERMES_STATE_SERVICE_DESCRIPTION_DOWNSTREAM)
{
HermesState = EHermesState.eHERMES_STATE_NOT_AVAILABLE_NOT_READY;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
}
}
private bool _bBoardForecast = false;
public bool bBoardForecast
{
get { return _bBoardForecast; }
set
{
_bBoardForecast = value;
RaisePropertyChanged();
if (_bBoardForecast)
{
if (HermesState == EHermesState.eHERMES_STATE_NOT_AVAILABLE_NOT_READY)
{
HermesState = EHermesState.eHERMES_STATE_NOT_AVAILABLE_NOT_READY;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
else if (HermesState == EHermesState.eHERMES_STATE_MACHINE_READY)
{
HermesState = EHermesState.eHERMES_STATE_MACHINE_READY;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
}
}
}
private bool _bMachineReady = false;
public bool bMachineReady
{
get { return _bMachineReady; }
set
{
_bMachineReady = value;
RaisePropertyChanged();
if (_bMachineReady)
{
if (HermesState == EHermesState.eHERMES_STATE_NOT_AVAILABLE_NOT_READY)
{
HermesState = EHermesState.eHERMES_STATE_MACHINE_READY;
if (bFromUp)
CallStatusChangedToUp(HermesState, bFrontLane);
}
else if (HermesState == EHermesState.eHERMES_STATE_AVAILABLE_AND_READY)
{
HermesState = EHermesState.eHERMES_STATE_BOARD_AVAILABLE;
if (bFromUp)
CallStatusChangedToUp(HermesState, bFrontLane);
}
}
else
{
if (HermesState == EHermesState.eHERMES_STATE_AVAILABLE_AND_READY)// RevokeMachineReady
{
HermesState = EHermesState.eHERMES_STATE_BOARD_AVAILABLE;
if (bFromUp)
CallStatusChangedToUp(HermesState, bFrontLane);
}
else if (HermesState == EHermesState.eHERMES_STATE_MACHINE_READY)// RevokeMachineReady
{
HermesState = EHermesState.eHERMES_STATE_NOT_AVAILABLE_NOT_READY;
if (bFromUp)
CallStatusChangedToUp(HermesState, bFrontLane);
}
}
}
}
private bool _bBoardAvailable = false;
public bool bBoardAvailable
{
get { return _bBoardAvailable; }
set
{
_bBoardAvailable = value;
RaisePropertyChanged();
if (_bBoardAvailable)
{
if (HermesState == EHermesState.eHERMES_STATE_NOT_AVAILABLE_NOT_READY)
{
HermesState = EHermesState.eHERMES_STATE_BOARD_AVAILABLE;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
else if (HermesState == EHermesState.eHERMES_STATE_MACHINE_READY)
{
HermesState = EHermesState.eHERMES_STATE_AVAILABLE_AND_READY;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
else if (HermesState == EHermesState.eHERMES_STATE_TRANSPORT_STOPPED)
{
HermesState = EHermesState.eHERMES_STATE_TRANSPORT_STOPPED;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
else if (HermesState == EHermesState.eHERMES_STATE_TRANSPORTING)
{
HermesState = EHermesState.eHERMES_STATE_TRANSPORTING;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
}
else
{// RevokeBoardAvailable
if (HermesState == EHermesState.eHERMES_STATE_BOARD_AVAILABLE)
{
HermesState = EHermesState.eHERMES_STATE_NOT_AVAILABLE_NOT_READY;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
else if (HermesState == EHermesState.eHERMES_STATE_AVAILABLE_AND_READY)
{
HermesState = EHermesState.eHERMES_STATE_MACHINE_READY;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
else if (HermesState == EHermesState.eHERMES_STATE_TRANSPORT_STOPPED)
{
HermesState = EHermesState.eHERMES_STATE_TRANSPORT_STOPPED;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
else if (HermesState == EHermesState.eHERMES_STATE_TRANSPORTING)
{
HermesState = EHermesState.eHERMES_STATE_TRANSPORTING;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
}
}
}
private bool _bStartTransport = false;
public bool bStartTransport
{
get { return _bStartTransport; }
set
{
_bStartTransport = value;
RaisePropertyChanged();
if (_bStartTransport)
{
if (HermesState == EHermesState.eHERMES_STATE_AVAILABLE_AND_READY
|| HermesState == EHermesState.eHERMES_STATE_MACHINE_READY)
{
HermesState = EHermesState.eHERMES_STATE_TRANSPORTING;
if(bFromUp)
CallStatusChangedToUp(HermesState, bFrontLane);
}
}
}
}
private bool _bStopTransport = false;
public bool bStopTransport
{
get { return _bStopTransport; }
set
{
_bStopTransport = value;
RaisePropertyChanged();
if (_bStopTransport)
{
if (HermesState == EHermesState.eHERMES_STATE_TRANSPORTING)
{
HermesState = EHermesState.eHERMES_STATE_TRANSPORT_STOPPED;
if (bFromUp)
CallStatusChangedToUp(HermesState, bFrontLane);
}
else if (HermesState == EHermesState.eHERMES_STATE_TRANSPORT_FINISHED)
{
HermesState = EHermesState.eHERMES_STATE_NOT_AVAILABLE_NOT_READY;
if (bFromUp)
CallStatusChangedToUp(HermesState, bFrontLane);
}
}
}
}
private bool _bTransportFinished = false;
public bool bTransportFinished
{
get { return _bTransportFinished; }
set
{
_bTransportFinished = value;
RaisePropertyChanged();
if (_bTransportFinished)
{
if (HermesState == EHermesState.eHERMES_STATE_TRANSPORTING)
{
HermesState = EHermesState.eHERMES_STATE_TRANSPORT_FINISHED;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
else if (HermesState == EHermesState.eHERMES_STATE_TRANSPORT_STOPPED)
{
HermesState = EHermesState.eHERMES_STATE_NOT_AVAILABLE_NOT_READY;
if (!bFromUp)
CallStatusChangedToDown(HermesState, bFrontLane);
}
}
}
}
#region Events
public delegate void StatusChangedEventHandler(object sender, EHermesState e,bool bfrontLane);
public event StatusChangedEventHandler OnHermesStatusChangedToUp;
protected virtual void CallStatusChangedToUp(EHermesState e, bool bfrontLane)
{
OnHermesStatusChangedToUp?.Invoke(this, e, bfrontLane);
}
public event StatusChangedEventHandler OnHermesStatusChangedToDown;
protected virtual void CallStatusChangedToDown(EHermesState e, bool bfrontLane)
{
OnHermesStatusChangedToDown?.Invoke(this, e, bfrontLane);
}
#endregion
}
}
2.与上游通信代码示例
public HermesConnectionUpStream(bool bDualLane, IPEndPoint frontLaneIPEndPoint, IPEndPoint rearLaneIPEndPoint)
{
FrontLaneClient = new AsyncTcpClientHermes(frontLaneIPEndPoint);
FrontLaneClient.DatagramReceived += HandleCmdFromUp;
HermesStatusToUpFront = new HermesStatus
{
bFromUp = true,
bFrontLane = true
};// Upstream Lane 1
HermesStatusToUpFront.OnHermesStatusChangedToUp += HermesStatusToUpR_OnHermesStatusChangedToUp;
if (bDualLane)
RearLaneClient = new AsyncTcpClientHermes(rearLaneIPEndPoint);
}
private void HermesStatusToUpR_OnHermesStatusChangedToUp(object sender, EHermesState e, bool bfrontLane)
{
if (bfrontLane)
{
#region UpStream FrontLane
if (HermesStatusToUpFront.HermesState.Equals(EHermesState.eHERMES_STATE_NOT_CONNECTED))
{
#region Disconnect
// Stop CheckAlive Timer
// TODO...
#endregion
}
else if (HermesStatusToUpFront.HermesState.Equals(EHermesState.eHERMES_STATE_SOCKET_CONNECTED))
{
#region Connect
// Start CheckAlive Timer
// TODO...
#endregion
}
else if (HermesStatusToUpFront.bServiceDescriptionDownstream && HermesStatusToUpFront.bSocketConnected)
{
#region ServiceDescriptionDownstream
// TODO...
// SendToUpCmd(ServiceDescriptionDownstream);
#endregion
}
else if (HermesStatusToUpFront.HermesState.Equals(EHermesState.eHERMES_STATE_MACHINE_READY))
{
#region MachineReady
// TODO...
// SendToUpCmd(MachineReady);
#endregion
}
else if (HermesStatusToUpFront.HermesState.Equals(EHermesState.eHERMES_STATE_TRANSPORTING))
{
#region StartTransport
// TODO...
#endregion
}
else if (HermesStatusToUpFront.HermesState.Equals(EHermesState.eHERMES_STATE_TRANSPORT_STOPPED))
{
#region StopTransport
// TODO...
#endregion
}
#endregion
}
else
{
#region UpStream RearLane
#endregion
}
}
private void HandleCmdFromUp(object sender, TcpDatagramReceivedEventArgs<byte[]> e)
{
string xml = Encoding.UTF8.GetString(e.Datagram);
Enum_CMD CmdFromUp = new Enum_CMD();// This value is from xml
switch (CmdFromUp)
{
case Enum_CMD.CheckAlive:
#region CheckAlive
// SendPong
#endregion
break;
case Enum_CMD.ServiceDescription:
#region ServiceDescription
HermesStatusToUpFront.bServiceDescriptionDownstream = true;
#endregion
break;
case Enum_CMD.Notification:
#region Notification
// Disconnect with UpStream
// Stop CheckAlive Timer
#endregion
break;
case Enum_CMD.BoardAvailable:
#region BoardAvailable
HermesStatusToUpFront.bBoardAvailable = true;
#endregion
break;
case Enum_CMD.RevokeBoardAvailable:
#region RevokeBoardAvailable
HermesStatusToUpFront.bBoardAvailable = false;
#endregion
break;
case Enum_CMD.TransportFinished:
#region TransportFinished
HermesStatusToUpFront.bTransportFinished = true;
#endregion
break;
case Enum_CMD.SendBoardInfo:
#region SendBoardInfo
// UpStream send SendBoardInfo after receiving QueryBoardInfo
#endregion
break;
case Enum_CMD.BoardForecast:
#region BoardForecast
HermesStatusToUpFront.bBoardForecast = true;
#endregion
break;
case Enum_CMD.Command:
#region Command
#endregion
break;
default:
break;
}
}
3. 与下游机通信的代码示例
public HermesConnectionDownStream(IPEndPoint localEP)
{
iPEndPoint = localEP;
FrontLaneServer = new AsyncTcpServerHermes(localEP);
FrontLaneServer.DataReceived += HandleCmdFromDown;
HermesStatusToDownFront = new HermesStatus
{
bFromUp = false,
bFrontLane = true
};
HermesStatusToDownFront.OnHermesStatusChangedToDown += HermesStatusToDownFront_OnHermesStatusChangedToDown;
}
private void HermesStatusToDownFront_OnHermesStatusChangedToDown(object sender, EHermesState e, bool bfrontLane)
{
if (bfrontLane)
{
#region DownStream FrontLane
if (HermesStatusToDownFront.HermesState.Equals(EHermesState.eHERMES_STATE_NOT_CONNECTED))
{
#region Disconnect
// TODO...
#endregion
}
else if (HermesStatusToDownFront.HermesState.Equals(EHermesState.eHERMES_STATE_SOCKET_CONNECTED))
{
#region Connect
// TODO...
#endregion
}
else if (HermesStatusToDownFront.bServiceDescriptionUpstream && HermesStatusToDownFront.bSocketConnected)
{
#region ServiceDescriptionUpstream
// TODO...,send to downstream
#endregion
}
else if (HermesStatusToDownFront.bBoardForecast)
{
#region BoardForecast
BoardForecast boardForecast = new BoardForecast();
string BoardForecastxml = string.Empty;// convert BoardForecast to xml string
SendToDownCmd(BoardForecastxml);
// TODO...
#endregion
}
else if (HermesStatusToDownFront.bBoardAvailable)
{
#region BoardAvailable
// TODO...
#endregion
}
else if (HermesStatusToDownFront.bTransportFinished)
{
#region TransportFinished
// TODO...
#endregion
}
#endregion
}
else
{
#region DownStream RearLane
#endregion
}
}
private void HandleCmdFromDown(object sender, AsyncEventArgs e)
{
string xml = Encoding.UTF8.GetString(e._state.Buffer);
Enum_CMD CmdFromDown = new Enum_CMD();// This value is from xml
switch (CmdFromDown)
{
case Enum_CMD.CheckAlive:
#region CheckAlive
// Send Pong
#endregion
break;
case Enum_CMD.QueryBoardInfo:
#region QueryBoardInfo
SendBoardInfo info = new SendBoardInfo();
///
#endregion
break;
case Enum_CMD.Notification:
#region Notification
// stop the client...
#endregion
break;
case Enum_CMD.ServiceDescription:
#region ServiceDescription
HermesStatusToDownFront.bServiceDescriptionDownstream = true;
#endregion
break;
case Enum_CMD.MachineReady:
#region MachineReady
HermesStatusToDownFront.bMachineReady = true;
#endregion
break;
case Enum_CMD.RevokeMachineReady:
#region MachineReady
HermesStatusToDownFront.bMachineReady = false;
#endregion
break;
case Enum_CMD.StartTransport:
#region StartTransport
HermesStatusToDownFront.bStartTransport = true;
#endregion
break;
case Enum_CMD.StopTransport:
#region StopTransport
HermesStatusToDownFront.bStopTransport = true;
#endregion
break;
case Enum_CMD.Command:
#region Command
#endregion
break;
default:
break;
}
}
4. 与SuperVisory System通信的代码示例
public HermesConnectionVertical(IPEndPoint IPEndPoint)
{
VerticalClient = new AsyncTcpClientHermes(IPEndPoint);
VerticalClient.DatagramReceived += HandleCmdFromVertical;
}
private void HandleCmdFromVertical(object sender, TcpClientWrapper.TcpDatagramReceivedEventArgs<byte[]> e)
{
string xml = Encoding.UTF8.GetString(e.Datagram);
Enum_CMD CmdFromUp = new Enum_CMD();// This value is from xml
switch (CmdFromUp)
{
case Enum_CMD.SendWorkOrderInfo:
#region SendWorkOrderInfo
#endregion
break;
case Enum_CMD.ReplyWorkOrderInfo:
#region ReplyWorkOrderInfo
#endregion
break;
case Enum_CMD.SupervisoryServiceDescription:
#region SupervisoryServiceDescription
// reply
#endregion
break;
default:
break;
}
}
总结
以上就是对Hersmes协议的简单介绍,不过Hermes协议还有很多本文未体现的内容,如具体的消息,消息的参数,范围,不同版本Hermes协议中消息内容也有所不同,异常处理,这里并没有一一列出。因此,在实际项目中,还需要有配置的地方,有些消息的参数是可选的,有效需要执行的操作也是可配置的,这些都要仔细研究协议,将代码写的更加详细。