using Sln.Wcs.HikRoBotAdapter.Domain.Dto.GbCancelTask; using Sln.Wcs.HikRoBotAdapter.Domain.Dto.GbContinueTask; using Sln.Wcs.HikRoBotAdapter.Domain.Dto.GbTaskSubmit; using Sln.Wcs.HikRoBotAdapter.Domain.Model.CarrierLock; using Sln.Wcs.HikRoBotAdapter.Domain.Model.MatLabelUnbind; using Sln.Wcs.HikRoBotAdapter.Domain.Model.SiteLock; using Sln.Wcs.HikRoBotAdapter.Domain.Model.TaskGroup; using Sln.Wcs.HikRoBotAdapter.Enum; using Sln.Wcs.HikRoBotAdapter.Util; using Sln.Wcs.HikRoBotSdk; using TaskGroupResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.TaskGroup.TaskGroupResultDto; using TaskPriorityResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.TaskPriority.TaskPriorityResultDto; using ZonePauseResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.ZonePause.ZonePauseResultDto; using ZoneHomingResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.ZoneHoming.ZoneHomingResultDto; using ZoneBanishResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.ZoneBanish.ZoneBanishResultDto; using ZoneBlockadeResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.ZoneBlockade.ZoneBlockadeResultDto; using CarrierBindResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.CarrierBind.CarrierBindResultDto; using CarrierUnbindResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.CarrierUnbind.CarrierUnbindResultDto; using SiteBindResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.SiteBind.SiteBindResultDto; using CarrierLockResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.CarrierLock.CarrierLockResultDto; using SiteLockResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.SiteLock.SiteLockResultDto; using PreTaskResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.PreTask.PreTaskResultDto; using QueryTaskResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.QueryTask.QueryTaskResultDto; using QueryRobotResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.QueryRobot.QueryRobotResultDto; using QueryCarrierResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.QueryCarrier.QueryCarrierResultDto; using MatLabelBindResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.MatLabelBind.MatLabelBindResultDto; using MatLabelUnbindResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.MatLabelUnbind.MatLabelUnbindResultDto; using EquipmentNotifyResultDto = Sln.Wcs.HikRoBotAdapter.Domain.Dto.EquipmentNotify.EquipmentNotifyResultDto; #region << 版 本 注 释 >> /*-------------------------------------------------------------------- * 版权所有 (c) 2026 WenJY 保留所有权利。 * CLR版本:4.0.30319.42000 * 机器名称:T14-GEN3-7895 * 命名空间:Sln.Wcs.HikRoBotAdapter.Service.Impl * 唯一标识:05713c1e-50f3-48cf-8ac8-0dfd1f768856 * * 创建者:WenJY * 电子邮箱: * 创建时间:2026-02-25 15:15:34 * 版本:V1.0.0 * 描述: * *-------------------------------------------------------------------- * 修改人: * 时间: * 修改说明: * * 版本:V1.0.0 *--------------------------------------------------------------------*/ #endregion << 版 本 注 释 >> namespace Sln.Wcs.HikRoBotAdapter.Service.Impl { public class HikRoBotService : IHikRoBotService { private readonly IHIKRoBotSdk hikRoBotSdk; private readonly ApiResultCodeUtil codeUtil; public HikRoBotService(IHIKRoBotSdk hikRoBotSdk, ApiResultCodeUtil codeUtil) { this.hikRoBotSdk = hikRoBotSdk; this.codeUtil = codeUtil; } /// /// 通用执行方法:封装空值检查、SDK 调用、结果码解析、异常处理 /// private TResult Execute( Func sdkCall, Func mapSuccess, Func mapError) { try { dynamic resp = sdkCall(); codeUtil.parse((string)resp.code, (string)resp.message, out var status); return mapSuccess(resp, status); } catch (Exception ex) { return mapError(ex.Message); } } private static TResult Error(string msg) where TResult : new() { dynamic result = new TResult(); result.code = HikRoBotStatusEnum.SDK调用异常; result.msg = msg; result.errorCode = msg; result.success = false; return result; } public GbContinueTaskResultDto GbContinueTask(GbContinueTaskDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.GbContinueTask(new HikRoBotSdk.Dto.GbContinueTask.GbContinueTaskDto { triggerType = req.TriggerType, triggerCode = req.TriggerCode, targetRoute = req.TargetRoute != null ? new HikRoBotSdk.Dto.Common.TargetRoute { type = req.TargetRoute.Type, code = req.TargetRoute.Code, operation = req.TargetRoute.Operation, } : null, extra = req.Extra, }); }, (resp, status) => new GbContinueTaskResultDto { code = status, msg = resp.message, data = new Domain.Model.GbContinueTask.GbContinueTaskResultModel { RobotTaskCode = resp.data?.robotTaskCode, NextSeq = resp.data?.nextSeq, Extra = resp.data?.extra, errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public GbCancelTaskResultDto GbCancelTask(GbCancelTaskDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.GbCancelTask(new HikRoBotSdk.Dto.GbCancelTask.GbCancelTaskDto { robotTaskCode = req.RobotTaskCode, cancelType = req.CancelType, carrierCode = req.CarrierCode, robotCode = req.RobotCode, reason = req.Reason, returnTaskType = req.ReturnTaskType, targetRoute = req.TargetRoute != null ? new HikRoBotSdk.Dto.Common.TargetRoute { type = req.TargetRoute.Type, code = req.TargetRoute.Code, } : null, extra = req.Extra, }); }, (resp, status) => new GbCancelTaskResultDto { code = status, msg = resp.message, data = new Domain.Model.GbCancelTask.GbCancelTaskResultModel { RobotTaskCode = resp.data?.robotTaskCode, Extra = resp.data?.extra, errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public GbTaskSubmitResultDto GbTaskSubmit(GbTaskSubmitDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.GbTaskSubmit(new HikRoBotSdk.Dto.GbTaskSubmit.GbTaskSubmitDto { taskType = req.TaskType, initPriority = req.InitPriority, deadline = req.Deadline, expectedStartTime = req.ExpectedStartTime, robotType = req.RobotType, robotCode = req.RobotCode, interrupt = req.Interrupt, robotTaskCode = req.RobotTaskCode, groupCode = req.GroupCode, extra = req.Extra != null ? new HikRoBotSdk.Dto.Common.ExtraInfo() : null, targetRoute = req.TargetRoute != null ? req.TargetRoute.ConvertAll(r => new HikRoBotSdk.Dto.Common.TargetRoute { type = r.Type, code = r.Code, operation = r.Operation, robotType = r.RobotType, robotCode = r.RobotCode, }) : null, }); }, (resp, status) => new GbTaskSubmitResultDto { code = status, msg = resp.message, data = new Domain.Model.GbTaskSubmit.GbTaskSubmitResultModel { code = resp.code, message = resp.message, data = new Domain.Model.GbTaskSubmit.GbTaskSubmitResultData { robotTaskCode = resp.data?.robotTaskCode, extra = resp.data?.extra?.ToString(), }, errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public TaskGroupResultDto TaskGroup(Domain.Dto.TaskGroup.TaskGroupDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.TaskGroup(new HikRoBotSdk.Dto.TaskGroup.TaskGroupDto { groupCode = req.GroupCode, strategy = req.Strategy, strategyValue = req.StrategyValue, groupSeq = !string.IsNullOrEmpty(req.GroupSeq) ? int.Parse(req.GroupSeq) : (int?)null, targetRoute = req.TargetRoute != null ? new HikRoBotSdk.Dto.Common.TargetRoute { type = req.TargetRoute.Type, code = req.TargetRoute.Code, } : null, }); }, (resp, status) => new TaskGroupResultDto { code = status, msg = resp.message,data = new TaskGroupResultModel() { success = resp.success, errorCode = resp.errorCode, }}, Error ); } public TaskPriorityResultDto SetTaskPriority(Domain.Dto.TaskPriority.TaskPriorityDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.SetTaskPriority(new HikRoBotSdk.Dto.TaskPriority.TaskPriorityDto { robotTaskCode = req.RobotTaskCode, initPriority = req.InitPriority ?? 0, deadline = req.Deadline, extra = req.Extra, }); }, (resp, status) => new TaskPriorityResultDto { code = status, msg = resp.message, data = new Domain.Model.TaskPriority.TaskPriorityResultModel { RobotTaskCode = resp.data?.robotTaskCode, Extra = resp.data?.extra?.ToString(), errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public ZonePauseResultDto ZonePause(Domain.Dto.ZonePause.ZonePauseDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.ZonePause(new HikRoBotSdk.Dto.ZonePause.ZonePauseDto { zoneCode = req.ZoneCode, mapCode = req.MapCode, invoke = req.Invoke, }); }, (resp, status) => new ZonePauseResultDto { code = status, msg = resp.message, data = new Domain.Model.ZonePause.ZonePauseResultModel { Extra = resp.extra?.ToString(), errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public ZoneHomingResultDto ZoneHoming(Domain.Dto.ZoneHoming.ZoneHomingDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.ZoneHoming(new HikRoBotSdk.Dto.ZoneHoming.ZoneHomingDto { mapCode = req.MapCode, zoneCode = req.ZoneCode, zoneCodes = req.ZoneCodes, autoShutdown = req.AutoShutdown.ToString().ToLower(), bootTime = req.BootTime, expireTime = req.ExpireTime, extra = req.Extra, }); }, (resp, status) => new ZoneHomingResultDto { code = status, msg = resp.message, data = new Domain.Model.ZoneHoming.ZoneHomingResultModel { HomingCode = resp.data?.homingCode, RobotCount = resp.data?.robotCount ?? 0, Extra = resp.data?.extra?.ToString(), errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public ZoneBanishResultDto ZoneBanish(Domain.Dto.ZoneBanish.ZoneBanishDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.ZoneBanish(new HikRoBotSdk.Dto.ZoneBanish.ZoneBanishDto { zoneCode = req.ZoneCode, zoneCodes = req.ZoneCodes, targetZoneCode = req.TargetZoneCode, pause = req.Pause.ToString().ToLower(), report = req.Report.ToString().ToLower(), controlMode = req.ControlMode, expireTime = req.ExpireTime, invoke = req.Invoke, extra = req.Extra, }); }, (resp, status) => new ZoneBanishResultDto { code = status, msg = resp.message, data = new Domain.Model.ZoneBanish.ZoneBanishResultModel { BanishCode = resp.data?.banishCode, Extra = resp.data?.extra?.ToString(), errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public ZoneBlockadeResultDto ZoneBlockade(Domain.Dto.ZoneBlockade.ZoneBlockadeDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.ZoneBlockade(new HikRoBotSdk.Dto.ZoneBlockade.ZoneBlockadeDto { zoneCode = req.ZoneCode, mapCode = req.MapCode, invoke = req.Invoke, pause = req.Pause.ToString().ToLower(), report = req.Report.ToString().ToLower(), disableSite = req.DisableSite, enableSite = req.EnableSite, }); }, (resp, status) => new ZoneBlockadeResultDto { code = status, msg = resp.message, data = new Domain.Model.ZoneBlockade.ZoneBlockadeResultModel { Extra = resp.extra?.ToString(), errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public CarrierBindResultDto CarrierBind(Domain.Dto.CarrierBind.CarrierBindDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.CarrierBind(new HikRoBotSdk.Dto.CarrierBind.CarrierBindDto { carrierCode = req.CarrierCode, siteCode = req.SiteCode, carrierDir = req.CarrierDir, extra = req.Extra, }); }, (resp, status) => new CarrierBindResultDto { code = status, msg = resp.message, data = new Domain.Model.CarrierBind.CarrierBindResultModel { Extra = resp.extra?.ToString(), errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public CarrierUnbindResultDto CarrierUnbind(Domain.Dto.CarrierUnbind.CarrierUnbindDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.CarrierUnbind(new HikRoBotSdk.Dto.CarrierUnbind.CarrierUnbindDto { carrierCode = req.CarrierCode, siteCode = req.SiteCode, extra = req.Extra, }); }, (resp, status) => new CarrierUnbindResultDto { code = status, msg = resp.message, data = new Domain.Model.CarrierUnbind.CarrierUnbindResultModel { Extra = resp.extra?.ToString(), errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public SiteBindResultDto SiteBind(Domain.Dto.SiteBind.SiteBindDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.SiteBind(new HikRoBotSdk.Dto.SiteBind.SiteBindDto { slotCategory = req.SlotCategory, slotCode = req.SlotCode, carrierCategory = req.CarrierCategory, carrierType = req.CarrierType, carrierCode = req.CarrierCode, carrierDir = req.CarrierDir, invoke = req.Invoke, stackLabel = req.StackLabel, colCount = req.ColCount, extra = req.Extra, }); }, (resp, status) => new SiteBindResultDto { code = status, msg = resp.message, data = new Domain.Model.SiteBind.SiteBindResultModel { Extra = resp.extra?.ToString(), errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public CarrierLockResultDto CarrierLock(Domain.Dto.CarrierLock.CarrierLockDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.CarrierLock(new HikRoBotSdk.Dto.CarrierLock.CarrierLockDto { carrierCode = req.CarrierCode, invoke = req.Invoke, }); }, (resp, status) => new CarrierLockResultDto { code = status, msg = resp.message, data = new CarrierLockResultModel() { success = resp.success, errorCode = resp.errorCode, }}, Error ); } public SiteLockResultDto SiteLock(Domain.Dto.SiteLock.SiteLockDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.SiteLock(new HikRoBotSdk.Dto.SiteLock.SiteLockDto { siteCode = req.SiteCode, invoke = req.Invoke, }); }, (resp, status) => new SiteLockResultDto { code = status, msg = resp.message,data = new SiteLockResultModel() { success = resp.success, errorCode = resp.errorCode, }}, Error ); } public PreTaskResultDto PreTask(Domain.Dto.PreTask.PreTaskDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.PreTask(new HikRoBotSdk.Dto.PreTask.PreTaskDto { siteCode = req.SiteCode, nextTaskTime = req.NextTaskTime, robotType = req.RobotType, priority = req.Priority, taskCount = req.TaskCount, capacityCount = req.CapacityCount, amrDir = req.AmrDir, extra = req.Extra, }); }, (resp, status) => new PreTaskResultDto { code = status, msg = resp.message, data = new Domain.Model.PreTask.PreTaskResultModel { RobotTaskCode = resp.data?.robotTaskCode, Extra = resp.data?.extra?.ToString(), errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public QueryTaskResultDto QueryTaskStatus(Domain.Dto.QueryTask.QueryTaskDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.QueryTaskStatus(new HikRoBotSdk.Dto.QueryTask.QueryTaskDto { robotTaskCode = req.RobotTaskCode, }); }, (resp, status) => { var data = resp.data; var model = new Domain.Model.QueryTask.QueryTaskResultModel { RobotTaskCode = data?.robotTaskCode, TaskType = data?.taskType, TaskStatus = data?.taskStatus, InitPriority = data?.initPriority, Deadline = data?.deadline, SingleRobotCode = data?.singleRobotCode, CurrentSeq = data?.currentSeq, Extra = data?.extra?.ToString(), success = resp.success, errorCode = resp.errorCode, }; if (data?.targetRoute != null) { model.TargetRoute = ((IEnumerable)data.targetRoute).Select(r => new Domain.Model.QueryTask.TargetRoute { Type = r.type, Code = r.code, }).ToList(); } if (data?.waring != null) { model.Waring = new Domain.Model.QueryTask.Waring { Code = data.waring.taskWarnCode, Desc = data.waring.errorMsg, }; } return new QueryTaskResultDto { code = status, msg = resp.message, data = model }; }, Error ); } public QueryRobotResultDto QueryRobotStatus(Domain.Dto.QueryRobot.QueryRobotDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.QueryRobotStatus(new HikRoBotSdk.Dto.QueryRobot.QueryRobotDto { singleRobotCode = req.SingleRobotCode, }); }, (resp, status) => { var data = resp.data; var model = new Domain.Model.QueryRobot.QueryRobotResultModel { SingleRobotCode = data?.singleRobotCode, RobotDir = data?.robotDir ?? 0, RobotIp = data?.robotIp, Battery = data?.battery ?? 0, Speed = data?.speed ?? 0, CarrierCode = data?.carrierCode, Extra = data?.extra?.ToString(), success = resp.success, errorCode = resp.errorCode, }; string xStr = data?.x; if (double.TryParse(xStr, out double xVal)) model.X = xVal; string yStr = data?.y; if (double.TryParse(yStr, out double yVal)) model.Y = yVal; if (data?.robotStatus != null) model.RobotStatus = data.robotStatus.taskable; if (data?.warnings != null) { model.Warnings = ((IEnumerable)data.warnings).Select(w => new Domain.Model.QueryRobot.Waring { Code = w.taskWarnCode, Desc = w.errorMsg, }).ToList(); } return new QueryRobotResultDto { code = status, msg = resp.message, data = model}; }, Error ); } public QueryCarrierResultDto QueryCarrierStatus(Domain.Dto.QueryCarrier.QueryCarrierDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.QueryCarrierStatus(new HikRoBotSdk.Dto.QueryCarrier.QueryCarrierDto { carrierCode = req.CarrierCode, }); }, (resp, status) => { var data = resp.data; var model = new Domain.Model.QueryCarrier.QueryCarrierResultModel { CarrierCode = data?.carrierCode, RobotTaskCode = data?.robotTaskCode, SiteCode = data?.siteCode, CarrierDir = data?.carrierDir, CarrierStatus = data?.carrierStatus, BinCode = data?.binCode, BinName = data?.binName, RobotCode = data?.robotCode, Extra = data?.extra?.ToString(), success = resp.success, errorCode = resp.errorCode, }; string xStr = data?.x; if (double.TryParse(xStr, out double xVal)) model.X = xVal; string yStr = data?.y; if (double.TryParse(yStr, out double yVal)) model.Y = yVal; return new QueryCarrierResultDto { code = status, msg = resp.message, data = model }; }, Error ); } public MatLabelBindResultDto MatLabelBind(Domain.Dto.MatLabelBind.MatLabelBindDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.MatLabelBind(new HikRoBotSdk.Dto.MatLabelBind.MatLabelBindDto { carrierCode = req.CarrierCode, matLabel = req.MatLabel, }); }, (resp, status) => new MatLabelBindResultDto { code = status, msg = resp.message, data = new Domain.Model.MatLabelBind.MatLabelBindResultModel { Extra = resp.extra?.ToString(), errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public MatLabelUnbindResultDto MatLabelUnbind(Domain.Dto.MatLabelUnbind.MatLabelUnbindDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.MatLabelUnbind(new HikRoBotSdk.Dto.MatLabelUnbind.MatLabelUnbindDto { carrierCode = req.CarrierCode, matLabel = req.MatLabel, }); }, (resp, status) => new MatLabelUnbindResultDto { code = status, msg = resp.message,data = new MatLabelUnbindResultModel() { errorCode = resp.errorCode, success = resp.success, }}, Error ); } public EquipmentNotifyResultDto EquipmentNotify(Domain.Dto.EquipmentNotify.EquipmentNotifyDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.EquipmentNotify(new HikRoBotSdk.Dto.EquipmentNotify.EquipmentNotifyDto { eqptCode = req.EqptCode, taskCode = req.TaskCode, actionStatus = req.ActionStatus, siteCode = req.SiteCode, extra = req.Extra, }); }, (resp, status) => new EquipmentNotifyResultDto { code = status, msg = resp.message, data = new Domain.Model.EquipmentNotify.EquipmentNotifyResultModel { RobotTaskCode = resp.data?.robotTaskCode, NextSeq = resp.data?.nextSeq, Extra = resp.data?.extra?.ToString(), errorCode = resp.errorCode, success = resp.success, }, }, Error ); } public EquipmentNotifyResultDto EquipmentNotifyGbt(Domain.Dto.EquipmentNotify.EquipmentNotifyDto req) { return Execute( () => { if (req == null) throw new ArgumentException("输入参数为空。"); return hikRoBotSdk.EquipmentNotifyGbt(new HikRoBotSdk.Dto.EquipmentNotify.EquipmentNotifyDto { eqptCode = req.EqptCode, taskCode = req.TaskCode, actionStatus = req.ActionStatus, siteCode = req.SiteCode, extra = req.Extra, }); }, (resp, status) => new EquipmentNotifyResultDto { code = status, msg = resp.message, data = new Domain.Model.EquipmentNotify.EquipmentNotifyResultModel { RobotTaskCode = resp.data?.robotTaskCode, NextSeq = resp.data?.nextSeq, Extra = resp.data?.extra?.ToString(), errorCode = resp.errorCode, success = resp.success, }, }, Error ); } } }