在开始用Processing 做那道水塔考题前, 让我重新想一下: 如果它不是考试, 这个水塔是我的产品, 我会写出什么样的控制要求呢?
我会说, 首先, 除自动上水和停止状态外, 水泵应该有强制运转状态. 在液位开关无法工作 (比如明明开关没有过水触点却有导通) 的情况下, 应该让负责的操作人员能够无视安全条件控制水泵.
然后, 最高液位感知到水面本身就说明次高液位失灵, 它不需要作为特殊条件去停止整个程序, 只要作为次高越位的双保险就可以了. 最低液位没有水只能说明水库已空, 但这无法直接导出需要停泵 (可能会有人认为这说明泵头已经损坏或水库水已空所以绝不能启动泵电机, 但这应该是现场巡视人员的责任) 的逻辑, 所以它只需要用来点亮警报灯, 也不需要参与控制流程.
最后, 对于水塔来说可能的异常状态是过满和过空, 这里明显停泵不及时导致过满所造成的损失会比过空大, 所以液位确认时间不该应用到次高液位开关做停泵动作, 而应该应用到次低液位开关做重启动作.
然后让我来重新考虑一下控制面板的动作: 我只需要一个三档开关, 分别是 禁用/自动/运行 档. 当处于禁用时, 无论什么情况下水泵都不会转动. 当处于运行时, 无论什么情况下水泵都会转动. 当处于自动时, 只要最高液位和次高液位有一个感知到水面, 泵就会停转, 但会在感知到次低液位后过设定秒后再次启动. 无论在什么情况下, 只要感知到水面高于最高液位或低于最低液位, 面板的警报灯就会亮灯. 三档开关可以用三个按钮拼成.
机械上, 水塔里的水和泵和液位开关的关系还是不会变的, 所以之前写的模拟水塔的类的逻辑不需要变动. 真实的工控机编程中会将模拟作为工控程序的一部分编写在工控机里, 运行它和运行主循环程序没有任何区别, 所以模拟代码可以和控制代码一同放到工控机类里. 工控机类里会有几个布尔量表示条件和开关量, 会有一个整形量表示水塔里的水.
另外还需要一个计时功能. 真实的工控机中的计时器一般用毫秒做单位, 虽然Processing 中也可以写一个功能性的类实现它, 但偷懒的我决定用一个更简单的办法来做: 数帧. 帧率乘以秒数就是设定秒数, 简单的计算, 当然结果就是水泵的重启等待时间只能以秒来设定了, 但我觉得对于水塔而言这是无关紧要的. 这样计时器也只需要一个整型量就可以表达了, 非常简单. (简单的闪烁功能也可以通过数帧实现)
现在我有一个工控机类, 它自己做了自己的模拟器. 我还有一个控件类, 它能扩展出一堆子类假装它们自己是一个上位机, 能做操作和设定, 还能显示一个模型! 之后再把控件和工控机关联起来, 这个简陋的模拟Sketch 就算完成了. 关联控件比制作它们简单多了, 它们本身都有值, 需要考虑的只是它对于工控机来说是输入还是输出就可以了: 如果是输入, 就把控件的值代入到工控机里, 如果是输出, 就把工控机的值代入到控件里. 这都不需要写一个类, 只要一个函数就可以了. (出于某些原因, 我本来想给这个函数起名叫link…但最后它变成了zelda…)
所以, 最后完成的代码是这样的:
/* *** *** *** ***
* sz_Reservoir_k
* *** *** *** * */
//--
int pbLeRoller=0;
//--
ArrayList<EcElement> pbHerElementList= new ArrayList();
//--
SxPLC pbThePLC = new SxPLC();
//--
EcFrame pbThisChartFrame = new EcFrame(160,202,"chart",1);
EcFrame pbThisOperateFrame = new EcFrame(135,100,"operate",2);
EcFrame pbThisSettingFrame = new EcFrame(135,100,"setting",3);
//--
EcShapePipe pbThatShapePipe = new EcShapePipe(40,120);
EcShapeHopper pbThatShapeHopper = new EcShapeHopper(80,80);
EcGauge pbThatTankLV = new EcGauge(60,60,'i',106);
EcIconPump pbThatPump = new EcIconPump(22);
EcIconBulb pbThatHighLimitBulb = new EcIconBulb(24,10);
EcIconBulb pbThatHighBulb = new EcIconBulb(24,10);
EcIconBulb pbThatLowBulb = new EcIconBulb(24,10);
EcIconBulb pbThatLowLimitBulb = new EcIconBulb(24,10);
//--
EcButton pbThatDisableSW = new EcButton(48,18,"disab",221);
EcButton pbThatAutoSW = new EcButton(48,18,"auto",222);
EcButton pbThatMannualSW = new EcButton(48,18,"mann",223);
EcLamp pbThatHighAlartPL = new EcLamp(18,"HiALT",231,'r');
EcLamp pbThatLowAlartPL = new EcLamp(18,"LoALT",231,'r');
//--
EcValueBox pbThatTimerTB = new EcValueBox(75,20,"Rstart Timer"," s",321,'a');
EcButton pbThatTimerDecStepper = new EcButton(20,20,"-",331);
EcButton pbThatTimerIncStepper = new EcButton(20,20,"+",332);
//--
void setup(){size(320,240);textAlign(LEFT,TOP);frameRate(16);noStroke();ellipseMode(CENTER);
//--
pbThisChartFrame.ccRepos(null,10,10);pbHerElementList.add(pbThisChartFrame);
pbThisOperateFrame.ccRepos(pbThisChartFrame,2,0);pbHerElementList.add(pbThisOperateFrame);
pbThisSettingFrame.ccRepos(pbThisOperateFrame,0,2);pbHerElementList.add(pbThisSettingFrame);
//--
pbThatShapePipe.ccRepos(pbThisChartFrame,80,40);pbHerElementList.add(pbThatShapePipe);
pbThatShapeHopper.ccRepos(pbThisChartFrame,20,60);pbHerElementList.add(pbThatShapeHopper);
pbThatTankLV.ccRepos(pbThisChartFrame,30,130);pbHerElementList.add(pbThatTankLV);
pbThatPump.ccRepos(pbThatShapePipe,50,120);pbHerElementList.add(pbThatPump);
pbThatHighLimitBulb.ccRepos(pbThatShapeHopper,-4,10);pbHerElementList.add(pbThatHighLimitBulb);
pbThatHighBulb.ccRepos(pbThatHighLimitBulb,0,6);pbHerElementList.add(pbThatHighBulb);
pbThatLowBulb.ccRepos(pbThatHighBulb,0,6);pbHerElementList.add(pbThatLowBulb);
pbThatLowLimitBulb.ccRepos(pbThatLowBulb,0,6);pbHerElementList.add(pbThatLowLimitBulb);
//--
pbThatDisableSW.ccRepos(pbThisOperateFrame,15,30);pbHerElementList.add(pbThatDisableSW);
pbThatAutoSW.ccRepos(pbThatDisableSW,0,1);pbHerElementList.add(pbThatAutoSW);
pbThatMannualSW.ccRepos(pbThatAutoSW,0,1);pbHerElementList.add(pbThatMannualSW);
pbThatHighAlartPL.ccRepos(pbThatAutoSW,5,0);pbHerElementList.add(pbThatHighAlartPL);
pbThatLowAlartPL.ccRepos(pbThatMannualSW,5,0);pbHerElementList.add(pbThatLowAlartPL);
//--
pbThatTimerTB.ccRepos(pbThisSettingFrame,10,50);pbHerElementList.add(pbThatTimerTB);
pbThatTimerDecStepper.ccRepos(pbThatTimerTB,2,0);pbHerElementList.add(pbThatTimerDecStepper);
pbThatTimerIncStepper.ccRepos(pbThatTimerDecStepper,2,0);pbHerElementList.add(pbThatTimerIncStepper);
//--
pbThatDisableSW.cmAct=true;
pbThatTimerTB.cmVal=2;
pbThatTankLV.cmOffColor=color(0x55,0x55,0xBB);
pbThatHighAlartPL.cmOnColor=color(0xEE,0x33,0x33);
pbThatLowAlartPL.cmOnColor=color(0xEE,0x33,0x33);
pbThePLC.ccSetTimerSetting(pbThatTimerTB.cmVal);
//--
}//+++
void draw(){background(0);pbLeRoller++;pbLeRoller&=0x0F;//fill(0xFF);text(nf(pbLeRoller,2),0,0);
//--
for(EcElement it:pbHerElementList){
it.ccUpdate();
}
//--
zeldaRefresh();
//--
}//+++
void keyPressed(){
exit();
}//+++
void mousePressed(){
//--
int lpID=0;
for(int i=pbHerElementList.size()-1;i>=0;i--){
EcElement it=pbHerElementList.get(i);
lpID=it.ccCheckClickedID();
if(lpID!=0){break;}
}
//--
switch(lpID){
//--
case 0:break;
//--
case 221:
pbThatDisableSW.cmAct=true;
pbThatAutoSW.cmAct=false;
pbThatMannualSW.cmAct=false;
break;
//--
case 222:
pbThatDisableSW.cmAct=false;
pbThatAutoSW.cmAct=true;
pbThatMannualSW.cmAct=false;
break;
//--
case 223:
pbThatDisableSW.cmAct=false;
pbThatAutoSW.cmAct=false;
pbThatMannualSW.cmAct=true;
break;
//--
case 331:
pbThatTimerTB.ccShiftValue(-1,0,99);
pbThePLC.ccSetTimerSetting(pbThatTimerTB.cmVal);
break;
//--
case 332:
pbThatTimerTB.ccShiftValue( 1,0,99);
pbThePLC.ccSetTimerSetting(pbThatTimerTB.cmVal);
break;
//--
default:break;
}
//--
}//+++
//-- --- --- --- ---
//-- --- --- --- ---
void zeldaRefresh(){
//--
pbThePLC.ccRefresh(pbLeRoller);
pbThePLC.mnDisableSW=pbThatDisableSW.cmAct;
pbThePLC.mnAutoSW=pbThatAutoSW.cmAct;
pbThePLC.mnMannualSW=pbThatMannualSW.cmAct;
//--
//--
pbThatHighLimitBulb.cmAct=pbThePLC.dcHighLimitLS;
pbThatHighBulb.cmAct=pbThePLC.dcHighLS;
pbThatLowBulb.cmAct=pbThePLC.dcLowLS;
pbThatLowLimitBulb.cmAct=pbThePLC.dcLowLimitLS;
//--
pbThatPump.cmAct=pbThePLC.dcPumpMC;
pbThatTankLV.cmVal=pbThePLC.dcTank;
//--
pbThatHighAlartPL.cmAct=pbThePLC.mnHiAlartPL;
pbThatLowAlartPL.cmAct=pbThePLC.mnLoAlartPL;
//--
}//+++
//-- --- --- --- ---
//-- --- --- --- ---
void fnTextFloat(String pxTitle, float pxVal, int pxLine){
text(pxTitle+nfc(pxVal,2),16,16*pxLine);
}//+++
void fnTextInt(String pxTitle,int pxVal, int pxLine){
text(pxTitle+nf(pxVal,4),16,16*pxLine);
}//+++
//-- --- --- --- ---
//-- --- --- --- ---
class EcElement{
//--
int cmX,cmY,cmW,cmH;
boolean cmAct;
color cmOnColor,cmOffColor;
String cmName;
int cmID;
EcElement(){
cmX=9;cmY=9;cmW=9;cmH=9;
cmAct=false;
cmOnColor=color(0xEE,0xEE,0x77);
cmOffColor=color(0x55,0x55,0x55);
cmName="nc";
cmID=0;
}
//--
void ccUpdate(){
fill(cmAct?cmOnColor:cmOffColor);
rect(cmX,cmY,cmW,cmH);
}
//--
void ccRepos(EcElement pxFollow, int cmOffsetX, int cmOffsetY){boolean lpNull=pxFollow==null;
cmX=(lpNull?0:pxFollow.cmX)+cmOffsetX+(cmOffsetY==0?(lpNull?0:pxFollow.cmW):0);
cmY=(lpNull?0:pxFollow.cmY)+cmOffsetY+(cmOffsetX==0?(lpNull?0:pxFollow.cmH):0);
}
//--
boolean ccIsMouseOver(){return (mouseX>cmX)&&(mouseY>cmY)&&(mouseX<(cmX+cmW))&&(mouseY<(cmY+cmH));}
int ccCheckClickedID(){if(ccIsMouseOver()){return cmID;}else{return 0;}}
//--
}//+++
class EcFrame extends EcElement{
//--
EcFrame(int pxW, int pxH, String pxName, int pxID){
super();
cmW=pxW;cmH=pxH;
cmName=pxName;
cmID=pxID;
}
//--
@Override
void ccUpdate(){
fill(0xEE);rect(cmX,cmY,cmW,cmH);
fill(0x33);rect(cmX+2,cmY+20,cmW-4,cmH-22);
text(cmName,cmX+2,cmY+2);
}
}//+++
class EcButton extends EcElement{
//--
EcButton(int pxW, int pxH, String pxName, int pxID){
super();
cmW=pxW;cmH=pxH;
cmName=pxName;
cmID=pxID;
}
//--
@Override
void ccUpdate(){
fill(0x77);rect(cmX,cmY,cmW,cmH);
stroke(0xDD);fill(ccIsMouseOver()?0xBB:0xAA);
rect(cmX+2,cmY+2,cmW-5,cmH-5);noStroke();
fill(cmAct?cmOnColor:cmOffColor);textAlign(CENTER,CENTER);
text(cmName,cmX+cmW/2,cmY+cmH/2-2);textAlign(LEFT,TOP);
}
//--
}//+++
class EcLamp extends EcElement{
//--
char cmMode;
//--
EcLamp(int pxScale, String pxName, int pxID, char pxMode_ablr){
super();
cmW=pxScale;cmH=pxScale;
cmName=pxName;
cmID=pxID;
cmMode=pxMode_ablr;
cmOnColor=color(0x77,0xFF,0x77);
}
@Override
void ccUpdate(){
fill(0x77);ellipse(cmX+cmW/2,cmY+cmH/2,cmW,cmH);
fill(cmAct?cmOnColor:cmOffColor);ellipse(cmX+cmW/2,cmY+cmH/2,cmW-4,cmH-4);
fill(0xEE);
switch(cmMode){
case 'a':textAlign(CENTER,BOTTOM);text(cmName,cmX+cmW/2, cmY);textAlign(LEFT,TOP);break;
case 'b':textAlign(CENTER,TOP);text(cmName,cmX+cmW+2, cmY+2);textAlign(LEFT,TOP);break;
case 'l':textAlign(RIGHT,TOP);text(cmName,cmX-2, cmY);textAlign(LEFT,TOP);break;
case 'r':text(cmName,cmX+cmW+2, cmY);break;
default:break;
}
}
}//+++
class EcValueBox extends EcElement{
//--
char cmNameMode;
int cmVal;
String cmUnit;
//--
EcValueBox(int pxW,int pxH, String pxName,String pxUnit, int pxID, char pxNameMode_ablr){
super();
cmW=pxW;cmH=pxH;cmName=pxName;cmID=pxID;
cmUnit=pxUnit;cmNameMode=pxNameMode_ablr;
cmVal=0;
}
//--
@Override
void ccUpdate(){
fill(0xAA);rect(cmX,cmY,cmW,cmH);
fill(0x55);rect(cmX+2,cmY+2,cmW-2,cmH-2);
fill(0xCC);textAlign(RIGHT,CENTER);text(ccBoxString()+cmUnit,cmX+cmW-2,cmY+cmH/2);textAlign(LEFT,TOP);
switch(cmNameMode){
case 'a':textAlign(CENTER,BOTTOM);text(cmName,cmX+cmW/2, cmY);textAlign(LEFT,TOP);break;
case 'b':textAlign(CENTER,TOP);text(cmName,cmX+cmW/2,cmY+cmH+2);textAlign(LEFT,TOP);break;
case 'l':textAlign(RIGHT,TOP);text(cmName,cmX-2, cmY);textAlign(LEFT,TOP);break;
case 'r':textAlign(LEFT,CENTER);text(cmName,cmX+cmW+2, cmY+cmH/2);textAlign(LEFT,TOP);break;
default:break;
}
}
//--
String ccBoxString(){return nf(cmVal,4);}
void ccShiftValue(int pxOffset){cmVal+=pxOffset;}
void ccShiftValue(int pxOffset, int pxMin, int pxMax){
ccShiftValue(pxOffset);cmVal=constrain(cmVal,pxMin,pxMax);
}
//--
}//+++
class EcRealBox extends EcValueBox{
float cmOffsetAD,cmOffset;
float cmSpanAD,cmSpan;
boolean cmMoreAccuracy;
EcRealBox(int pxWidth,int pxHeight, String pxName,String pxUnit, int pxID, char pxNameMode_ablr){
super(pxWidth,pxHeight,pxName,pxUnit,pxID,pxNameMode_ablr);
cmOffsetAD=400;cmOffset=0;
cmSpanAD=3600;cmSpan=100;
}
@Override
String ccBoxString(){
return nfc(ccTellRealValue(1),cmMoreAccuracy?2:1);
}
float ccTellRealValue(int pxDivide){
return map(float(cmVal),cmOffsetAD,cmSpanAD,cmOffset,cmSpan)/pxDivide;
}
}//+++
class EcGauge extends EcRealBox{
char cmGaugeMode;
EcGauge(int pxWidth, int pxHeight, char pxGaugeMode_vhiw,int pxID){
super(pxWidth, pxHeight, "nc", "nc",pxID, 'x');
cmGaugeMode=pxGaugeMode_vhiw;
cmOffColor=color(0xCC,0xCC,0x44);
cmOffset=2;
}
@Override
void ccUpdate(){
float lpPt=ccTellRealValue(100);
float lpPtX=1;
float lpPtY=-1;
switch(cmGaugeMode){
case 'i':stroke(0xEE);noFill();rect(cmX,cmY,cmW,-cmH);
case 'v':
lpPtY=-1*lpPt;
break;
case 'w':stroke(0xEE);noFill();rect(cmX,cmY,cmW,-cmH);
case 'h':
lpPtX=lpPt;
break;
default:break;
}
fill(cmAct?cmOnColor:cmOffColor);
rect(cmX,cmY,cmW*lpPtX,cmH*lpPtY);
noStroke();
}
//--
}//+++
class EcIconBulb extends EcElement{
EcIconBulb(int pxWidth, int pxHeight){
super();
cmW=pxWidth;
cmH=pxHeight;
cmOnColor=color(0xAA,0xAA,0xFF);
cmOffColor=0x88;
}
@Override
void ccUpdate(){
fill(cmAct?cmOnColor:cmOffColor);
ellipse(cmX+cmH/2,cmY+cmH/2,cmH,cmH);
rect(cmX+cmH/2,cmY+cmH/2-cmH/8,cmW-cmH/2,cmH/4);
}
}//+++
class EcIconPump extends EcElement{
float cmHeading=0;
EcIconPump(int pxScale){
super();
cmW=pxScale;
cmH=pxScale;
cmOnColor=color(0x77,0x77,0xEE);
cmOffColor=color(0xAA,0xAA,0xAA);
}
@Override
void ccUpdate(){
if(cmAct){cmHeading-=0.5;}if(cmHeading>=PI){cmHeading=0;}
rectMode(CENTER);
fill(0x55);ellipse(cmX,cmY,cmW,cmH);
fill(cmAct?cmOnColor:cmOffColor);ellipse(cmX,cmY,cmW-4,cmH-4);
fill(cmAct?0xFF:0x55);
pushMatrix();
translate(cmX,cmY);rotate(cmHeading);
rect(0,0,cmH-8,4);
popMatrix();
rectMode(CORNER);
}
//--
}//+++
class EcShapePipe extends EcElement{
EcShapePipe(int pxW, int pxH){
super();
cmW=pxW;
cmH=pxH;
cmOffColor=0x66;
}
@Override
void ccUpdate(){
//--
stroke(cmOffColor);strokeWeight(12);
//--
line(cmX,cmY+cmH/4,
cmX,cmY);
line(cmX,cmY,
cmX+3*cmW/4,cmY);
line(cmX+3*cmW/4,cmY,
cmX+3*cmW/4,cmY+cmH);
line(cmX+3*cmW/4,cmY+cmH,
cmX+cmW,cmY+cmH);
//--
noStroke(); strokeWeight(1);
}
//--
}//+++
class EcShapeHopper extends EcElement{
EcShapeHopper(int pxW, int pxH){
super();
cmW=pxW;
cmH=pxH;
cmOffColor=0x66;
}
@Override
void ccUpdate(){
int lpRadius=cmW/4;
//--
fill(cmOffColor);
//--
rect(cmX,cmY,cmW,cmH);
quad(cmX,cmY+cmH,
cmX+cmW,cmY+cmH,
cmX+cmW-lpRadius,cmY+cmH+lpRadius,
cmX+lpRadius,cmY+cmH+lpRadius
);
//--
rect(cmX+lpRadius,cmY+cmH+lpRadius,cmW/2,cmW/3);
//--
}
//--
}//+++
//-- --- --- --- ---
//-- --- --- --- ---
class SxPLC{
//--
boolean mnDisableSW=false;
boolean mnAutoSW=false;
boolean mnMannualSW=false;
boolean mnHiAlartPL=false;
boolean mnLoAlartPL=false;
//--
boolean cmAutoRunFlag=false;
int cmRestartTimer=0;
int cmRestartTimerSet=0;
int cmChargeSpeed=8;
int cmDischargeSpeed=4;
//--
int dcTank=1200;
boolean dcPumpMC=false;
boolean dcHighLimitLS=false;
boolean dcHighLS=false;
boolean dcLowLS=false;
boolean dcLowLimitLS=false;
//--
SxPLC(){;}
void ccRefresh(int pxRoller){ccScan(pxRoller);ccSimulator(pxRoller);}
//--
void ccScan(int pxRoller){
//--
if(!dcLowLS){
cmRestartTimer+=(cmRestartTimer<cmRestartTimerSet?1:0);
}else{cmRestartTimer=0;}
//--
if(dcHighLimitLS || dcHighLS){cmAutoRunFlag=false;}
if(cmRestartTimer>=cmRestartTimerSet){cmAutoRunFlag=true;}
//--
dcPumpMC=(mnMannualSW||(cmAutoRunFlag&&mnAutoSW))&&!mnDisableSW;
mnHiAlartPL=(pxRoller< 7)&& dcHighLimitLS;
mnLoAlartPL=(pxRoller>10)&& !dcLowLimitLS;
//--
}
//--
void ccSimulator(int pxRoller){
//--
if(dcPumpMC){dcTank+=cmChargeSpeed;}
dcTank-=cmDischargeSpeed;
dcTank=constrain(dcTank,400,3600);
//--
dcHighLimitLS=(dcTank>=3300);
dcHighLS=(dcTank>=2500);
dcLowLS=(dcTank>=1600);
dcLowLimitLS=(dcTank>=800);
//--
}
//--
void ccSetTimerSetting(int pxSec){
cmRestartTimerSet=pxSec*16;if(cmRestartTimerSet<2){cmRestartTimerSet=2;}
}
//--
}//+++
- 本文固定链接: http://iprocessing.cn/2017/09/22/模拟工控机的模拟5模拟工控机模拟水塔水位控制/
- 转载请注明: constrain 于 Processing编程艺术 发表