| | |
| | | using Serilog; |
| | | using WIDESEAWCS_QuartzJob; |
| | | |
| | | namespace WIDESEAWCS_Tasks |
| | | { |
| | | /// <summary> |
| | | /// 输送线目标地址选择器 - 处理拘束机/插拔钉机的上下层请求 |
| | | /// 输送线目标地址选择器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 核心职责: |
| | | /// 1. 处理入库场景的目标地址选择 |
| | | /// 2. 处理出库场景的目标地址选择 |
| | | /// 3. 判断拘束机和插拔钉机的物料请求状态 |
| | | /// 4. 协调输送线与上下层设备之间的物料流转 |
| | | /// |
| | | /// 拘束机和插拔钉机都有上下两层结构, |
| | | /// 每层都有独立的物料请求和出料信号,需要分别处理。 |
| | | /// </remarks> |
| | | public class ConveyorLineTargetAddressSelector |
| | | { |
| | | /// <summary> |
| | | /// 拘束机名称常量 |
| | | /// </summary> |
| | | private const string ConstraintMachineName = "拘束机"; |
| | | |
| | | /// <summary> |
| | | /// 插拔钉机名称常量 |
| | | /// </summary> |
| | | private const string PinMachineName = "插拔钉机"; |
| | | |
| | | /// <summary> |
| | | /// 拘束机对应的点位编码列表 |
| | | /// 日志记录器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 当目标地址在这些编码中时,表示需要与拘束机交互。 |
| | | /// 通过 Microsoft.Extensions.Logging 接口注入,用于结构化日志输出。 |
| | | /// </remarks> |
| | | private static readonly List<string> ConstraintMachineCodes = new List<string> { "10180", "20090" }; |
| | | private readonly ILogger _logger; |
| | | |
| | | /// <summary> |
| | | /// 插拔钉机对应的点位编码列表 |
| | | /// 构造函数 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 当目标地址在这些编码中时,表示需要与插拔钉机交互。 |
| | | /// </remarks> |
| | | private static readonly List<string> PinMachineCodes = new List<string> { "10190", "20100" }; |
| | | /// <param name="logger">日志记录器,由依赖注入容器自动注入</param> |
| | | public ConveyorLineTargetAddressSelector(ILogger logger) |
| | | { |
| | | _logger = logger; // 保存日志记录器实例,供后续方法使用 |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理入库场景的下一地址请求 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 当入库任务执行到某个位置时调用此方法。 |
| | | /// 判断目标设备是否需要物料或可以出料。 |
| | | /// 入库任务到达某个位置时调用此方法,判断目标设备是否需要物料。 |
| | | /// 入库对应上层工位(Layer.Upper),因为物料从上层进入仓库。 |
| | | /// </remarks> |
| | | /// <param name="conveyorLine">输送线设备对象</param> |
| | | /// <param name="nextAddress">下一地址/目标设备编码</param> |
| | | /// <param name="childDeviceCode">当前子设备编码</param> |
| | | public void HandleInboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode) |
| | | /// <param name="conveyorLine">输送线设备对象,用于写入目标地址和 ACK 信号</param> |
| | | /// <param name="nextAddress">下一地址/目标设备编码,用于识别目标设备类型</param> |
| | | /// <param name="childDeviceCode">当前子设备编码,用于精确定位写入哪个子设备</param> |
| | | public bool HandleInboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode) |
| | | { |
| | | // 调用通用处理方法,isUpper = true 表示处理上层 |
| | | HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: true); |
| | | // 记录入库场景的调试日志,包含子设备和目标地址信息 |
| | | WriteDebug(conveyorLine, "入库下一地址", childDeviceCode, nextAddress); |
| | | |
| | | var cvState = conveyorLine.GetValue<ConveyorLineDBNameNew, byte>(ConveyorLineDBNameNew.CV_State, nextAddress); |
| | | bool isAvailable = cvState == 2; |
| | | if (isAvailable) |
| | | { |
| | | return conveyorLine.SetValue(ConveyorLineDBNameNew.Target, Convert.ToInt16(nextAddress), childDeviceCode); |
| | | } |
| | | return false; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理出库场景的下一地址请求 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 当出库任务执行到某个位置时调用此方法。 |
| | | /// 判断目标设备是否需要物料或可以出料。 |
| | | /// 出库任务到达某个位置时调用此方法,判断目标设备是否需要出料。 |
| | | /// 出库对应下层工位(Layer.Lower),因为物料从下层离开仓库。 |
| | | /// </remarks> |
| | | /// <param name="conveyorLine">输送线设备对象</param> |
| | | /// <param name="nextAddress">下一地址/目标设备编码</param> |
| | | /// <param name="childDeviceCode">当前子设备编码</param> |
| | | public void HandleOutboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode) |
| | | /// <param name="conveyorLine">输送线设备对象,用于写入目标地址和 ACK 信号</param> |
| | | /// <param name="nextAddress">下一地址/目标设备编码,用于识别目标设备类型</param> |
| | | /// <param name="childDeviceCode">当前子设备编码,用于精确定位写入哪个子设备</param> |
| | | public bool HandleOutboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode) |
| | | { |
| | | // 调用通用处理方法,isUpper = false 表示处理下层 |
| | | HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: false); |
| | | } |
| | | // 记录出库场景的调试日志,包含子设备和目标地址信息 |
| | | WriteDebug(conveyorLine, "出库下一地址", childDeviceCode, nextAddress); |
| | | |
| | | var cvState = conveyorLine.GetValue<ConveyorLineDBNameNew, byte>(ConveyorLineDBNameNew.CV_State, nextAddress); |
| | | bool isAvailable = cvState == 2; |
| | | if (isAvailable) |
| | | { |
| | | return conveyorLine.SetValue(ConveyorLineDBNameNew.Target, Convert.ToInt16(nextAddress), childDeviceCode); |
| | | } |
| | | return false; |
| | | } |
| | | /// <summary> |
| | | /// 通用设备请求处理方法 |
| | | /// 写入调试日志(同时输出到两个日志系统) |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 根据目标地址类型(拘束机/插拔钉机)调用相应的处理逻辑。 |
| | | /// 处理上下层设备的物料请求和出料协调。 |
| | | /// 统一入口点日志格式,同时向 Microsoft.Extensions.Logging 和 QuartzLogger 写入, |
| | | /// 保证日志既能在控制台查看也能在文件中追溯。 |
| | | /// </remarks> |
| | | /// <param name="conveyorLine">输送线设备对象</param> |
| | | /// <param name="nextAddress">下一地址/目标设备编码</param> |
| | | /// <param name="childDeviceCode">当前子设备编码</param> |
| | | /// <param name="isUpper">是否处理上层(true=上层,false=下层)</param> |
| | | private void HandleDeviceRequest(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode, bool isUpper) |
| | | /// <param name="conveyorLine">输送线设备对象,用于获取设备编码写入 QuartzLogger</param> |
| | | /// <param name="scenario">场景描述,如"入库下一地址"或"出库下一地址"</param> |
| | | /// <param name="childDeviceCode">子设备编码</param> |
| | | /// <param name="nextAddress">目标设备编码</param> |
| | | private void WriteDebug(CommonConveyorLine conveyorLine, string scenario, string childDeviceCode, string nextAddress) |
| | | { |
| | | // 获取全局设备列表 |
| | | var devices = Storage.Devices; |
| | | |
| | | // 判断目标设备类型 |
| | | if (ConstraintMachineCodes.Contains(nextAddress)) |
| | | { |
| | | // 拘束机处理分支 |
| | | // 查找拘束机设备 |
| | | ConstraintMachine? constraint = devices.OfType<ConstraintMachine>().FirstOrDefault(d => d.DeviceName == ConstraintMachineName); |
| | | if (constraint == null) |
| | | { |
| | | // 未找到拘束机设备,直接返回 |
| | | return; |
| | | } |
| | | |
| | | // 处理拘束机的请求 |
| | | ProcessDeviceRequest( |
| | | conveyorLine, |
| | | childDeviceCode, |
| | | // 获取物料请求标志(上层或下层) |
| | | getMaterialRequest: () => isUpper |
| | | ? constraint.GetValue<ConstraintMachineDBName, short>(ConstraintMachineDBName.MaterialRequestUpper) != 0 |
| | | : constraint.GetValue<ConstraintMachineDBName, short>(ConstraintMachineDBName.MaterialRequestLower) != 0, |
| | | // 获取出料请求标志(上层或下层) |
| | | getOutputRequest: () => isUpper |
| | | ? constraint.GetValue<ConstraintMachineDBName, short>(ConstraintMachineDBName.OutputRequestUpper) != 0 |
| | | : constraint.GetValue<ConstraintMachineDBName, short>(ConstraintMachineDBName.OutputRequestLower) != 0, |
| | | // 设置输出就绪标志(上层或下层) |
| | | setOutputReady: outputReq => |
| | | { |
| | | if (isUpper) |
| | | { |
| | | constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyUpper, outputReq ? 1 : 0); |
| | | } |
| | | else |
| | | { |
| | | constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyLower, outputReq ? 1 : 0); |
| | | } |
| | | }); |
| | | } |
| | | else if (PinMachineCodes.Contains(nextAddress)) |
| | | { |
| | | // 插拔钉机处理分支 |
| | | // 查找插拔钉机设备 |
| | | PinMachine? pinMachine = devices.OfType<PinMachine>().FirstOrDefault(d => d.DeviceName == PinMachineName); |
| | | if (pinMachine == null) |
| | | { |
| | | return; |
| | | } |
| | | |
| | | // 处理插拔钉机的请求 |
| | | ProcessDeviceRequest( |
| | | conveyorLine, |
| | | childDeviceCode, |
| | | // 获取物料请求标志(上层或下层) |
| | | getMaterialRequest: () => isUpper |
| | | ? pinMachine.GetValue<PinMachineDBName, short>(PinMachineDBName.MaterialRequestUpper) != 0 |
| | | : pinMachine.GetValue<PinMachineDBName, short>(PinMachineDBName.MaterialRequestLower) != 0, |
| | | // 获取出料请求标志(上层或下层) |
| | | getOutputRequest: () => isUpper |
| | | ? pinMachine.GetValue<PinMachineDBName, short>(PinMachineDBName.OutputRequestUpper) != 0 |
| | | : pinMachine.GetValue<PinMachineDBName, short>(PinMachineDBName.OutputRequestLower) != 0, |
| | | // 设置输出就绪标志(上层或下层) |
| | | setOutputReady: outputReq => |
| | | { |
| | | if (isUpper) |
| | | { |
| | | pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyUpper, outputReq ? 1 : 0); |
| | | } |
| | | else |
| | | { |
| | | pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyLower, outputReq ? 1 : 0); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理设备请求的核心逻辑 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 根据物料请求和出料请求的状态: |
| | | /// - 如果有物料请求,设置目标地址并发送 ACK |
| | | /// - 如果有出料请求,设置设备的输出就绪标志 |
| | | /// </remarks> |
| | | /// <param name="conveyorLine">输送线设备对象</param> |
| | | /// <param name="childDeviceCode">当前子设备编码</param> |
| | | /// <param name="getMaterialRequest">获取物料请求状态的委托</param> |
| | | /// <param name="getOutputRequest">获取出料请求状态的委托</param> |
| | | /// <param name="setOutputReady">设置输出就绪标志的委托</param> |
| | | private static void ProcessDeviceRequest( |
| | | CommonConveyorLine conveyorLine, |
| | | string childDeviceCode, |
| | | Func<bool> getMaterialRequest, |
| | | Func<bool> getOutputRequest, |
| | | Action<bool> setOutputReady) |
| | | { |
| | | // 获取物料请求状态 |
| | | bool materialReq = getMaterialRequest(); |
| | | |
| | | // 获取出料请求状态 |
| | | bool outputReq = getOutputRequest(); |
| | | |
| | | // 如果设备需要物料 |
| | | if (materialReq) |
| | | { |
| | | // 设置目标地址为 1(表示有料进来) |
| | | conveyorLine.SetValue(ConveyorLineDBNameNew.Target, 1, childDeviceCode); |
| | | |
| | | // 回复 ACK 确认信号 |
| | | conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode); |
| | | } |
| | | else |
| | | { |
| | | // 设备不需要物料,设置输出就绪标志 |
| | | // 通知设备可以继续出料 |
| | | setOutputReady(outputReq); |
| | | } |
| | | // 写入结构化日志(可被 Serilog 等日志框架捕获) |
| | | QuartzLogHelper.LogDebug(_logger, "Handle{Scenario}:子设备: {ChildDeviceCode},目标地址: {NextAddress}", $"Handle{scenario}:子设备: {childDeviceCode},目标地址: {nextAddress}", conveyorLine.DeviceCode, scenario, childDeviceCode, nextAddress); |
| | | } |
| | | } |
| | | } |
| | | } |