| | |
| | | public class ConveyorLineTargetAddressSelector |
| | | { |
| | | /// <summary> |
| | | /// 拘束机名称常量 |
| | | /// 设备层级(上层/下层) |
| | | /// </summary> |
| | | private const string ConstraintMachineName = "拘束机"; |
| | | /// <remarks> |
| | | /// 用于区分拘束机和插拔钉机的上层工位和下层工位。 |
| | | /// 入库任务对应上层(MaterialRequestUpper),出库任务对应下层(MaterialRequestLower)。 |
| | | /// </remarks> |
| | | private enum Layer |
| | | { |
| | | /// <summary> |
| | | /// 上层工位 |
| | | /// </summary> |
| | | Upper, |
| | | |
| | | /// <summary> |
| | | /// 插拔钉机名称常量 |
| | | /// 下层工位 |
| | | /// </summary> |
| | | private const string PinMachineName = "插拔钉机"; |
| | | Lower |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 拘束机对应的点位编码列表 |
| | | /// 目标设备类型枚举 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 用于根据目标地址编码识别需要对接的设备类型。 |
| | | /// </remarks> |
| | | private enum TargetDeviceType |
| | | { |
| | | /// <summary> |
| | | /// 无有效设备(地址不在已知范围内) |
| | | /// </summary> |
| | | None, |
| | | |
| | | /// <summary> |
| | | /// 拘束机 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 负责固定/约束电池托盘的设备,有上下两层。 |
| | | /// </remarks> |
| | | ConstraintMachine, |
| | | |
| | | /// <summary> |
| | | /// 插拔钉机 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 负责插针和拔针操作的设备,有上下两层。 |
| | | /// </remarks> |
| | | PinMachine |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 拘束机对应的点位编码集合 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 当目标地址在这些编码中时,表示需要与拘束机交互。 |
| | | /// 使用 HashSet 保证 O(1) 的 Contains 查找性能。 |
| | | /// </remarks> |
| | | private static readonly List<string> ConstraintMachineCodes = new List<string> { "10180", "20090" }; |
| | | private static readonly HashSet<string> ConstraintMachineCodes = new HashSet<string> { "10180", "20090" }; |
| | | |
| | | /// <summary> |
| | | /// 插拔钉机对应的点位编码列表 |
| | | /// 插拔钉机对应的点位编码集合 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 当目标地址在这些编码中时,表示需要与插拔钉机交互。 |
| | | /// 使用 HashSet 保证 O(1) 的 Contains 查找性能。 |
| | | /// </remarks> |
| | | private static readonly List<string> PinMachineCodes = new List<string> { "10190", "20100" }; |
| | | private static readonly HashSet<string> PinMachineCodes = new HashSet<string> { "10190", "20100" }; |
| | | |
| | | /// <summary> |
| | | /// 目标地址到设备类型的映射 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 通过单一字典实现 O(1) 查找,替代原先分别用两个 List + Contains + if/else if 的写法。 |
| | | /// Key: 目标地址编码,Value: 对应的设备类型。 |
| | | /// </remarks> |
| | | private static readonly Dictionary<string, TargetDeviceType> AddressToDeviceType = new Dictionary<string, TargetDeviceType> |
| | | { |
| | | // 拘束机的两个点位编码都映射到 ConstraintMachine 类型 |
| | | { "10180", TargetDeviceType.ConstraintMachine }, |
| | | { "20090", TargetDeviceType.ConstraintMachine }, |
| | | // 插拔钉机的两个点位编码都映射到 PinMachine 类型 |
| | | { "10190", TargetDeviceType.PinMachine }, |
| | | { "20100", TargetDeviceType.PinMachine } |
| | | }; |
| | | |
| | | /// <summary> |
| | | /// 日志记录器 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 通过 Microsoft.Extensions.Logging 接口注入,用于结构化日志输出。 |
| | | /// </remarks> |
| | | private readonly ILogger _logger; |
| | | |
| | | /// <summary> |
| | | /// 构造函数 |
| | | /// </summary> |
| | | /// <param name="logger">日志记录器</param> |
| | | /// <param name="logger">日志记录器,由依赖注入容器自动注入</param> |
| | | public ConveyorLineTargetAddressSelector(ILogger logger) |
| | | { |
| | | _logger = logger; |
| | | _logger = logger; // 保存日志记录器实例,供后续方法使用 |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理入库场景的下一地址请求 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 当入库任务执行到某个位置时调用此方法。 |
| | | /// 判断目标设备是否需要物料或可以出料。 |
| | | /// 入库任务到达某个位置时调用此方法,判断目标设备是否需要物料。 |
| | | /// 入库对应上层工位(Layer.Upper),因为物料从上层进入仓库。 |
| | | /// </remarks> |
| | | /// <param name="conveyorLine">输送线设备对象</param> |
| | | /// <param name="nextAddress">下一地址/目标设备编码</param> |
| | | /// <param name="childDeviceCode">当前子设备编码</param> |
| | | /// <param name="conveyorLine">输送线设备对象,用于写入目标地址和 ACK 信号</param> |
| | | /// <param name="nextAddress">下一地址/目标设备编码,用于识别目标设备类型</param> |
| | | /// <param name="childDeviceCode">当前子设备编码,用于精确定位写入哪个子设备</param> |
| | | public void HandleInboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode) |
| | | { |
| | | _logger.LogDebug("HandleInboundNextAddress:入库下一地址,子设备: {ChildDeviceCode},目标地址: {NextAddress}", childDeviceCode, nextAddress); |
| | | QuartzLogger.Debug($"HandleInboundNextAddress:入库下一地址,子设备: {childDeviceCode},目标地址: {nextAddress}", conveyorLine.DeviceCode); |
| | | // 调用通用处理方法,isUpper = true 表示处理上层 |
| | | HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: true); |
| | | // 记录入库场景的调试日志,包含子设备和目标地址信息 |
| | | WriteDebug(conveyorLine, "入库下一地址", childDeviceCode, nextAddress); |
| | | // 委托通用处理方法,入库对应上层(isUpper: true) |
| | | HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, Layer.Upper); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理出库场景的下一地址请求 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 当出库任务执行到某个位置时调用此方法。 |
| | | /// 判断目标设备是否需要物料或可以出料。 |
| | | /// 出库任务到达某个位置时调用此方法,判断目标设备是否需要出料。 |
| | | /// 出库对应下层工位(Layer.Lower),因为物料从下层离开仓库。 |
| | | /// </remarks> |
| | | /// <param name="conveyorLine">输送线设备对象</param> |
| | | /// <param name="nextAddress">下一地址/目标设备编码</param> |
| | | /// <param name="childDeviceCode">当前子设备编码</param> |
| | | /// <param name="conveyorLine">输送线设备对象,用于写入目标地址和 ACK 信号</param> |
| | | /// <param name="nextAddress">下一地址/目标设备编码,用于识别目标设备类型</param> |
| | | /// <param name="childDeviceCode">当前子设备编码,用于精确定位写入哪个子设备</param> |
| | | public void HandleOutboundNextAddress(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode) |
| | | { |
| | | _logger.LogDebug("HandleOutboundNextAddress:出库下一地址,子设备: {ChildDeviceCode},目标地址: {NextAddress}", childDeviceCode, nextAddress); |
| | | QuartzLogger.Debug($"HandleOutboundNextAddress:出库下一地址,子设备: {childDeviceCode},目标地址: {nextAddress}", conveyorLine.DeviceCode); |
| | | // 调用通用处理方法,isUpper = false 表示处理下层 |
| | | HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, isUpper: false); |
| | | // 记录出库场景的调试日志,包含子设备和目标地址信息 |
| | | WriteDebug(conveyorLine, "出库下一地址", childDeviceCode, nextAddress); |
| | | // 委托通用处理方法,出库对应下层(isUpper: false) |
| | | HandleDeviceRequest(conveyorLine, nextAddress, childDeviceCode, Layer.Lower); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 通用设备请求处理方法 |
| | | /// 根据目标地址类型分发到对应设备处理 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 根据目标地址类型(拘束机/插拔钉机)调用相应的处理逻辑。 |
| | | /// 处理上下层设备的物料请求和出料协调。 |
| | | /// 通过 AddressToDeviceType 字典将目标地址映射到设备类型, |
| | | /// 然后分发到对应的专用处理方法(HandleConstraintMachine / HandlePinMachine)。 |
| | | /// 如果目标地址不在已知映射表中,直接返回,不做任何处理。 |
| | | /// </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">输送线设备对象,传递到具体设备处理方法</param> |
| | | /// <param name="nextAddress">目标设备编码,通过字典查找识别设备类型</param> |
| | | /// <param name="childDeviceCode">子设备编码,传递到具体设备处理方法</param> |
| | | /// <param name="layer">设备层级(上层或下层),决定读取哪组请求标志</param> |
| | | private void HandleDeviceRequest(CommonConveyorLine conveyorLine, string nextAddress, string childDeviceCode, Layer layer) |
| | | { |
| | | // 获取全局设备列表 |
| | | var devices = Storage.Devices; |
| | | // 通过字典查找目标地址对应的设备类型,如果找不到则 deviceType 为 None |
| | | if (!AddressToDeviceType.TryGetValue(nextAddress, out var deviceType) || deviceType == TargetDeviceType.None) |
| | | { |
| | | // 目标地址不在已知映射表中,直接返回(可能是其他类型设备) |
| | | return; |
| | | } |
| | | |
| | | // 判断目标设备类型 |
| | | if (ConstraintMachineCodes.Contains(nextAddress)) |
| | | // 根据识别出的设备类型分发到对应的处理方法 |
| | | switch (deviceType) |
| | | { |
| | | // 拘束机处理分支 |
| | | // 查找拘束机设备 |
| | | ConstraintMachine? constraint = devices.OfType<ConstraintMachine>().FirstOrDefault(d => d.DeviceName == ConstraintMachineName); |
| | | case TargetDeviceType.ConstraintMachine: |
| | | // 拘束机处理分支:获取拘束机实例并处理其上下层请求 |
| | | HandleConstraintMachine(conveyorLine, childDeviceCode, layer); |
| | | break; // 处理完毕,跳出 switch |
| | | |
| | | case TargetDeviceType.PinMachine: |
| | | // 插拔钉机处理分支:获取插拔钉机实例并处理其上下层请求 |
| | | HandlePinMachine(conveyorLine, childDeviceCode, layer); |
| | | break; // 处理完毕,跳出 switch |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理拘束机的物料/出料请求 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 查找拘束机设备,获取当前层级的物料请求和出料请求状态, |
| | | /// 然后调用通用处理逻辑 ProcessDeviceRequest。 |
| | | /// </remarks> |
| | | /// <param name="conveyorLine">输送线设备对象,用于通知目标地址和 ACK</param> |
| | | /// <param name="childDeviceCode">子设备编码,用于精确定位</param> |
| | | /// <param name="layer">设备层级,决定读取上层还是下层的请求标志</param> |
| | | private void HandleConstraintMachine(CommonConveyorLine conveyorLine, string childDeviceCode, Layer layer) |
| | | { |
| | | // 从全局设备列表中查找名为"拘束机"的拘束机设备实例 |
| | | var constraint = FindDevice<ConstraintMachine>("拘束机"); |
| | | if (constraint == null) |
| | | { |
| | | _logger.LogDebug("HandleDeviceRequest:未找到拘束机设备"); |
| | | QuartzLogger.Debug("HandleDeviceRequest:未找到拘束机设备", conveyorLine.DeviceCode); |
| | | // 未找到拘束机设备,直接返回 |
| | | // 未找到拘束机设备,已在 FindDevice 中记录日志,此处直接返回 |
| | | 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); |
| | | // 获取当前层级(上层/下层)的物料请求标志,非零表示设备需要物料 |
| | | bool materialRequest = GetConstraintFlag(constraint, layer, isMaterial: true); |
| | | // 获取当前层级(上层/下层)的出料请求标志,非零表示设备有货要出 |
| | | bool outputRequest = GetConstraintFlag(constraint, layer, isMaterial: false); |
| | | |
| | | // 构造设置输出就绪标志的委托(根据层级写入 Upper 或 Lower 对应的寄存器) |
| | | Action<bool> setOutputReady = outputReady => |
| | | SetConstraintOutputReady(constraint, layer, outputReady); |
| | | |
| | | // 调用通用请求处理逻辑,传入设备类型描述用于日志记录 |
| | | ProcessDeviceRequest(conveyorLine, childDeviceCode, materialRequest, outputRequest, setOutputReady, "拘束机"); |
| | | } |
| | | else |
| | | |
| | | /// <summary> |
| | | /// 处理插拔钉机的物料/出料请求 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 查找插拔钉机设备,获取当前层级的物料请求和出料请求状态, |
| | | /// 然后调用通用处理逻辑 ProcessDeviceRequest。 |
| | | /// </remarks> |
| | | /// <param name="conveyorLine">输送线设备对象,用于通知目标地址和 ACK</param> |
| | | /// <param name="childDeviceCode">子设备编码,用于精确定位</param> |
| | | /// <param name="layer">设备层级,决定读取上层还是下层的请求标志</param> |
| | | private void HandlePinMachine(CommonConveyorLine conveyorLine, string childDeviceCode, Layer layer) |
| | | { |
| | | constraint.SetValue(ConstraintMachineDBName.ConstraintTrayOutputReadyLower, outputReq ? 1 : 0); |
| | | } |
| | | }, |
| | | "拘束机"); |
| | | } |
| | | else if (PinMachineCodes.Contains(nextAddress)) |
| | | { |
| | | // 插拔钉机处理分支 |
| | | // 查找插拔钉机设备 |
| | | PinMachine? pinMachine = devices.OfType<PinMachine>().FirstOrDefault(d => d.DeviceName == PinMachineName); |
| | | // 从全局设备列表中查找名为"插拔钉机"的插拔钉机设备实例 |
| | | var pinMachine = FindDevice<PinMachine>("插拔钉机"); |
| | | if (pinMachine == null) |
| | | { |
| | | _logger.LogDebug("HandleDeviceRequest:未找到插拔钉机设备"); |
| | | QuartzLogger.Debug("HandleDeviceRequest:未找到插拔钉机设备", conveyorLine.DeviceCode); |
| | | // 未找到插拔钉机设备,已在 FindDevice 中记录日志,此处直接返回 |
| | | 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); |
| | | // 获取当前层级(上层/下层)的物料请求标志,非零表示设备需要物料 |
| | | bool materialRequest = GetPinMachineFlag(pinMachine, layer, isMaterial: true); |
| | | // 获取当前层级(上层/下层)的出料请求标志,非零表示设备有货要出 |
| | | bool outputRequest = GetPinMachineFlag(pinMachine, layer, isMaterial: false); |
| | | |
| | | // 构造设置输出就绪标志的委托(根据层级写入 Upper 或 Lower 对应的寄存器) |
| | | Action<bool> setOutputReady = outputReady => |
| | | SetPinMachineOutputReady(pinMachine, layer, outputReady); |
| | | |
| | | // 调用通用请求处理逻辑,传入设备类型描述用于日志记录 |
| | | ProcessDeviceRequest(conveyorLine, childDeviceCode, materialRequest, outputRequest, setOutputReady, "插拔钉机"); |
| | | } |
| | | else |
| | | |
| | | /// <summary> |
| | | /// 查找指定名称的设备 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 从全局设备列表 Storage.Devices 中通过设备类型和名称查找设备实例。 |
| | | /// 这是一个泛型方法,可以适用于 ConstraintMachine、PinMachine 等多种设备类型。 |
| | | /// </remarks> |
| | | /// <typeparam name="T">设备类型,必须是引用类型(如 ConstraintMachine、PinMachine)</typeparam> |
| | | /// <param name="deviceName">设备名称,用于精确匹配设备的 DeviceName 属性</param> |
| | | /// <returns>找到的设备实例,未找到则返回 null</returns> |
| | | private T? FindDevice<T>(string deviceName) where T : class |
| | | { |
| | | pinMachine.SetValue(PinMachineDBName.PlugPinTrayOutputReadyLower, outputReq ? 1 : 0); |
| | | // OfType<T>() 筛选出指定类型的设备,FirstOrDefault 按名称精确匹配 |
| | | var device = Storage.Devices.OfType<T>().FirstOrDefault(d => GetDeviceName(d) == deviceName); |
| | | if (device == null) |
| | | { |
| | | // 设备未找到时记录调试日志,方便排查配置问题 |
| | | _logger.LogDebug("FindDevice:未找到 {DeviceName}", deviceName); |
| | | } |
| | | }, |
| | | "插拔钉机"); |
| | | return device; // 可能为 null,由调用方负责 null 检查 |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 通过多态获取任意设备的名称 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 使用 switch 表达式根据设备的具体类型获取其 DeviceName 属性, |
| | | /// 避免为每种设备类型编写独立的反射或属性访问代码。 |
| | | /// 当前支持 ConstraintMachine 和 PinMachine 两种类型。 |
| | | /// </remarks> |
| | | /// <typeparam name="T">设备类型</typeparam> |
| | | /// <param name="device">设备实例,非空</param> |
| | | /// <returns>设备的名称字符串,如果类型不匹配则返回空字符串</returns> |
| | | private static string GetDeviceName<T>(T device) where T : class |
| | | { |
| | | // 模式匹配:根据设备的具体运行时类型返回对应的 DeviceName |
| | | return device switch |
| | | { |
| | | ConstraintMachine cm => cm.DeviceName, // 拘束机返回其设备名称 |
| | | PinMachine pm => pm.DeviceName, // 插拔钉机返回其设备名称 |
| | | _ => string.Empty // 未知类型返回空字符串(理论上不会走到这里) |
| | | }; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取拘束机的请求标志(物料请求或出料请求) |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 根据 isMaterial 参数决定读取物料请求还是出料请求寄存器, |
| | | /// 再根据 layer 参数决定读取上层(Upper)还是下层(Lower)寄存器。 |
| | | /// 返回值表示请求是否有效(非零为有效)。 |
| | | /// </remarks> |
| | | /// <param name="constraint">拘束机设备实例,用于读取 PLC 寄存器值</param> |
| | | /// <param name="layer">设备层级,决定读取 Upper 还是 Lower 寄存器</param> |
| | | /// <param name="isMaterial">true=读取物料请求标志,false=读取出料请求标志</param> |
| | | /// <returns>请求标志是否有效(true=有请求,false=无请求)</returns> |
| | | private bool GetConstraintFlag(ConstraintMachine constraint, Layer layer, bool isMaterial) |
| | | { |
| | | // 根据 isMaterial 选择对应的寄存器名称对(物料请求或出料请求) |
| | | var (materialKey, outputKey) = isMaterial |
| | | ? (ConstraintMachineDBName.MaterialRequestUpper, ConstraintMachineDBName.MaterialRequestLower) // 物料请求 |
| | | : (ConstraintMachineDBName.OutputRequestUpper, ConstraintMachineDBName.OutputRequestLower); // 出料请求 |
| | | |
| | | // 根据 layer 选择具体使用 Upper 还是 Lower 版本的寄存器 |
| | | var key = layer == Layer.Upper ? materialKey : outputKey; |
| | | // 读取寄存器值,非零表示有请求,返回布尔值 |
| | | return constraint.GetValue<ConstraintMachineDBName, short>(key) != 0; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 获取插拔钉机的请求标志(物料请求或出料请求) |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 与 GetConstraintFlag 逻辑相同,但操作对象是插拔钉机(PinMachine)。 |
| | | /// 根据 isMaterial 参数决定读取物料请求还是出料请求寄存器, |
| | | /// 再根据 layer 参数决定读取上层(Upper)还是下层(Lower)寄存器。 |
| | | /// </remarks> |
| | | /// <param name="pinMachine">插拔钉机设备实例,用于读取 PLC 寄存器值</param> |
| | | /// <param name="layer">设备层级,决定读取 Upper 还是 Lower 寄存器</param> |
| | | /// <param name="isMaterial">true=读取物料请求标志,false=读取出料请求标志</param> |
| | | /// <returns>请求标志是否有效(true=有请求,false=无请求)</returns> |
| | | private bool GetPinMachineFlag(PinMachine pinMachine, Layer layer, bool isMaterial) |
| | | { |
| | | // 根据 isMaterial 选择对应的寄存器名称对(物料请求或出料请求) |
| | | var (materialKey, outputKey) = isMaterial |
| | | ? (PinMachineDBName.MaterialRequestUpper, PinMachineDBName.MaterialRequestLower) // 物料请求 |
| | | : (PinMachineDBName.OutputRequestUpper, PinMachineDBName.OutputRequestLower); // 出料请求 |
| | | |
| | | // 根据 layer 选择具体使用 Upper 还是 Lower 版本的寄存器 |
| | | var key = layer == Layer.Upper ? materialKey : outputKey; |
| | | // 读取寄存器值,非零表示有请求,返回布尔值 |
| | | return pinMachine.GetValue<PinMachineDBName, short>(key) != 0; |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 设置拘束机的输出就绪标志 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 向拘束机的 PLC 寄存器写入输出就绪状态,告知拘束机可以继续出料。 |
| | | /// 根据 layer 参数决定写入上层(ConstraintTrayOutputReadyUpper)还是下层(ConstraintTrayOutputReadyLower)寄存器。 |
| | | /// </remarks> |
| | | /// <param name="constraint">拘束机设备实例,用于写入 PLC 寄存器</param> |
| | | /// <param name="layer">设备层级,决定写入 Upper 还是 Lower 寄存器</param> |
| | | /// <param name="outputReady">输出就绪状态,true=可以出料,false=不能出料</param> |
| | | private void SetConstraintOutputReady(ConstraintMachine constraint, Layer layer, bool outputReady) |
| | | { |
| | | // 根据 layer 选择对应的输出就绪寄存器(Upper 或 Lower) |
| | | var key = layer == Layer.Upper |
| | | ? ConstraintMachineDBName.ConstraintTrayOutputReadyUpper // 上层输出就绪寄存器 |
| | | : ConstraintMachineDBName.ConstraintTrayOutputReadyLower; // 下层输出就绪寄存器 |
| | | // 向 PLC 写入值,outputReady 为 true 时写入 1,否则写入 0 |
| | | constraint.SetValue(key, outputReady ? true : false); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 设置插拔钉机的输出就绪标志 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 向插拔钉机的 PLC 寄存器写入输出就绪状态,告知插拔钉机可以继续出料。 |
| | | /// 根据 layer 参数决定写入上层(PlugPinTrayOutputReadyUpper)还是下层(PlugPinTrayOutputReadyLower)寄存器。 |
| | | /// </remarks> |
| | | /// <param name="pinMachine">插拔钉机设备实例,用于写入 PLC 寄存器</param> |
| | | /// <param name="layer">设备层级,决定写入 Upper 还是 Lower 寄存器</param> |
| | | /// <param name="outputReady">输出就绪状态,true=可以出料,false=不能出料</param> |
| | | private void SetPinMachineOutputReady(PinMachine pinMachine, Layer layer, bool outputReady) |
| | | { |
| | | // 根据 layer 选择对应的输出就绪寄存器(Upper 或 Lower) |
| | | var key = layer == Layer.Upper |
| | | ? PinMachineDBName.PlugPinTrayOutputReadyUpper // 上层输出就绪寄存器 |
| | | : PinMachineDBName.PlugPinTrayOutputReadyLower; // 下层输出就绪寄存器 |
| | | // 向 PLC 写入值,outputReady 为 true 时写入 1,否则写入 0 |
| | | pinMachine.SetValue(key, outputReady ? true : false); |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 处理设备请求的核心逻辑 |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 根据物料请求和出料请求的状态: |
| | | /// - 如果有物料请求,设置目标地址并发送 ACK |
| | | /// - 如果有出料请求,设置设备的输出就绪标志 |
| | | /// 根据物料请求和出料请求的状态决定行为: |
| | | /// - 如果设备需要物料(materialRequest=true),设置输送线的目标地址为 1(有料进来)并回复 ACK 确认信号 |
| | | /// - 如果设备不需要物料(materialRequest=false),则通知设备可以继续出料(setOutputReady) |
| | | /// |
| | | /// 这是拘束机和插拔钉机共用的处理逻辑,设备特有的部分已在此方法外封装。 |
| | | /// </remarks> |
| | | /// <param name="conveyorLine">输送线设备对象</param> |
| | | /// <param name="childDeviceCode">当前子设备编码</param> |
| | | /// <param name="getMaterialRequest">获取物料请求状态的委托</param> |
| | | /// <param name="getOutputRequest">获取出料请求状态的委托</param> |
| | | /// <param name="setOutputReady">设置输出就绪标志的委托</param> |
| | | /// <param name="deviceType">设备类型描述</param> |
| | | /// <param name="conveyorLine">输送线设备对象,用于写入目标地址(Target)和 ACK 信号</param> |
| | | /// <param name="childDeviceCode">子设备编码,用于精确定位写入哪个子设备</param> |
| | | /// <param name="materialRequest">物料请求标志,true=设备当前需要补充物料</param> |
| | | /// <param name="outputRequest">出料请求标志,true=设备当前有物料需要输出(配合 materialRequest=false 时使用)</param> |
| | | /// <param name="setOutputReady">设置设备输出就绪标志的委托,根据层级写入 Upper 或 Lower 寄存器</param> |
| | | /// <param name="deviceType">设备类型描述字符串,用于日志输出</param> |
| | | private void ProcessDeviceRequest( |
| | | CommonConveyorLine conveyorLine, |
| | | string childDeviceCode, |
| | | Func<bool> getMaterialRequest, |
| | | Func<bool> getOutputRequest, |
| | | bool materialRequest, |
| | | bool outputRequest, |
| | | Action<bool> setOutputReady, |
| | | string deviceType) |
| | | { |
| | | // 获取物料请求状态 |
| | | bool materialReq = getMaterialRequest(); |
| | | |
| | | // 获取出料请求状态 |
| | | bool outputReq = getOutputRequest(); |
| | | |
| | | // 记录当前请求状态的调试日志,供排查问题使用 |
| | | _logger.LogDebug("ProcessDeviceRequest:{DeviceType},子设备: {ChildDeviceCode},物料请求: {MaterialReq},出料请求: {OutputReq}", |
| | | deviceType, childDeviceCode, materialReq, outputReq); |
| | | QuartzLogger.Debug($"ProcessDeviceRequest:{deviceType},子设备: {childDeviceCode},物料请求: {materialReq},出料请求: {outputReq}", conveyorLine.DeviceCode); |
| | | deviceType, childDeviceCode, materialRequest, outputRequest); |
| | | // 同步写入 Quartz 日志文件(双写可追溯,这里保留与原逻辑一致的行为) |
| | | QuartzLogger.Debug($"ProcessDeviceRequest:{deviceType},子设备: {childDeviceCode},物料请求: {materialRequest},出料请求: {outputRequest}", conveyorLine.DeviceCode); |
| | | |
| | | // 如果设备需要物料 |
| | | if (materialReq) |
| | | // 分支判断:设备是需要物料还是需要出料 |
| | | if (materialRequest) |
| | | { |
| | | // 设置目标地址为 1(表示有料进来) |
| | | // 设备需要物料 -> 通知输送线有料过来 |
| | | // 1. 设置目标地址为 1,表示"有料进入" |
| | | conveyorLine.SetValue(ConveyorLineDBNameNew.Target, 1, childDeviceCode); |
| | | |
| | | // 回复 ACK 确认信号 |
| | | // 2. 回复 ACK 确认信号,告知设备已收到请求 |
| | | conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, 1, childDeviceCode); |
| | | |
| | | // 3. 记录信息日志,表明已完成目标地址设置和 ACK 回复 |
| | | _logger.LogInformation("ProcessDeviceRequest:{DeviceType} 需要物料,已设置目标地址和ACK", deviceType); |
| | | QuartzLogger.Info($"ProcessDeviceRequest:{deviceType} 需要物料,已设置目标地址和ACK", conveyorLine.DeviceCode); |
| | | } |
| | | else |
| | | { |
| | | // 设备不需要物料,设置输出就绪标志 |
| | | // 通知设备可以继续出料 |
| | | setOutputReady(outputReq); |
| | | // 设备不需要物料 -> 通知设备可以继续出料(无论当前是否有货要出,都要通知) |
| | | // outputRequest 表示设备当前是否确实有货,如果没有货则 outputReady=false,设备收到后等待 |
| | | setOutputReady(outputRequest); |
| | | } |
| | | } |
| | | |
| | | /// <summary> |
| | | /// 写入调试日志(同时输出到两个日志系统) |
| | | /// </summary> |
| | | /// <remarks> |
| | | /// 统一入口点日志格式,同时向 Microsoft.Extensions.Logging 和 QuartzLogger 写入, |
| | | /// 保证日志既能在控制台查看也能在文件中追溯。 |
| | | /// </remarks> |
| | | /// <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) |
| | | { |
| | | // 写入结构化日志(可被 Serilog 等日志框架捕获) |
| | | _logger.LogDebug("Handle{Scenario}:子设备: {ChildDeviceCode},目标地址: {NextAddress}", |
| | | scenario, childDeviceCode, nextAddress); |
| | | // 写入 Quartz 专用日志文件(供定时任务轨迹追踪) |
| | | QuartzLogger.Debug($"Handle{scenario}:子设备: {childDeviceCode},目标地址: {nextAddress}", conveyorLine.DeviceCode); |
| | | } |
| | | } |
| | | } |