Files
bj_power/bj_power_mes/internal/processor/factory.go
T

211 lines
7.0 KiB
Go
Raw Normal View History

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 mapkey=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
}