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

211 lines
7.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}