对于编写工业程序来说,大多数时候我们首先保证的是安全和稳定,试想一个几吨的大型机床程序出了问题,可能一个数百万的零件就此报废,甚至危害加工人员的人身安全。本文就在PMAC中如何构造足够安全的工业设备程序给出一点实用方法。
1.状态_迁移图
先想想对于程序来说,怎样才算足够安全?对于每一种可能出现的操作都事先考虑到并给出对应的处理方案,这样的程序就是足够安全的。那么我们首先要做的就是列举每种可能出现的问题,其实前人们早就考虑到这个问题了,人们提出一个“状态_迁移图”的概念,用来描述程序不同的状态和状态之间的跳变过程,如下图对于如下操作界面的程序:
这里为了简便,仅考虑程序运行的状态,可以列表如下:
程序包括三种状态:运行状态、暂停状态和停止状态。不同的状态间跳变,通过点击下方的运行、暂停、继续和停止按钮来完成。
上方的箭头标明不同的转态之间跳变关系,中间为完成某一个跳变必须完成的事件(点击的按钮)。
下方的按钮灰色表示当前按钮不能点击、绿色表示当前按钮能够点击。按钮能不能点击其实就是标明当前状态能否跳变到对应的点击后状态,即上方箭头标明的跳变图和下方灰色和绿色标明的按钮状态列表是等价的。
2.编写安全程序方法
那么,我们编写安全程序就可以分别按照如下两种方法:
1.跳变图:当点击某一个按钮时,检查当期的状态能不能跳变到点击后的状态,如果不能则当前点击无效,避免误操作,对应上图的箭头跳变图。这种方法需要一个变量来记录当前的状态,根据不同的点击实时更新当前状态。
2.状态列表:在某一状态下,不能操作的按钮全部灰度化和禁用,只开放可发生状态跳变的按钮供用户点击,对应上图的按钮状态列表。这种方法需要在状态跳变后实时更改当前按钮状态。
3.代码演示
方法1.跳变图
void CTest2Dlg::OnRun()
{
TCHAR szRes[MAX_PATH];
TCHAR szCmd[MAX_PATH];
lstrcpy(szCmd, "&1b40r");
//判断跳变是否有效,进行相关操作和更新状态
if (MOVE_STATE_STOP == m_moveState)
{
PmacGetResponse(0,szRes,MAX_PATH,szCmd);
m_moveState = MOVE_STATE_RUN;
}
else
{
AfxMessageBox(TEXT("当前不能进行该操作!"));
}
}
void CTest2Dlg::OnPause()
{
TCHAR szRes[MAX_PATH];
TCHAR szCmd[MAX_PATH];
lstrcpy(szCmd, "&1b40h");
//判断跳变是否有效,进行相关操作和更新状态
if (MOVE_STATE_RUN == m_moveState)
{
PmacGetResponse(0,szRes,MAX_PATH,szCmd);
m_moveState = MOVE_STATE_PAUSE;
}
else
{
AfxMessageBox(TEXT("当前不能进行该操作!"));
}
}
void CTest2Dlg::OnContinue()
{
TCHAR szRes[MAX_PATH];
TCHAR szCmd[MAX_PATH];
lstrcpy(szCmd, "&1b40r");
//判断跳变是否有效,进行相关操作和更新状态
if (MOVE_STATE_PAUSE == m_moveState)
{
PmacGetResponse(0,szRes,MAX_PATH,szCmd);
m_moveState = MOVE_STATE_RUN;
}
else
{
AfxMessageBox(TEXT("当前不能进行该操作!"));
}
}
void CTest2Dlg::OnStop()
{
TCHAR szRes[MAX_PATH];
TCHAR szCmd[MAX_PATH];
lstrcpy(szCmd, "&1b40a");
//判断跳变是否有效,进行相关操作和更新状态
if (MOVE_STATE_RUN==m_moveState || MOVE_STATE_PAUSE==m_moveState)
{
PmacGetResponse(0,szRes,MAX_PATH,szCmd);
m_moveState = MOVE_STATE_STOP;
}
else
{
AfxMessageBox(TEXT("当前不能进行该操作!"));
}
}
方法2.状态列表
void CTest2Dlg::OnRun()
{
TCHAR szRes[MAX_PATH];
TCHAR szCmd[MAX_PATH];
lstrcpy(szCmd, "&1b40r");
PmacGetResponse(0,szRes,MAX_PATH,szCmd);
//更新状态,仅开放当前能操作的按钮
GetDlgItem(IDB_RUN)->EnableWindow(FALSE);
GetDlgItem(IDB_PAUSE)->EnableWindow(TRUE);
GetDlgItem(IDB_CONTINUE)->EnableWindow(FALSE);
GetDlgItem(IDB_STOP)->EnableWindow(TRUE);
}
void CTest2Dlg::OnPause()
{
TCHAR szRes[MAX_PATH];
TCHAR szCmd[MAX_PATH];
lstrcpy(szCmd, "&1b40h");
PmacGetResponse(0,szRes,MAX_PATH,szCmd);
//更新状态,仅开放当前能操作的按钮
GetDlgItem(IDB_RUN)->EnableWindow(FALSE);
GetDlgItem(IDB_PAUSE)->EnableWindow(FALSE);
GetDlgItem(IDB_CONTINUE)->EnableWindow(TRUE);
GetDlgItem(IDB_STOP)->EnableWindow(TRUE);
}
void CTest2Dlg::OnContinue()
{
TCHAR szRes[MAX_PATH];
TCHAR szCmd[MAX_PATH];
lstrcpy(szCmd, "&1b40r");
PmacGetResponse(0,szRes,MAX_PATH,szCmd);
//更新状态,仅开放当前能操作的按钮
GetDlgItem(IDB_RUN)->EnableWindow(FALSE);
GetDlgItem(IDB_PAUSE)->EnableWindow(TRUE);
GetDlgItem(IDB_CONTINUE)->EnableWindow(FALSE);
GetDlgItem(IDB_STOP)->EnableWindow(TRUE);
}
void CTest2Dlg::OnStop()
{
TCHAR szRes[MAX_PATH];
TCHAR szCmd[MAX_PATH];
lstrcpy(szCmd, "&1b40a");
PmacGetResponse(0,szRes,MAX_PATH,szCmd);
//更新状态,仅开放当前能操作的按钮
GetDlgItem(IDB_RUN)->EnableWindow(TRUE);
GetDlgItem(IDB_PAUSE)->EnableWindow(FALSE);
GetDlgItem(IDB_CONTINUE)->EnableWindow(FALSE);
GetDlgItem(IDB_STOP)->EnableWindow(FALSE);
}
可以看到,方法1相对方法2,可以多了一个状态记录变量m_moveState,可以随时查看当前状态,但是用户操作不能第一时间知道当前操作是否有效, 必须点击后才会知道当前操作能不能进行。方法2, 每一时刻用户可以进行的操作都很清楚,当时不能实时查看当前状态。
通常,我们使用第二种方法,当需要查询状态时,结合方法1,使用一个变量记录当前状态即可。
这里讲的都是在上位机改变状态,只有用户点击后程序状态才会改变,但是在实际中,当程序运行完毕(如这里的回零按钮实际上也应该加入到状态控制中,开始点击后程序为回零状态,所有按钮都必须灰度化,等待回零完毕后自动改变程序状态为运行状态)或特定动作发生(某一行为触发指定状态改变),程序的状态也会发生改变,如何捕获这一状态改变呢?这是后续中断响应需要讲的内容。
演示源代码下载链接
原创,转载请注明来自http://blog.csdn.net/wenzhou1219