wanshenmean
16 小时以前 5bf10c1dafe485d506ec534f98e5220a3b83dd17
Code/WCS/WIDESEAWCS_Server/WIDESEAWCS_Tasks/RobotJob/RobotTaskProcessor.cs
@@ -69,6 +69,14 @@
        private readonly HttpClientHelper _httpClientHelper;
        /// <summary>
        /// 假电芯平面点位服务
        /// </summary>
        /// <remarks>
        /// 用于管理假电芯平面点位的分配和状态。
        /// </remarks>
        private readonly IFakeBatteryPositionService _fakeBatteryPositionService;
        /// <summary>
        /// 日志记录器
        /// </summary>
        private readonly ILogger _logger;
@@ -88,7 +96,8 @@
            IRobotTaskService robotTaskService,
            ITaskService taskService,
            HttpClientHelper httpClientHelper,
            ILogger logger)
            ILogger logger,
            IFakeBatteryPositionService fakeBatteryPositionService)
        {
            _socketClientGateway = socketClientGateway;
            _stateManager = stateManager;
@@ -96,6 +105,7 @@
            _taskService = taskService;
            _httpClientHelper = httpClientHelper;
            _logger = logger;
            _fakeBatteryPositionService = fakeBatteryPositionService;
        }
        /// <summary>
@@ -154,7 +164,8 @@
        /// </remarks>
        /// <param name="task">要下发的任务对象</param>
        /// <param name="state">机器人当前状态</param>
        public async Task SendSocketRobotPickAsync(Dt_RobotTask task, RobotSocketState state)
        /// <param name="isScanNG">是否扫码NG</param>
        public async Task SendSocketRobotPickAsync(Dt_RobotTask task, RobotSocketState state, bool isScanNG)
        {
            // 构建取货指令,格式:Pickbattery,{源地址}
            string taskString = $"Pickbattery,{task.RobotSourceAddress}";
@@ -174,6 +185,11 @@
                // 将任务关联到状态对象
                state.CurrentTask = task;
                if(isScanNG)
                {
                    state.IsScanNG = true;
                }
                // 保持原语义:仅在状态安全写入成功后再更新任务状态
                // 这样可以确保状态和任务记录的一致性
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
@@ -186,6 +202,193 @@
                // 发送失败,记录 Error 日志
                _logger.LogError("下发取货指令失败,指令: {TaskString},设备: {DeviceName}", taskString, state.RobotCrane?.DeviceName);
                QuartzLogger.Error($"下发取货指令失败,指令: {taskString}", state.RobotCrane?.DeviceName);
            }
        }
        /// <summary>
        /// 下发假电芯取货指令到机器人客户端
        /// </summary>
        /// <remarks>
        /// 发送格式:Pickbattery,5,{startPosition}-{endPosition}
        /// 例如:Pickbattery,5,1-3 表示从假电芯位置5抓取,平面点位1到3
        ///
        /// 下发成功后:
        /// 1. 标记点位为已使用
        /// 2. 更新任务状态为"机器人执行中"
        /// 3. 安全更新状态到 Redis
        /// </remarks>
        /// <param name="task">要下发的任务对象</param>
        /// <param name="state">机器人当前状态</param>
        /// <param name="positions">要抓取的平面点位列表</param>
        public async Task SendSocketRobotFakeBatteryPickAsync(Dt_RobotTask task, RobotSocketState state, List<int> positions)
        {
            if (positions == null || positions.Count == 0)
            {
                _logger.LogWarning("SendSocketRobotFakeBatteryPickAsync:平面点位列表为空,任务号: {TaskNum}", task.RobotTaskNum);
                return;
            }
            // 计算点位范围,格式:1-3
            int startPos = positions.Min();
            int endPos = positions.Max();
            string taskString = $"Pickbattery,5,{startPos}-{endPos}";
            // 标记点位为已使用
            _fakeBatteryPositionService.MarkAsUsed(positions);
            // 通过 Socket 网关发送指令到机器人客户端
            bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString);
            if (result)
            {
                _logger.LogInformation("下发假电芯取货指令成功,指令: {TaskString},点位: {Positions},设备: {DeviceName}",
                    taskString, string.Join(",", positions), state.RobotCrane?.DeviceName);
                QuartzLogger.Info($"下发假电芯取货指令成功,指令: {taskString}", state.RobotCrane?.DeviceName);
                // 更新任务状态为"机器人执行中"
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
                // 将任务关联到状态对象
                state.CurrentTask = task;
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                {
                    await _robotTaskService.UpdateRobotTaskAsync(task);
                }
            }
            else
            {
                _logger.LogError("下发假电芯取货指令失败,指令: {TaskString},设备: {DeviceName}", taskString, state.RobotCrane?.DeviceName);
                QuartzLogger.Error($"下发假电芯取货指令失败,指令: {taskString}", state.RobotCrane?.DeviceName);
            }
        }
        /// <summary>
        /// 获取下N个可用的假电芯平面点位
        /// </summary>
        /// <param name="count">需要获取的点位数量</param>
        /// <returns>可用点位列表</returns>
        public List<int> GetNextAvailableFakeBatteryPositions(int count)
        {
            return _fakeBatteryPositionService.GetNextAvailable(count);
        }
        /// <summary>
        /// 计算批次编号范围
        /// </summary>
        /// <remarks>
        /// 返回格式:(start, end)
        /// - remaining >= 4: (currentIndex, currentIndex + 3)
        /// - remaining > 1: (currentIndex, currentIndex + remaining - 1)
        /// - remaining == 1: (currentIndex, 0)  -- 单个物品用 0 表示 end
        /// </remarks>
        /// <param name="currentIndex">当前批次起始编号</param>
        /// <param name="remaining">剩余数量</param>
        /// <returns>(start, end) 元组</returns>
        public (int Start, int End) BuildBatchRange(int currentIndex, int remaining)
        {
            if (remaining >= 4)
                return (currentIndex, currentIndex + 3);
            else if (remaining > 1)
                return (currentIndex, currentIndex + remaining - 1);
            else  // remaining == 1
                return (currentIndex, 0);
        }
        /// <summary>
        /// 下发取货指令(带批次格式和总数)
        /// </summary>
        /// <remarks>
        /// 发送顺序:
        /// 1. PickTotalNum,{N} -- 真实电芯总数
        /// 2. Pickbattery,{位置},{start}-{end} -- 批次取货指令
        ///
        /// 下发成功后更新任务状态为"机器人执行中"。
        /// </remarks>
        /// <param name="task">要下发的任务对象</param>
        /// <param name="state">机器人当前状态</param>
        /// <param name="position">取货位置</param>
        /// <param name="batchStart">批次起始编号</param>
        /// <param name="batchEnd">批次结束编号</param>
        public async Task SendPickWithBatchAsync(Dt_RobotTask task, RobotSocketState state, string position, int batchStart, int batchEnd)
        {
            // 先发送总数指令
            string totalNumCmd = $"PickTotalNum,{task.RobotTaskTotalNum}";
            await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd);
            // 再发送批次取货指令
            string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}";
            string taskString = $"Pickbattery,{position},{range}";
            bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString);
            if (result)
            {
                _logger.LogInformation("下发批次取货指令成功,指令: {TaskString},批次: {Range},设备: {DeviceName}",
                    taskString, range, state.RobotCrane?.DeviceName);
                QuartzLogger.Info($"下发批次取货指令成功,指令: {taskString},批次: {range}", state.RobotCrane?.DeviceName);
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
                state.CurrentTask = task;
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                {
                    await _robotTaskService.UpdateRobotTaskAsync(task);
                }
            }
            else
            {
                _logger.LogError("下发批次取货指令失败,指令: {TaskString},设备: {DeviceName}", taskString, state.RobotCrane?.DeviceName);
                QuartzLogger.Error($"下发批次取货指令失败,指令: {taskString}", state.RobotCrane?.DeviceName);
            }
        }
        /// <summary>
        /// 下发放货指令(带批次格式和总数)
        /// </summary>
        /// <remarks>
        /// 发送顺序:
        /// 1. PutTotalNum,{N} -- 真实电芯总数
        /// 2. Putbattery,{位置},{start}-{end} -- 批次放货指令
        ///
        /// 下发成功后更新任务状态为"机器人执行中"。
        /// </remarks>
        /// <param name="task">要下发的任务对象</param>
        /// <param name="state">机器人当前状态</param>
        /// <param name="position">放货位置</param>
        /// <param name="batchStart">批次起始编号</param>
        /// <param name="batchEnd">批次结束编号</param>
        public async Task SendPutWithBatchAsync(Dt_RobotTask task, RobotSocketState state, string position, int batchStart, int batchEnd)
        {
            // 先发送总数指令
            string totalNumCmd = $"PutTotalNum,{task.RobotTaskTotalNum}";
            await _socketClientGateway.SendToClientAsync(state.IPAddress, totalNumCmd);
            // 再发送批次放货指令
            string range = batchEnd == 0 ? $"{batchStart}-0" : $"{batchStart}-{batchEnd}";
            string taskString = $"Putbattery,{position},{range}";
            bool result = await _socketClientGateway.SendToClientAsync(state.IPAddress, taskString);
            if (result)
            {
                _logger.LogInformation("下发放货指令成功,指令: {TaskString},批次: {Range},设备: {DeviceName}",
                    taskString, range, state.RobotCrane?.DeviceName);
                QuartzLogger.Info($"下发放货指令成功,指令: {taskString},批次: {range}", state.RobotCrane?.DeviceName);
                task.RobotTaskState = TaskRobotStatusEnum.RobotExecuting.GetHashCode();
                state.CurrentTask = task;
                if (_stateManager.TryUpdateStateSafely(state.IPAddress, state))
                {
                    await _robotTaskService.UpdateRobotTaskAsync(task);
                }
            }
            else
            {
                _logger.LogError("下发放货指令失败,指令: {TaskString},设备: {DeviceName}",
                    taskString, state.RobotCrane?.DeviceName);
                QuartzLogger.Error($"下发放货指令失败,指令: {taskString}", state.RobotCrane?.DeviceName);
            }
        }
@@ -317,37 +520,37 @@
            }
            // 解析返回的任务信息
            var taskInfos = JsonConvert.DeserializeObject<List<Dt_Task>>(content.Data.ToJson() ?? string.Empty) ?? new List<Dt_Task>();
            var taskInfo = taskInfos.FirstOrDefault();
            //var taskInfos = JsonConvert.DeserializeObject<List<Dt_Task>>(content.Data.ToJson() ?? string.Empty) ?? new List<Dt_Task>();
            //var taskInfo = taskInfos.FirstOrDefault();
            // 获取源地址
            string sourceAddress = taskDTO.SourceAddress;
            //// 获取源地址
            //string sourceAddress = taskDTO.SourceAddress;
            // 查找源地址对应的输送线设备
            IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceProDTOs.Any(d => d.DeviceChildCode == sourceAddress));
            //// 查找源地址对应的输送线设备
            //IDevice? device = Storage.Devices.FirstOrDefault(x => x.DeviceProDTOs.Any(d => d.DeviceChildCode == sourceAddress));
            if (device != null)
            {
                // 将设备转换为输送线类型
                CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
            //if (device != null)
            //{
            //    // 将设备转换为输送线类型
            //    CommonConveyorLine conveyorLine = (CommonConveyorLine)device;
                // 设置输送线的目标地址
                conveyorLine.SetValue(ConveyorLineDBNameNew.Target, taskInfo.NextAddress, sourceAddress);
            //    // 设置输送线的目标地址
            //    conveyorLine.SetValue(ConveyorLineDBNameNew.Target, taskInfo.NextAddress, sourceAddress);
                // 设置输送线的任务号
                conveyorLine.SetValue(ConveyorLineDBNameNew.TaskNo, taskInfo.TaskNum, sourceAddress);
            //    // 设置输送线的任务号
            //    conveyorLine.SetValue(ConveyorLineDBNameNew.TaskNo, taskInfo.TaskNum, sourceAddress);
                // 触发输送线开始执行(写入 WCS_STB = 1)
                conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_STB, 1, sourceAddress);
            //    // 触发输送线开始执行(写入 WCS_ACK = 1)
            //    conveyorLine.SetValue(ConveyorLineDBNameNew.WCS_ACK, (short)1, sourceAddress);
                // 更新任务状态到下一阶段
                if (_taskService.UpdateTaskStatusToNext(taskInfo).Status)
                {
                    _logger.LogInformation("HandleInboundTaskAsync:入库任务处理成功,任务号: {TaskNum}", taskInfo.TaskNum);
                    QuartzLogger.Info($"HandleInboundTaskAsync:入库任务处理成功,任务号: {taskInfo.TaskNum}", state.RobotCrane?.DeviceName ?? "Unknown");
                    return true;
                }
            }
            //    // 更新任务状态到下一阶段
            //    if (_taskService.UpdateTaskStatusToNext(taskInfo).Status)
            //    {
            //        _logger.LogInformation("HandleInboundTaskAsync:入库任务处理成功,任务号: {TaskNum}", taskInfo.TaskNum);
            //        QuartzLogger.Info($"HandleInboundTaskAsync:入库任务处理成功,任务号: {taskInfo.TaskNum}", state.RobotCrane?.DeviceName ?? "Unknown");
            //        return true;
            //    }
            //}
            return false;
        }
@@ -435,5 +638,33 @@
        {
            return _httpClientHelper.Post<WebResponseContent>(configKey, stockDTO.ToJson());
        }
        /// <summary>
        /// 调用批量拆盘确认 API
        /// </summary>
        /// <remarks>
        /// 当拆盘任务全部取完时调用,一次性上传整个托盘的解绑数据到 MES。
        /// </remarks>
        /// <param name="palletCode">源托盘号</param>
        /// <returns>HTTP 响应结果</returns>
        public HttpResponseResult<WebResponseContent> PostSplitPalletConfirmAsync(string palletCode)
        {
            var request = new { PalletCode = palletCode };
            return _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.SplitPalletConfirm), request.ToJson());
        }
        /// <summary>
        /// 调用批量组盘确认 API
        /// </summary>
        /// <remarks>
        /// 当组盘任务全部放完时调用,一次性上传整个托盘的绑定数据到 MES。
        /// </remarks>
        /// <param name="palletCode">目标托盘号</param>
        /// <returns>HTTP 响应结果</returns>
        public HttpResponseResult<WebResponseContent> PostGroupPalletConfirmAsync(string palletCode)
        {
            var request = new { PalletCode = palletCode };
            return _httpClientHelper.Post<WebResponseContent>(nameof(ConfigKey.GroupPalletConfirm), request.ToJson());
        }
    }
}