fix: rebuild MES as compilable go-zero+ent backend (renamed bj_power_mes), restore 3 workstation projects from pristine original, align naming; all Go projects go build clean
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
package processor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"bj_power_mes/constants"
|
||||
"bj_power_mes/ent"
|
||||
"bj_power_mes/ent/workorderpart"
|
||||
"bj_power_mes/internal/cnc"
|
||||
"bj_power_mes/internal/preload"
|
||||
"bj_power_mes/internal/processor/actor"
|
||||
"bj_power_mes/internal/processor/eventloop"
|
||||
"bj_power_mes/internal/processor/station"
|
||||
"bj_power_mes/internal/robot"
|
||||
"bj_power_mes/internal/sse"
|
||||
|
||||
goplc "bjhardman.cn/bjhardman/goplc"
|
||||
)
|
||||
|
||||
// ProductionLine 生产产线聚合(Actor + SignalRouter + TempStoreActor)
|
||||
type ProductionLine struct {
|
||||
Actors map[int]actor.MachineActor
|
||||
Stations map[int]station.Station
|
||||
SignalRouter *actor.SignalRouter
|
||||
TempStore *actor.TempStoreActor
|
||||
}
|
||||
|
||||
// BuildProductionLine 从 DB 设备表构建 Actor 产线。
|
||||
// onDone/onInspect/onSlotFree 回调在 Actor 信号处理后触发,用于投递事件到 EventLoop。
|
||||
func BuildProductionLine(
|
||||
ctx context.Context,
|
||||
entClient *ent.Client,
|
||||
dbState *eventloop.DBState,
|
||||
plc goplc.Client,
|
||||
robotCtrl *robot.Controller,
|
||||
onDone actor.OnMachineEventFunc,
|
||||
onInspect actor.OnInspectionEventFunc,
|
||||
onSlotFree actor.OnSlotFreeFunc,
|
||||
sseHandler *sse.Handler,
|
||||
) (*ProductionLine, error) {
|
||||
doneSignals, ngSignals := buildSignalMaps(ctx, entClient)
|
||||
signalRouter := actor.NewSignalRouter(plc)
|
||||
machineActors := make(map[int]actor.MachineActor)
|
||||
|
||||
equipments, err := entClient.Equipment.Query().WithEquipmentType().All(ctx)
|
||||
if err != nil {
|
||||
slog.Error("factory: query equipment failed, production line cannot start", "error", err)
|
||||
return nil, fmt.Errorf("query equipment: %w", err)
|
||||
}
|
||||
for _, eq := range equipments {
|
||||
isInsp := eq.Edges.EquipmentType.Code == "INSPECTION" || eq.Edges.EquipmentType.Code == "SAMPLING"
|
||||
signalCh := signalRouter.Watch(eq.ID, doneSignals[eq.ID], ngSignals[eq.ID])
|
||||
cfg := actor.MachineActorConfig{
|
||||
ID: eq.ID,
|
||||
Type: string(eq.Edges.EquipmentType.Code),
|
||||
Capacity: eq.SlotCount,
|
||||
Batch: eq.Batch,
|
||||
Inspection: isInsp,
|
||||
SignalCh: signalCh,
|
||||
DB: dbState,
|
||||
OnDone: onDone,
|
||||
OnInspect: onInspect,
|
||||
OnSlotFree: onSlotFree,
|
||||
}
|
||||
a := actor.NewMachineActor(cfg)
|
||||
machineActors[eq.ID] = a
|
||||
go a.Run(ctx)
|
||||
}
|
||||
|
||||
configureActorProductTypes(ctx, entClient, machineActors)
|
||||
|
||||
tempStoreActor := actor.NewTempStoreActor(dbState.TempSlotCapacity(ctx), dbState,
|
||||
actor.WithTempStoreOnChange(func() {
|
||||
sseHandler.Emit(sse.EventTempStationUpdate, "{}")
|
||||
}),
|
||||
)
|
||||
go tempStoreActor.Run(ctx)
|
||||
slog.Info("build production line: done", "actors", len(machineActors))
|
||||
|
||||
// FANUC 通讯管理器:地址从 equipment.ipAddress 读取(格式 "IP:端口"),
|
||||
// 高压清洗机放料前通过 FANUC 写宏变量 #666/#888
|
||||
cncMgr := cnc.NewManager(buildCncAddrMap(ctx, entClient))
|
||||
|
||||
return &ProductionLine{
|
||||
Actors: machineActors,
|
||||
Stations: buildStations(equipments, robotCtrl, cncMgr, washerGroupNoFinder(entClient)),
|
||||
SignalRouter: signalRouter,
|
||||
TempStore: tempStoreActor,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// buildCncAddrMap 从 equipment 表构建 machineID → ipAddress("IP:端口")映射
|
||||
func buildCncAddrMap(ctx context.Context, entClient *ent.Client) map[int]string {
|
||||
addrs := make(map[int]string)
|
||||
equips, err := entClient.Equipment.Query().All(ctx)
|
||||
if err != nil {
|
||||
slog.Warn("factory: failed to query equipment for cnc addresses", "error", err)
|
||||
return addrs
|
||||
}
|
||||
for _, e := range equips {
|
||||
if e.IpAddress != "" {
|
||||
addrs[e.ID] = e.IpAddress
|
||||
}
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
// washerGroupNoFinder jobID → 清洗机组号(工单零件号 → part_no.washerGroupNo)
|
||||
// 链路:job → work_order_id → work_order_part.part_no_id → part_no.washer_group_no
|
||||
func washerGroupNoFinder(entClient *ent.Client) func(ctx context.Context, jobID int) (int, error) {
|
||||
return func(ctx context.Context, jobID int) (int, error) {
|
||||
j, err := entClient.Job.Get(ctx, jobID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("job %d not found: %w", jobID, err)
|
||||
}
|
||||
wop, err := entClient.WorkOrderPart.Query().
|
||||
Where(workorderpart.WorkOrderIdEQ(j.WorkOrderId)).
|
||||
First(ctx)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("work order %d has no part no: %w", j.WorkOrderId, err)
|
||||
}
|
||||
pn, err := entClient.PartNo.Get(ctx, wop.PartNoId)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("part no %d not found: %w", wop.PartNoId, err)
|
||||
}
|
||||
return pn.WasherGroupNo, nil
|
||||
}
|
||||
}
|
||||
|
||||
// buildSignalMaps 从 DB equipment 表 + preload 信号映射构建 machineID→PLC地址
|
||||
func buildSignalMaps(ctx context.Context, entClient *ent.Client) (doneSignals, ngSignals map[int]string) {
|
||||
doneSignals = make(map[int]string)
|
||||
ngSignals = make(map[int]string)
|
||||
|
||||
equips, err := entClient.Equipment.Query().All(ctx)
|
||||
if err != nil {
|
||||
slog.Warn("factory: failed to query equipment for signal maps", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
for _, e := range equips {
|
||||
if e.DoneSignalName != "" {
|
||||
addr := preload.GetMAddress(e.DoneSignalName)
|
||||
if addr.Name != "" {
|
||||
doneSignals[e.ID] = addr.Addr.String()
|
||||
} else {
|
||||
slog.Warn("factory: done signal not found", "equipment", e.Name, "signalName", e.DoneSignalName)
|
||||
}
|
||||
}
|
||||
if e.NgSignalName != "" {
|
||||
addr := preload.GetMAddress(e.NgSignalName)
|
||||
if addr.Name != "" {
|
||||
ngSignals[e.ID] = addr.Addr.String()
|
||||
} else {
|
||||
slog.Warn("factory: ng signal not found", "equipment", e.Name, "signalName", e.NgSignalName)
|
||||
}
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// configureActorProductTypes 从 DB product_type 表配置 CNC 可接受的产品类型
|
||||
func configureActorProductTypes(ctx context.Context, entClient *ent.Client, actors map[int]actor.MachineActor) {
|
||||
productTypes, err := entClient.ProductType.Query().All(ctx)
|
||||
if err != nil {
|
||||
slog.Error("factory: query product types for Actor config failed", "error", err)
|
||||
return
|
||||
}
|
||||
for _, pt := range productTypes {
|
||||
for _, cncID := range pt.CncMachineIds {
|
||||
if a, ok := actors[cncID]; ok {
|
||||
a.AddAllowedProductType(pt.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// buildStations 从设备列表构建 Station map(key=machineID)。
|
||||
// TEMP_STORE 和 ROBOT 类型不创建 Station,返回 nil。
|
||||
func buildStations(equipments []*ent.Equipment, robotCtrl *robot.Controller, cncMgr *cnc.Manager, groupNoFinder func(ctx context.Context, jobID int) (int, error)) map[int]station.Station {
|
||||
stations := make(map[int]station.Station)
|
||||
for _, eq := range equipments {
|
||||
typeCode := constants.EquipmentTypeCode(eq.Edges.EquipmentType.Code)
|
||||
if typeCode == constants.EquipmentTypeCode_TempStore || typeCode == constants.EquipmentTypeCode_Robot {
|
||||
continue
|
||||
}
|
||||
if robotCtrl == nil {
|
||||
continue
|
||||
}
|
||||
var st station.Station
|
||||
if typeCode == constants.EquipmentTypeCode_WashingMachine {
|
||||
// 高压清洗机:放料前先写 FANUC 宏变量 #666/#888(组号来自工单零件号),再发 PLC 命令
|
||||
st = station.NewWasherStation(robotCtrl, eq.ID, &station.FanucOption{
|
||||
WriteMacro: func(ctx context.Context, macroNo int, value float64) error {
|
||||
return cncMgr.WriteMacro(ctx, eq.ID, macroNo, value)
|
||||
},
|
||||
GroupNo: groupNoFinder,
|
||||
})
|
||||
} else {
|
||||
st = station.NewByType(typeCode, eq.ID, robotCtrl)
|
||||
}
|
||||
if st != nil {
|
||||
stations[eq.ID] = st
|
||||
}
|
||||
}
|
||||
slog.Info("factory: built stations", "count", len(stations))
|
||||
return stations
|
||||
}
|
||||
Reference in New Issue
Block a user