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 }