我们在第一期内测的时候,有朋友也提到生成程序和代码前需要plan规划下,这几天时间针对这点做了重大更新,新增了PlanAgent,专门处理用户的输入请求。
当然,思路也很简单,跟目前所有的AI进行vibe coding前,最好使用Plan模式进行规划,反复几轮对话以后,确定好plan规划,再来执行,一般比走一步看一步再优化这种方式,既准确,而且相对来说也更加节约tokens。

所以,我们这期来简单概括下,目前代码还在优化,会放到我们第二期内测中,敬请期待!
01

PLAN模式
关于plan模式实际上,各个AI工具基本上都已经内置了,甚至都不用去某个选项里特意去配置,只需要提问的时候带上这个词,在比较复杂的应用里面也会生成plan。

从我们真正使用和体验上来看,plan模式也需要先通过大量的上下文及需求询问后,才能得到比较好的PLAN计划。
当然,我们在vibe coding当前的RealPLC产品时候,也发现不仅仅有plan文档,而且还多了spec文档,格式都是md。

也就是说,在Plan以前还多了一步spec,也就是详细的细节和设计需求和你讨论清楚后,再来执行计划。根据计划再更新todolist:

02

需求分析
实际上,我们自己在编程之前,也会详细的了解客户设备的选型清单,输入输出的具体类型,输出是变频还是伺服等等。这些信息没有,根本没办法下手。
所以,我们在生成代码之前就加入需求分析这一步,可以让AI了解系统具体需求之后,再来推理,生成代码。

为了方便交互和快速推进,我们改善了问题回答的交互方式,直接通过选项选择具体的问题答案。通过这些问题交流后,AI才知道代码方向在哪里。
03

比较
我们为了方便比较,把第一期的生成效果,和当前的效果做个对比。第一期已经在我们网站www.realplc.com,所以直接登录访问:

选择最简单的专家模板,输送带的控制为例,这样两者提示词一模一样,我们直接把这两次的具体代码贴在下方。
第一期初始代码:
VAR
// 输入变量
xStartButton: BOOL;// 启动按钮
xStopButton: BOOL;// 急停按钮
xSensor: BOOL;// 传感器信号
xSensorFault: BOOL;// 传感器故障
xGrid: BOOL;// 输送机安全栅
// 状态变量
xRun: BOOL;// 输送机运行状态
xStartCmd: BOOL;// 启动命令标志
xStopCmd: BOOL;// 停止命令标志
bStartPress: BOOL;// 启动按钮按下标志
bStopHold: BOOL;// 急停锁定标志
// 定时器
tSensorTimeout: TON;
tSensorFaultCheck: TON;
// 常量
CONST
gSensorTimeout: TIME := T#10s;// 传感器超时时间
gFaultCheckInterval: TIME := T#1s;// 故障检测间隔
END_VAR
//--------------------------------------------------------------------//
// ========================
// 输入处理
// ========================
// 输入消抖
xStartButton := xStartButton OR NOT bStartPress;
xStopButton := xStopButton AND bStopHold;
// 启动按钮处理
CASE bStartPress
OFTRUE:
xStartCmd :=TRUE;
bStartPress :=FALSE;
ELSE
xStartCmd :=FALSE;
END_CASE;
// 急停按钮处理
IF xStopButton THEN
bStopHold :=TRUE;
xStopCmd :=TRUE;
ELSE
IF NOT xStopButton THEN
bStopHold :=FALSE;
xStopCmd :=FALSE;
END_IF;
END_IF;
// ========================
// 故障检测
// ========================
// 检测传感器故障
IF xSensorFault THEN
tSensorFaultCheck(Q :=TRUE, PT := gFaultCheckInterval);
xSensor :=FALSE;// 优先级高故障时强制置为FALSE
ELSE
tSensorFaultCheck(Q :=FALSE);
END_IF;
// 传感器超时检测
tSensorTimeout(Q := xSensor AND NOT xSensorFault, PT := gSensorTimeout);
// ========================
// 状态机逻辑
// ========================
// 急停优先级最高
IF xStopCmd THEN
xRun :=FALSE;
ELSE
// 启动条件判断
IF xStartCmd THEN
xRun := xGrid AND xSensor;
ELSE
xRun :=FALSE;
END_IF;
END_IF;
// 运行控制
IF xRun THEN
// 启动PLC运行
xRun :=TRUE;
ELSE
// 停止PLC运行
xRun :=FALSE;
END_IF;
// ========================
// 输出控制
// ========================
TONMotor(
Q := xRun,
IN := xRun AND xSensor,// 冲程检测
PT := gCycleTime
);
// 输出保护
IF NOT xGrid THEN
xRun :=FALSE;// 安全栅断开时停止
END_IF;经过Plan之后优化的代码:
(*============================================================*)
(* 数据类型定义 *)
(*============================================================*)
TYPE
E_ConveyorState :
(
Idle,(* 1. 空闲状态 *)
Starting,(* 2. 启动阶段 *)
Running,(* 3. 运行阶段 *)
Stopping,(* 4. 停止阶段 *)
Fault (* 5. 故障状态 *)
);
ST_FaultRec :
STRUCT
dtFault : DATE_AND_TIME;(* 故障时间戳 *)
sType :STRING[20];(* 故障类型 *)
END_STRUCT;
END_TYPE
(*============================================================*)
(* 功能块:输送线控制 *)
(*============================================================*)
FUNCTION_BLOCK FB_ConveyorControl
VAR_INPUT
xStartBtn : BOOL;(* 启动按钮 (干触点) *)
xStopBtn : BOOL;(* 停止按钮 (干触点) *)
xEStop : BOOL;(* 急停按钮 (硬件联锁) *)
xOverTemp : BOOL;(* 超温传感器 (干触点) *)
xFaultReset : BOOL;(* 故障复位按钮 *)
bAuxReady : BOOL;(* 辅助设备就绪信号 *)
rSpeedSet : REAL;(* 手动电位器设定速度 0~50Hz *)
rTemperature: REAL;(* 温度值,用于条件联锁 *)
END_VAR
VAR_OUTPUT
xVFDEnable : BOOL;(* 变频器启停信号 *)
rVFDSpeed : REAL;(* 变频器速度设定 *)
xFault : BOOL;(* 故障指示 *)
sFaultCode :STRING[20];(* 故障描述 *)
sFaultTime :STRING[30];(* 故障时间字符串 *)
END_VAR
VAR
(* 状态机 *)
eState : E_ConveyorState := E_ConveyorState.Idle;
(* 边沿检测 *)
rtrigStartBtn : R_TRIG;
rtrigStopBtn : R_TRIG;
rtrigFaultReset : R_TRIG;
(* 计时器 *)
tonStopDelay : TON;
tonOverTempClear: TON;
(* 速度输出 *)
rSpeedOut : REAL;
(* 故障历史 *)
arrFaultHistory :ARRAY[0..gc_iMaxFaultHistory-1]OF ST_FaultRec;
iFaultIndex : INT :=0;
(* 变量 *)
xFirstScan : BOOL :=TRUE;
xFaultPrev : BOOL :=FALSE;
END_VAR
VAR_CONSTANT
gc_iMaxFaultHistory : INT :=10;(* 故障历史记录最大数量 *)
gc_tStopDelay : TIME := T#1s;(* 停止延迟 *)
gc_tOverTempClearDelay: TIME := T#5s;(* 超温恢复延迟 *)
gc_tStartDelay : TIME := T#1s;(* 启动延迟(可选) *)
END_VAR
(*------------------------------------------------------------*)
(* 1. 首扫初始化 *)
(*------------------------------------------------------------*)
IF xFirstScan THEN
(* 输出安全状态 *)
xVFDEnable :=FALSE;
rVFDSpeed :=0.0;
xFault :=FALSE;
sFaultCode :='';
sFaultTime :='';
(* 计时器重置 *)
tonStopDelay(IN:=FALSE, PT:=gc_tStopDelay);
tonOverTempClear(IN:=FALSE, PT:=gc_tOverTempClearDelay);
(* 边沿检测重置 *)
rtrigStartBtn(IN:=FALSE);
rtrigStopBtn(IN:=FALSE);
rtrigFaultReset(IN:=FALSE);
(* 计数器重置 *)
iFaultIndex :=0;
xFirstScan :=FALSE;
END_IF
(*------------------------------------------------------------*)
(* 2. 边沿检测 *)
(*------------------------------------------------------------*)
rtrigStartBtn(CLK:=xStartBtn);
rtrigStopBtn(CLK:=xStopBtn);
rtrigFaultReset(CLK:=xFaultReset);
(*------------------------------------------------------------*)
(* 3. 计时器调用(无条件) *)
(*------------------------------------------------------------*)
tonStopDelay(IN:=rtrigStopBtn.Q, PT:=gc_tStopDelay);
tonOverTempClear(IN:=NOT xOverTemp, PT:=gc_tOverTempClearDelay);
(*------------------------------------------------------------*)
(* 4. 速度限幅 *)
(*------------------------------------------------------------*)
rSpeedOut := FC_Clamp(rSpeedSet,0.0,50.0);
(*------------------------------------------------------------*)
(* 5. 故障检测(急停 > 超温) *)
(*------------------------------------------------------------*)
IF xEStop THEN
xFault :=TRUE;
sFaultCode :='EStop';
ELSIF xOverTemp THEN
xFault :=TRUE;
sFaultCode :='OverTemp';
ELSIF tonOverTempClear.Q THEN
(* 超温恢复后清除故障 *)
xFault :=FALSE;
END_IF
(*------------------------------------------------------------*)
(* 6. 故障历史记录(首次检测时记录) *)
(*------------------------------------------------------------*)
IF xFault AND NOT xFaultPrev THEN
(* 记录时间戳与类型 *)
arrFaultHistory[iFaultIndex].dtFault := DATE_AND_TIME();
arrFaultHistory[iFaultIndex].sType := sFaultCode;
(* 记录时间字符串 *)
sFaultTime := DATE_AND_TIME_TO_STRING(arrFaultHistory[iFaultIndex].dtFault);
(* 循环写入 *)
iFaultIndex :=(iFaultIndex +1) MOD gc_iMaxFaultHistory;
END_IF
xFaultPrev := xFault;
(*------------------------------------------------------------*)
(* 7. 状态机 *)
(*------------------------------------------------------------*)
CASE eState OF
E_ConveyorState.Idle:
IF NOT xEStop AND NOT xOverTemp AND NOT xFault AND rtrigStartBtn.Q AND bAuxReady THEN
eState := E_ConveyorState.Starting;
END_IF
E_ConveyorState.Starting:
(* 先等待辅助设备就绪后再启用 VFD *)
IF bAuxReady THEN
xVFDEnable :=TRUE;
rVFDSpeed := rSpeedOut;
eState := E_ConveyorState.Running;
END_IF
E_ConveyorState.Running:
IF rtrigStopBtn.Q OR xEStop OR xOverTemp THEN
eState := E_ConveyorState.Stopping;
END_IF
E_ConveyorState.Stopping:
(* 等待停止延迟后关闭 VFD *)
IF tonStopDelay.Q THEN
xVFDEnable :=FALSE;
rVFDSpeed :=0.0;
eState := E_ConveyorState.Idle;
END_IF
E_ConveyorState.Fault:
(* 故障时保持 VFD 关闭 *)
xVFDEnable :=FALSE;
rVFDSpeed :=0.0;
(* 等待复位按钮 *)
IF rtrigFaultReset.Q THEN
xFault :=FALSE;
eState := E_ConveyorState.Idle;
END_IF
ELSE
(* 防止意外状态,安全退回 Idle *)
eState := E_ConveyorState.Idle;
END_CASE
(* 结束状态机 *)
(*------------------------------------------------------------*)
(* 8. 输出映射(已在状态机内完成) *)
(*------------------------------------------------------------*)
END_FUNCTION_BLOCK
(*============================================================*)
(* 主程序:IO 映射与 FB 调用 *)
(*============================================================*)
PROGRAM PRG_Main
VAR
(* 实例 *)
fbConveyor : FB_ConveyorControl;
(* 输入变量(示例,实际映射到硬件 IO) *)
xStartBtn : BOOL;(* 启动按钮 *)
xStopBtn : BOOL;(* 停止按钮 *)
xEStop : BOOL;(* 急停按钮 *)
xOverTemp : BOOL;(* 超温传感器 *)
xFaultReset : BOOL;(* 故障复位按钮 *)
bAuxReady : BOOL;(* 辅助设备就绪 *)
rSpeedSet : REAL;(* 手动电位器设定速度 0~50Hz *)
rTemperature: REAL;(* 温度值,供条件联锁使用 *)
(* 输出变量 *)
xVFDEnable : BOOL;(* VFD 开启信号 *)
rVFDSpeed : REAL;(* VFD 速度设定 *)
xFault : BOOL;(* 故障指示 *)
sFaultCode :STRING[20];(* 故障描述 *)
sFaultTime :STRING[30];(* 故障时间字符串 *)
END_VAR
//-------------------------------------------------------------------------------------//
(* 读取硬件 IO(示例,需根据实际 IO 名称进行替换) *)
(* 这里仅作演示,实际项目请使用系统 IO 映射或 HMI 读取 *)
(* 赋值给 FB 输入 *)
fbConveyor.xStartBtn := xStartBtn;
fbConveyor.xStopBtn := xStopBtn;
fbConveyor.xEStop := xEStop;
fbConveyor.xOverTemp := xOverTemp;
fbConveyor.xFaultReset := xFaultReset;
fbConveyor.bAuxReady := bAuxReady;
fbConveyor.rSpeedSet := rSpeedSet;
fbConveyor.rTemperature := rTemperature;
(* 调用 FB *)
fbConveyor();
(* 将 FB 输出映射到硬件 IO 或上位系统 *)
xVFDEnable := fbConveyor.xVFDEnable;
rVFDSpeed := fbConveyor.rVFDSpeed;
xFault := fbConveyor.xFault;
sFaultCode := fbConveyor.sFaultCode;
sFaultTime := fbConveyor.sFaultTime;
END_PROGRAM
当然,除了plan Agent外,代码的生成规则也做了优化,从代码的水平及具体的质量看,确实有了巨大提升。
参考链接:
【1】https://www.realplc.com