Files
bj_power/bj_power_mes/internal/plcproc/process.go
T

140 lines
3.4 KiB
Go
Raw Normal View History

2026-08-27 19:50:57 +08:00
package plcproc
import (
"context"
"fmt"
"log/slog"
"time"
"bj_power_mes/ent"
"bj_power_mes/internal/svc"
goplc "bjhardman.cn/bjhardman/goplc"
)
/*
PLC 工序号下发(S7-1214)。
业务逻辑确定:产线共 12 道工序;将收齐的工序号拼成整型下发,
例:下发 125 → PLC 执行 1 序、2 序、5 序。
信号地址未定义前先用默认约定(配置可改):
工序号写入:DB100.DBW0 (uint16)
完成位读取:M100.0 (收齐后清复位)
Mock.Enable 或 PLC 未连接时自动进入模拟模式,SimDoneSeconds 秒后确认完成。
*/
// SendProcess 下发工序号并返回日志记录
func SendProcess(ctx *svc.ServiceContext, orderNo, codeStr string) (*ent.PlcSendLog, error) {
if codeStr == "" {
return nil, fmt.Errorf("codeStr 必填,如 125 表示执行1序2序5序")
}
// 校验每一位都是 1~12 且不重复
for _, ch := range codeStr {
d := int(ch - '0')
if d < 1 || d > 12 {
return nil, fmt.Errorf("工序号 %c 超出范围(1~12)", ch)
}
}
if len(codeStr) > 12 {
return nil, fmt.Errorf("工序号数量超过 12 道")
}
mode := "real"
result := "已下发"
var errText string
simMode := ctx.Config.Mock.Enable || !ctx.PLCManager.IsConnected()
if simMode {
mode = "sim"
} else {
plc := ctx.PLCManager.GetPlc()
if plc == nil {
return nil, fmt.Errorf("PLC 客户端不可用")
}
var code uint16
fmt.Sscanf(codeStr, "%d", &code)
if err := plc.WriteUint16(ctx.Config.Plc.ProcessAddr, code); err != nil {
return nil, fmt.Errorf("写 PLC 失败: %w", err)
}
}
rec := ctx.EntClient.PlcSendLog.Create().
SetCodeStr(codeStr).
SetMode(mode).
SetResult(result)
if orderNo != "" {
rec.SetOrderNo(orderNo)
}
if errText != "" {
rec.SetErrorText(errText)
}
saveRec, err := rec.Save(context.Background())
if err != nil {
return nil, err
}
// 模拟模式:延时后确认完成;真实模式:轮询完成位
go waitAck(ctx, saveRec.ID, mode, plcRefOf(simMode, ctx))
slog.Info("plc process sent", "order", orderNo, "code", codeStr, "mode", mode)
return saveRec, nil
}
// plcRefOf 真实模式下返回 PLC 引用供后台轮询
type plcHolder struct{ p goplc.Client }
func plcRefOf(sim bool, ctx *svc.ServiceContext) any {
if sim {
return nil
}
return &plcHolder{p: ctx.PLCManager.GetPlc()}
}
// waitAck 后台等待 PLC 完成位
func waitAck(ctx *svc.ServiceContext, logID int, mode string, holder any) {
cfg := ctx.Config.Plc
done := false
switch h := holder.(type) {
case nil:
time.Sleep(time.Duration(cfg.SimDoneSeconds) * time.Second)
done = true
case *plcHolder:
if h.p != nil && h.p.IsConnected() {
deadline := time.Now().Add(time.Duration(cfg.SimDoneSeconds+120) * time.Second)
for time.Now().Before(deadline) {
b, err := h.p.ReadBool(cfg.DoneBitAddr)
if err == nil && b {
done = true
_ = h.p.WriteBool(cfg.DoneBitAddr, false) // 收到后清复位
break
}
time.Sleep(500 * time.Millisecond)
}
}
}
now := time.Now()
if done {
ctx.EntClient.PlcSendLog.UpdateOneID(logID).
SetResult("已确认").
SetAckAt(now).
ExecX(context.Background())
} else if mode != "sim" {
ctx.EntClient.PlcSendLog.UpdateOneID(logID).
SetResult("超时未确认").
ExecX(context.Background())
} else {
ctx.EntClient.PlcSendLog.UpdateOneID(logID).
SetResult("已确认").
SetAckAt(now).
ExecX(context.Background())
}
}
func nillable(s string) *string {
if s == "" {
return nil
}
return &s
}