package tightening import ( "context" "encoding/json" "fmt" "math" "math/rand/v2" "net/url" "time" "bj_power_workstation/internal/mes" "bj_power_workstation/internal/store" "github.com/zeromicro/go-zero/core/logx" ) // Source 拧紧枪数据源接口。 type Source interface { Start(ctx context.Context) } // MockSource 内置模拟拧紧枪数据源:每 6 秒生成一条拧紧结果插入本地 torque_results(synced=0)。 // SN 从最近工单任务中获取;拿不到则用 "MOCK-"+时间戳。 type MockSource struct { db *store.Store mes *mes.Client dockCode string // 模拟枪挂载的工位 } // NewMockSource 模拟枪挂载在固定工位(stationNo 1~12),DOCK 编码为 DOCK01~DOCK12。 func NewMockSource(db *store.Store, mc *mes.Client, stationNo int) *MockSource { dockCode := fmt.Sprintf("DOCK%02d", stationNo) return &MockSource{db: db, mes: mc, dockCode: dockCode} } func (m *MockSource) Start(ctx context.Context) { logx.Infof("模拟拧紧枪已启动:每 6 秒采集一条,工位 %s", m.dockCode) ticker := time.NewTicker(6 * time.Second) defer ticker.Stop() for { select { case <-ctx.Done(): logx.Info("模拟拧紧枪已停止") return case <-ticker.C: m.produceOne() } } } func (m *MockSource) produceOne() { sn, orderNo := m.fetchRecentTask() if sn == "" { sn = "MOCK-" + time.Now().Format("20060102150405") orderNo = "MOCK-ORDER" } torque := round1(40 + rand.Float64()*12) // 40 ~ 52 Nm angle := round0(100 + rand.Float64()*40) // 100 ~ 140° screwNo := fmt.Sprintf("S%d", rand.IntN(10)+1) // S1 ~ S10 result := "OK" // OK 概率 90% if rand.Float64() >= 0.9 { result = "NG" } _, err := m.db.InsertTorqueResult(store.TorqueResult{ WorkOrderNo: orderNo, Sn: sn, DockCode: m.dockCode, ScrewNo: screwNo, Torque: torque, Angle: angle, Result: result, Operator: "auto", // 终端自动采集 }) if err != nil { logx.Errorf("模拟拧紧结果入库失败: %v", err) return } logx.Infof("[模拟拧紧] sn=%s 工位=%s 螺丝=%s 扭矩=%.1fNm 角度=%.0f° 结果=%s", sn, m.dockCode, screwNo, torque, angle, result) } // fetchRecentTask 从 MES 拉取当前工位最近任务,宽松解析其中的 sn/orderNo 字段, // 任一环节失败均静默返回空串(保证离线时继续生成 MOCK 数据)。 func (m *MockSource) fetchRecentTask() (sn string, orderNo string) { stationNo := 0 for _, ch := range m.dockCode { if ch < '0' || ch > '9' { stationNo = 0 break } stationNo = stationNo*10 + int(ch-'0') } query := url.Values{} if stationNo > 0 { query.Set("stationNo", fmt.Sprintf("%d", stationNo)) } body, _, err := m.mes.Get("/api/internal/station/task", query) if err != nil { return "", "" } var wrap map[string]any if json.Unmarshal(body, &wrap) != nil { return "", "" } data, ok := wrap["data"].(map[string]any) if !ok { data = wrap // 兼容裸 JSON 响应 } s, _ := data["sn"].(string) o, _ := data["orderNo"].(string) return s, o } func round1(v float64) float64 { return math.Round(v*10) / 10 } func round0(v float64) float64 { return math.Round(v) } // NetSource 丹尼科尔拧紧枪网络数据源占位。 // TODO 正式协议暂放:待拿到丹尼科尔协议文档后在此实现 TCP/UDP 报文解析。 type NetSource struct{} var _ Source = (*NetSource)(nil) func (n *NetSource) Start(ctx context.Context) { <-ctx.Done() }