- MES(B): 工单/BOM/备料/工序字典/PLC下发/拧紧/扫码报工/半成品/AGV/追溯逻辑,WMS与海康RCS客户端,看板Redis缓存API(概览/设备/进度/报警/趋势)+SSE - WMS(C): JWT滑动续签、Excel导入、盘点、内部API、种子数据、独立Postgres配置 - WMS客户端(E): Go网关8891反向代理+内嵌Vue3十页 - 工位终端(D): SQLite本地缓存+模拟拧紧源+内嵌Vue3页面 - Dashboard(A): 看板数据改接MES内部缓存API,SSE实时刷新+vite代理 - 清理各项目球形磨遗留代码,新增部署手册.md
140 lines
3.4 KiB
Go
140 lines
3.4 KiB
Go
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
|
|
}
|