wanshenmean
10 小时以前 f319fd5d5e5e0332c4c7e209df64c351dfbe6887
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/ConveyorLineNewJob/ConveyorLineTargetAddressSelector.cs
@@ -1,211 +1,94 @@
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);
        }
    }
}
}