فهرست منبع

产品服务与标定流程接口优化及注释完善

本次提交主要内容:
- 补充并完善了 ProductService、RemoteCommandService 及相关 ViewModel 的中文注释,提升代码可读性和维护性。
- 产品服务接口标准化,所有核心操作均支持同步与异步版本,增强线程安全和异常处理。
- 远程命令服务功能增强,统一异步执行流程,丰富事件通知机制,支持更多命令类型。
- 标定相关 ViewModel 结构优化,统一引入 ICameraCalibrationService,实现拍照与处理解耦,简化标定流程,提升复用性。
- 增加 CancellationTokenSource 控制,支持标定过程的中断与资源释放。
- 导航与对话框关闭时统一释放回调,防止资源泄漏。
- 统一命名与代码风格,优化部分方法参数与返回值类型。
孝锋 徐 10 ماه پیش
والد
کامیت
1b3a764999

+ 244 - 88
TeamAAS-VM/Services/ProductService.cs

@@ -16,6 +16,12 @@ using TeamAAS_VP.Models.Product;
 
 namespace TeamAAS_VP.Services
 {
+    /// <summary>
+    /// 产品管理服务。
+    /// 负责在内存中维护产品集合、加载/保存产品到磁盘、产品的增删改查、相机/机器人相关变更的级联更新等操作。
+    /// 线程安全:使用 `_sync` 对共享字典 `_products` 的访问进行保护。
+    /// 产品文件存放遵循约定目录:`ProductsRootPath\{ProductName}\{ProductName}.cfg`,视觉流程文件为 `.vpp`。
+    /// </summary>
     public class ProductService : IProductService
     {
         private readonly Dictionary<Guid, ProductModel> _products = new Dictionary<Guid, ProductModel>();
@@ -26,32 +32,37 @@ namespace TeamAAS_VP.Services
 
         // 可配置的路径与文件名
         // 默认把产品放到公共应用数据目录下的 TeamAAS\Products
+        /// <summary>
+        /// 默认产品路径的相对子目录(相对于应用运行目录或配置指定的根)。
+        /// </summary>
         public const string DefaultProductsSubPath = "..//Products";
 
         /// <summary>
-        /// 模板Toolblock路径
+        /// 模板 ToolBlock 的默认路径,用于在产品不存在视觉工具文件时进行初始化。
         /// </summary>
         public const string TemplateToolBlock = "..//Vision Template//TemplateToolBlock.vpp";
 
         /// <summary>
-        /// 产品模板文件夹路径
+        /// 产品模板文件夹路径(用于通过模板创建新产品)。
         /// </summary>
         public const string ProductTemplatePath = "..//Product Template";
 
         /// <summary>
-        /// 产品文件夹路径
+        /// 产品根目录(可配置)。默认值为 <see cref="DefaultProductsSubPath"/>.
+        /// 每个产品在该目录下以其 Name 为子文件夹保存其配置与视觉文件。
         /// </summary>
         public string ProductsRootPath { get; set; }
 
         /// <summary>
-        /// 产品切换事件
+        /// 当当前产品切换时触发的事件,参数为新的当前 <see cref="ProductModel"/> 实例。
         /// </summary>
         public event Action<ProductModel> OnProductChanged;
 
         /// <summary>
-        /// 构造函数
+        /// 构造函数。
         /// </summary>
-        /// <param name="configService"></param>
+        /// <param name="configService">系统配置服务,用于读取/保存最近打开的产品等系统配置。</param>
+        /// <param name="feederService">料斗服务,当前保存但未直接在本类中使用(保留用于扩展)。</param>
         public ProductService(IConfigService configService, IFeederService feederService)
         {
             _configService = configService;
@@ -59,6 +70,10 @@ namespace TeamAAS_VP.Services
             ProductsRootPath = DefaultProductsSubPath;
         }
 
+        /// <summary>
+        /// 初始化服务:清空内存产品并从磁盘枚举并加载产品清单(仅读取 JSON 配置,不加载所有视觉 ToolBlock)。
+        /// </summary>
+        /// <returns>初始化是否成功(始终返回 true,除非抛出未捕获异常)。</returns>
         public bool Initialize()
         {
             // 清空现有产品
@@ -80,6 +95,10 @@ namespace TeamAAS_VP.Services
             return true;
         }
 
+        /// <summary>
+        /// 异步初始化服务。
+        /// </summary>
+        /// <returns>异步任务,完成时返回初始化结果。</returns>
         public Task<bool> InitializeAsync()
         {
             return Task.Run(() => Initialize());
@@ -87,9 +106,9 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 获取本地所有产品列表
+        /// 获取本地所有产品的元信息列表(ID、名称、配置文件路径)。
         /// </summary>
-        /// <returns></returns>
+        /// <returns>返回一个元组集合,每项包含产品 ID、名称和对应的配置文件路径。</returns>
         public IEnumerable<(Guid id,string name,string filePath)> GetAllProductList()
         {
             if (!Directory.Exists(ProductsRootPath))
@@ -117,7 +136,7 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 保存产品清单
+        /// 将当前内存中的产品清单保存到磁盘(ProductList.cfg)。
         /// </summary>
         public void SaveProductList()
         {
@@ -141,14 +160,20 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 异步保存产品清单
+        /// 异步保存产品清单到磁盘。
         /// </summary>
-        /// <returns></returns>
+        /// <returns>表示异步操作的任务。</returns>
         public Task SaveProductListAsync()
         {
             return Task.Run(() => SaveProductList());
         }
 
+        /// <summary>
+        /// 在内存中创建并注册一个新产品,同时将其保存到磁盘。
+        /// </summary>
+        /// <param name="product">要创建的产品模型。若其 ID 为空则会分配新的 GUID。</param>
+        /// <returns>若名称冲突则返回 false;否则返回 true 表示创建成功。</returns>
+        /// <exception cref="ArgumentNullException">当 <paramref name="product"/> 为 null 时抛出。</exception>
         public bool CreateProduct(ProductModel product)
         {
             if (product == null) throw new ArgumentNullException(nameof(product));
@@ -190,11 +215,21 @@ namespace TeamAAS_VP.Services
             return true;
         }
 
+        /// <summary>
+        /// 异步创建产品。
+        /// </summary>
+        /// <param name="product">产品模型。</param>
+        /// <returns>异步任务,结果为创建是否成功。</returns>
         public Task<bool> CreateProductAsync(ProductModel product)
         {
             return Task.Run(() => CreateProduct(product));
         }
 
+        /// <summary>
+        /// 根据 ID 获取内存中的产品模型(同步)。
+        /// </summary>
+        /// <param name="id">产品 ID。</param>
+        /// <returns>对应的 <see cref="ProductModel"/>,若不存在则返回 null。</returns>
         public ProductModel GetProduct(Guid id)
         {
             lock (_sync)
@@ -204,12 +239,21 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 异步获取指定 ID 的产品(仅包装同步方法)。
+        /// </summary>
+        /// <param name="id">产品 ID。</param>
+        /// <returns>包含产品模型的任务。</returns>
         public Task<ProductModel> GetProductAsync(Guid id)
         {
             var p = GetProduct(id);
             return Task.FromResult(p);
         }
 
+        /// <summary>
+        /// 获取当前服务内存中所有产品的只读集合。
+        /// </summary>
+        /// <returns>只读的产品集合。</returns>
         public IReadOnlyCollection<ProductModel> GetAllProducts()
         {
             lock (_sync)
@@ -218,12 +262,22 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 异步获取所有产品(包装同步实现)。
+        /// </summary>
+        /// <returns>包含只读产品集合的任务。</returns>
         public Task<IReadOnlyCollection<ProductModel>> GetAllProductsAsync()
         {
             var list = GetAllProducts();
             return Task.FromResult(list);
         }
 
+        /// <summary>
+        /// 尝试从内存中获取指定 ID 的产品。
+        /// </summary>
+        /// <param name="id">产品 ID。</param>
+        /// <param name="product">输出参数,若找到则为对应产品,否则为 null。</param>
+        /// <returns>若找到返回 true,否则返回 false。</returns>
         public bool TryGetProduct(Guid id, out ProductModel product)
         {
             lock (_sync)
@@ -232,6 +286,11 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 异步尝试获取产品。
+        /// </summary>
+        /// <param name="id">产品 ID。</param>
+        /// <returns>任务,结果为 (found, product) 二元组。</returns>
         public Task<(bool found, ProductModel product)> TryGetProductAsync(Guid id)
         {
             ProductModel p;
@@ -243,6 +302,11 @@ namespace TeamAAS_VP.Services
             return Task.FromResult((f, p));
         }
 
+        /// <summary>
+        /// 判断内存中是否包含指定 ID 的产品。
+        /// </summary>
+        /// <param name="id">产品 ID。</param>
+        /// <returns>存在返回 true,否则 false。</returns>
         public bool ContainsProduct(Guid id)
         {
             lock (_sync)
@@ -251,6 +315,11 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 异步判断产品是否存在(包装同步实现)。
+        /// </summary>
+        /// <param name="id">产品 ID。</param>
+        /// <returns>包含结果的任务。</returns>
         public Task<bool> ContainsProductAsync(Guid id)
         {
             var v = ContainsProduct(id);
@@ -282,6 +351,11 @@ namespace TeamAAS_VP.Services
         }
 
 
+        /// <summary>
+        /// 从内存中移除产品(不删除磁盘文件),并保存产品清单。
+        /// </summary>
+        /// <param name="id">要移除的产品 ID。</param>
+        /// <returns>若内存中存在并移除成功返回 true,否则 false。</returns>
         public bool RemoveProduct(Guid id)
         {
             bool result;
@@ -295,12 +369,20 @@ namespace TeamAAS_VP.Services
             return result;
         }
 
+        /// <summary>
+        /// 异步移除产品(包装同步实现)。
+        /// </summary>
+        /// <param name="id">产品 ID。</param>
+        /// <returns>任务,结果为移除是否成功。</returns>
         public Task<bool> RemoveProductAsync(Guid id)
         {
             var r = RemoveProduct(id);
             return Task.FromResult(r);
         }
 
+        /// <summary>
+        /// 清空内存中所有产品(不删除磁盘数据)。
+        /// </summary>
         public void RemoveAllProducts()
         {
             lock (_sync)
@@ -310,6 +392,11 @@ namespace TeamAAS_VP.Services
         }
 
         // 仍保留按路径保存的版本(向后兼容)
+        /// <summary>
+        /// 按指定路径保存产品。
+        /// </summary>
+        /// <param name="id">产品 ID。</param>
+        /// <param name="filePath">保存路径(包含文件名)。</param>
         public void SaveProduct(Guid id, string filePath)
         {
             var p = GetProduct(id);
@@ -317,12 +404,22 @@ namespace TeamAAS_VP.Services
             SaveProduct(p, filePath);
         }
 
+        /// <summary>
+        /// 异步按路径保存产品(包装同步实现)。
+        /// </summary>
+        /// <param name="id">产品 ID。</param>
+        /// <param name="filePath">保存路径。</param>
+        /// <returns>表示异步操作的任务。</returns>
         public Task SaveProductAsync(Guid id, string filePath)
         {
             return Task.Run(() => SaveProduct(id, filePath));
         }
 
         // 新增:不需要传入路径的保存,使用约定目录和文件名
+        /// <summary>
+        /// 将产品保存到约定目录(ProductsRootPath\<product.Name>\<product.Name>.cfg),并保存其视觉 ToolBlock 文件。
+        /// </summary>
+        /// <param name="id">产品 ID。</param>
         public void SaveProduct(Guid id)
         {
             // 获取产品
@@ -335,6 +432,12 @@ namespace TeamAAS_VP.Services
             SaveProduct(p, filePath);
         }
 
+        /// <summary>
+        /// 将指定的 <see cref="ProductModel"/> 序列化并写入指定路径,同时为每个相机流程保存或初始化对应的 `.vpp` ToolBlock 文件。
+        /// </summary>
+        /// <param name="p">产品模型,不能为空。</param>
+        /// <param name="filePath">目标配置文件路径(包含文件名)。</param>
+        /// <exception cref="ArgumentNullException">当 <paramref name="p"/> 为 null 时抛出。</exception>
         public void SaveProduct(ProductModel p,string filePath)
         {
             if (p == null) throw new ArgumentNullException(nameof(p));
@@ -376,11 +479,21 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 异步按约定目录保存产品。
+        /// </summary>
+        /// <param name="id">产品 ID。</param>
+        /// <returns>表示异步操作的任务。</returns>
         public Task SaveProductAsync(Guid id)
         {
             return Task.Run(() => SaveProduct(id));
         }
 
+        /// <summary>
+        /// 从指定路径加载产品配置(仅反序列化 JSON,不加载 ToolBlock 文件)。
+        /// </summary>
+        /// <param name="filePath">产品配置文件路径。</param>
+        /// <returns>加载得到的 <see cref="ProductModel"/>,若文件不存在则返回 null。</returns>
         public ProductModel LoadProduct(string filePath)
         {
             if (!File.Exists(filePath)) return null;
@@ -388,6 +501,11 @@ namespace TeamAAS_VP.Services
             return prd;
         }
 
+        /// <summary>
+        /// 根据内存中已注册的产品 ID,从约定目录加载其视觉 ToolBlock 并返回产品实例。
+        /// </summary>
+        /// <param name="id">产品 ID(必须已在内存中注册)。</param>
+        /// <returns>加载后的 <see cref="ProductModel"/>,若未注册或找不到则返回 null。</returns>
         public ProductModel LoadProduct(Guid id)
         {
             var p = GetProduct(id);
@@ -397,8 +515,6 @@ namespace TeamAAS_VP.Services
             var dir = Path.GetDirectoryName(filePath);
             if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
 
-
-
             // 加载每个相机的流程ToolBlock
             foreach (var camera in p.CameraProcedures)
             {
@@ -412,11 +528,20 @@ namespace TeamAAS_VP.Services
             return p;
         }
 
+        /// <summary>
+        /// 异步加载产品视觉流程(包装同步实现)。
+        /// </summary>
+        /// <param name="id">产品 ID。</param>
+        /// <returns>包含加载后产品的任务。</returns>
         public Task<ProductModel> LoadProductAsync(Guid id)
         {
             return Task.Run(() => LoadProduct(id));
         }
 
+        /// <summary>
+        /// 从系统配置中读取上次打开的产品并尝试加载该产品到内存,并将其设置为当前产品。
+        /// </summary>
+        /// <returns>若成功加载并设置则返回 (id, name),否则返回 (Guid.Empty, string.Empty)。</returns>
         public (Guid id, string name) LoadLastProduct()
         {
             // 获取最后打开的产品ID
@@ -445,12 +570,21 @@ namespace TeamAAS_VP.Services
             return (Guid.Empty, string.Empty);
         }
 
+        /// <summary>
+        /// 异步加载最后打开的产品(包装同步实现)。
+        /// </summary>
+        /// <returns>包含 (id, name) 的任务。</returns>
         public Task<(Guid id, string name)> LoadLastProductAsync()
         {
             // 可以使用 Task.FromResult,因为内部是同步且快速的文件检测/读取
             return Task.Run(() => LoadLastProduct());
         }
 
+        /// <summary>
+        /// 将指定产品设置为当前产品,并更新系统配置中的 LastOpenedProductId,同时触发 OnProductChanged 事件。
+        /// 仅当内存中存在该产品时生效。
+        /// </summary>
+        /// <param name="id">要设置为当前的产品 ID。</param>
         public void SetCurrentProduct(Guid id)
         {
             lock (_sync)
@@ -475,6 +609,10 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 获取当前产品模型(若未设置则返回 null)。
+        /// </summary>
+        /// <returns>当前产品或 null。</returns>
         public ProductModel GetCurrentProduct()
         {
             lock (_sync)
@@ -485,6 +623,12 @@ namespace TeamAAS_VP.Services
             }
         }
 
+        /// <summary>
+        /// 复制已有产品并为副本分配新的 ID 与名称,同时保存副本并返回新副本的 ID。
+        /// </summary>
+        /// <param name="id">源产品 ID。</param>
+        /// <param name="newName">新产品的名称。</param>
+        /// <returns>新产品的 ID,失败时返回 Guid.Empty。</returns>
         public Guid CopyProduct(Guid id, string newName)
         {
             var p = GetProduct(id);
@@ -497,15 +641,21 @@ namespace TeamAAS_VP.Services
             return clone.ID;
         }
 
+        /// <summary>
+        /// 异步复制产品。
+        /// </summary>
+        /// <param name="id">源产品 ID。</param>
+        /// <param name="newName">新名称。</param>
+        /// <returns>包含新产品 ID 的任务。</returns>
         public Task<Guid> CopyProductAsync(Guid id, string newName)
         {
             return Task.Run(() => CopyProduct(id, newName));
         }
 
         /// <summary>
-        /// 永久删除产品
+        /// 永久删除产品:从内存中移除并递归删除其在磁盘上的文件夹及所有内容。
         /// </summary>
-        /// <param name="id"></param>
+        /// <param name="id">要删除的产品 ID。</param>
         public void DeleteProduct(Guid id)
         {
             //获取产品
@@ -521,19 +671,20 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 永久删除产品
+        /// 异步删除产品(包装同步实现)。
         /// </summary>
-        /// <param name="id"></param>
+        /// <param name="id">产品 ID。</param>
+        /// <returns>表示异步删除操作的任务。</returns>
         public Task DeleteProductAsync(Guid id)
         {
             return Task.Run(() => DeleteProduct(id));
         }
 
         /// <summary>
-        /// 对指定产品进行重命名
+        /// 对指定产品进行重命名:更新内存模型并在磁盘上重命名对应文件夹(若存在),然后保存产品清单与产品配置。
         /// </summary>
-        /// <param name="id"></param>
-        /// <param name="newName"></param>
+        /// <param name="id">产品 ID。</param>
+        /// <param name="newName">新的产品名称。</param>
         public void RenameProduct(Guid id, string newName)
         {
             // 获取产品
@@ -558,20 +709,20 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 对指定产品进行重命名
+        /// 异步重命名产品(包装同步实现)。
         /// </summary>
-        /// <param name="id"></param>
-        /// <param name="newName"></param>
-        /// <returns></returns>
+        /// <param name="id">产品 ID。</param>
+        /// <param name="newName">新的名称。</param>
+        /// <returns>表示异步操作的任务。</returns>
         public Task RenameProductAsync(Guid id, string newName)
         {
             return Task.Run(() => RenameProduct(id, newName));
         }
 
         /// <summary>
-        /// 更新产品
+        /// 更新内存中产品对象并将其保存到磁盘(若存在)。
         /// </summary>
-        /// <param name="product"></param>
+        /// <param name="product">要更新的产品模型(不能为空)。</param>
         public void UpdateProduct(ProductModel product)
         {
             if (product == null) throw new ArgumentNullException(nameof(product));
@@ -587,21 +738,21 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 更新产品
+        /// 异步更新产品(包装同步实现)。
         /// </summary>
-        /// <param name="product"></param>
-        /// <returns></returns>
+        /// <param name="product">产品模型。</param>
+        /// <returns>表示异步操作的任务。</returns>
         public Task UpdateProductAsync(ProductModel product)
         {
             return Task.Run(() => UpdateProduct(product));
         }
 
         /// <summary>
-        /// 根据指定的模板名称创建产品
+        /// 根据指定的模板名称创建产品并保存。模板文件位于 <see cref="ProductTemplatePath"/>。
         /// </summary>
-        /// <param name="templateName"></param>
-        /// <param name="productName"></param>
-        /// <returns></returns>
+        /// <param name="templateName">模板名称(对应子文件夹与 cfg 文件名)。</param>
+        /// <param name="productName">新产品名称。</param>
+        /// <returns>创建并返回的新产品模型;若模板不存在则返回 null。</returns>
         public ProductModel CreateProductByTemplateName(string templateName, string productName)
         {
             // 产品模板路径为:ProductTemplatePath//templateName//templateName.cfg
@@ -616,21 +767,21 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 根据指定的模板名称异步创建产品
+        /// 异步根据模板创建产品。
         /// </summary>
-        /// <param name="templateName"></param>
-        /// <param name="productName"></param>
-        /// <returns></returns>
+        /// <param name="templateName">模板名。</param>
+        /// <param name="productName">产品名。</param>
+        /// <returns>包含新产品模型的任务。</returns>
         public Task<ProductModel> CreateProductByTemplateNameAsync(string templateName, string productName)
         {
             return Task.Run(() => CreateProductByTemplateName(templateName, productName));
         }
 
         /// <summary>
-        /// 增加相机时,需要对产品的相机流程进行增加
+        /// 在所有产品中增加一个相机流程(当系统新增相机时调用),并保存每个产品的配置。
         /// </summary>
-        /// <param name="cameraId"></param>
-        /// <param name="cameraName"></param>
+        /// <param name="cameraId">相机 ID。</param>
+        /// <param name="cameraName">相机名称。</param>
         public void AddCameraToProduct(Guid cameraId, string cameraName)
         {
             //获取所有产品
@@ -649,20 +800,20 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 增加相机时,需要对产品的相机流程进行增加
+        /// 异步在所有产品中增加相机流程。
         /// </summary>
-        /// <param name="cameraId"></param>
-        /// <param name="cameraName"></param>
-        /// <returns></returns>
+        /// <param name="cameraId">相机 ID。</param>
+        /// <param name="cameraName">相机名。</param>
+        /// <returns>表示异步操作的任务。</returns>
         public Task AddCameraToProductAsync(Guid cameraId, string cameraName)
         {
             return Task.Run(() => AddCameraToProduct(cameraId, cameraName));
         }
 
         /// <summary>
-        /// 删除相机时,需要对产品的相机流程进行删除
+        /// 在所有产品中移除指定相机对应的流程,并保存产品文件。
         /// </summary>
-        /// <param name="cameraId"></param>
+        /// <param name="cameraId">要移除的相机 ID。</param>
         public void RemoveCameraFromProduct(Guid cameraId)
         {
             var products= GetAllProducts();
@@ -679,20 +830,20 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 删除相机时,需要对产品的相机流程进行删除
+        /// 异步在所有产品中移除相机流程。
         /// </summary>
-        /// <param name="cameraId"></param>
-        /// <returns></returns>
+        /// <param name="cameraId">相机 ID。</param>
+        /// <returns>任务。</returns>
         public Task RemoveCameraFromProductAsync(Guid cameraId)
         {
             return Task.Run(() => RemoveCameraFromProduct(cameraId));
         }
 
         /// <summary>
-        /// 修改相机名称时,需要对产品的相机流程进行修改
+        /// 更新所有产品中指定相机的名称,并在磁盘上重命名对应的相机文件夹(若存在)。
         /// </summary>
-        /// <param name="cameraId"></param>
-        /// <param name="newCameraName"></param>
+        /// <param name="cameraId">相机 ID。</param>
+        /// <param name="newCameraName">新相机名称。</param>
         public void UpdateCameraNameInProduct(Guid cameraId, string newCameraName)
         {
             var products= GetAllProducts();
@@ -730,21 +881,21 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 修改相机名称时,需要对产品的相机流程进行修改
+        /// 异步更新所有产品中的相机名称。
         /// </summary>
-        /// <param name="cameraId"></param>
-        /// <param name="newCameraName"></param>
-        /// <returns></returns>
+        /// <param name="cameraId">相机 ID。</param>
+        /// <param name="newCameraName">新名称。</param>
+        /// <returns>任务。</returns>
         public Task UpdateCameraNameInProductAsync(Guid cameraId, string newCameraName)
         {
             return Task.Run(() => UpdateCameraNameInProduct(cameraId, newCameraName));
         }
 
         /// <summary>
-        /// 增加机器人时,需要对产品的机器人点位进行增加
+        /// 在所有产品中增加机器人点位表条目并保存对应产品文件(当系统新增机器人或点位时调用)。
         /// </summary>
-        /// <param name="robotId"></param>
-        /// <param name="robotPointName"></param>
+        /// <param name="robotId">机器人 ID。</param>
+        /// <param name="robotPointName">点位名称。</param>
         public void AddRobotPointToProduct(Guid robotId, string robotPointName)
         {
             //获取所有产品
@@ -763,20 +914,20 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 增加机器人时,需要对产品的机器人点位进行增加
+        /// 异步为所有产品增加机器人点位。
         /// </summary>
-        /// <param name="robotId"></param>
-        /// <param name="robotPointName"></param>
-        /// <returns></returns>
+        /// <param name="robotId">机器人 ID。</param>
+        /// <param name="robotPointName">点位名。</param>
+        /// <returns>任务。</returns>
         public Task AddRobotPointToProductAsync(Guid robotId, string robotPointName)
         {
             return Task.Run(() => AddRobotPointToProduct(robotId, robotPointName));
         }
 
         /// <summary>
-        /// 删除机器人时,需要对产品的机器人点位进行删除
+        /// 在所有产品中移除指定机器人相关的点位配置并保存产品文件。
         /// </summary>
-        /// <param name="robotId"></param>
+        /// <param name="robotId">机器人 ID。</param>
         public void RemoveRobotPointFromProduct(Guid robotId)
         {
             var products = GetAllProducts();
@@ -793,20 +944,20 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 删除机器人时,需要对产品的机器人点位进行删除
+        /// 异步在所有产品中移除机器人点位。
         /// </summary>
-        /// <param name="robotId"></param>
-        /// <returns></returns>
+        /// <param name="robotId">机器人 ID。</param>
+        /// <returns>任务。</returns>
         public Task RemoveRobotPointFromProductAsync(Guid robotId)
         {
             return Task.Run(() => RemoveRobotPointFromProduct(robotId));
         }
 
         /// <summary>
-        /// 修改机器人名称时,需要对产品的机器人点位进行修改
+        /// 更新所有产品中指定机器人点位的名称并保存产品配置。
         /// </summary>
-        /// <param name="robotId"></param>
-        /// <param name="newRobotPointName"></param>
+        /// <param name="robotId">机器人 ID。</param>
+        /// <param name="newRobotPointName">新的点位名称。</param>
         public void UpdateRobotPointNameInProduct(Guid robotId, string newRobotPointName)
         {
             var products = GetAllProducts();
@@ -827,22 +978,23 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 修改机器人名称时,需要对产品的机器人点位进行修改
+        /// 异步更新机器人点位名称。
         /// </summary>
-        /// <param name="robotId"></param>
-        /// <param name="newRobotPointName"></param>
-        /// <returns></returns>
+        /// <param name="robotId">机器人 ID。</param>
+        /// <param name="newRobotPointName">新名称。</param>
+        /// <returns>任务。</returns>
         public Task UpdateRobotPointNameInProductAsync(Guid robotId, string newRobotPointName)
         {
             return Task.Run(() => UpdateRobotPointNameInProduct(robotId, newRobotPointName));
         }
 
         /// <summary>
-        /// 加载指定产品流程的视觉工具
+        /// 加载指定产品的某个相机流程的视觉工具(ToolBlock)。
+        /// 若视觉工具文件不存在则尝试从模板加载并保存到目标路径;若模板也不存在则抛出 <see cref="FileNotFoundException"/>。
         /// </summary>
-        /// <param name="productid"></param>
-        /// <param name="cameraid"></param>
-        /// <param name="prcname"></param>
+        /// <param name="productid">产品 ID。</param>
+        /// <param name="cameraid">相机 ID。</param>
+        /// <param name="prcname">流程名称。</param>
         public void LoadProcedureVisionTools(Guid productid, Guid cameraid, string prcname)
         {
             //获取产品
@@ -877,12 +1029,12 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 加载指定产品流程的视觉工具
+        /// 异步加载指定产品流程的视觉工具,返回是否成功。
         /// </summary>
-        /// <param name="productid"></param>
-        /// <param name="cameraid"></param>
-        /// <param name="prcname"></param>
-        /// <returns></returns>
+        /// <param name="productid">产品 ID。</param>
+        /// <param name="cameraid">相机 ID。</param>
+        /// <param name="prcname">流程名称。</param>
+        /// <returns>成功返回 true,发生异常返回 false。</returns>
         public Task<bool> LoadProcedureVisionToolsAsync(Guid productid, Guid cameraid, string prcname)
         {
             return Task.Run(() =>
@@ -901,9 +1053,10 @@ namespace TeamAAS_VP.Services
 
 
         /// <summary>
-        /// 移除指定文件夹路径下的所有文件和文件夹,及其本身,子文件夹递归删除
+        /// 递归删除指定目录及其所有子目录和文件。
+        /// 注意:此操作会永久删除磁盘上的文件,请谨慎调用。
         /// </summary>
-        /// <param name="dirPath"></param>
+        /// <param name="dirPath">要删除的目录路径。</param>
         private void DeleteDirectoryRecursively(string dirPath)
         {
             if (Directory.Exists(dirPath))
@@ -925,14 +1078,17 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 产品切换方法,触发事件
+        /// 触发当前产品切换事件的内部方法(线程安全地调用事件)。
         /// </summary>
-        /// <param name="product"></param>
+        /// <param name="product">新的当前产品。</param>
         private void OnCurrentProductChanged(ProductModel product)
         {
             OnProductChanged?.Invoke(product);
         }
 
+        /// <summary>
+        /// 释放资源,清空内存中的产品集合并抑制终结化。
+        /// </summary>
         public void Dispose()
         {
             RemoveAllProducts();

+ 162 - 189
TeamAAS-VM/Services/RemoteCommandService.cs

@@ -33,12 +33,14 @@ using System.Collections.Concurrent;
 namespace TeamAAS_VP.Services
 {
     /// <summary>
-    /// Lightweight migration of Management.ExecuteRemoteCommand into a dedicated service.
-    /// This service depends on existing domain services and will call into them.
-    /// Only the core helpers used by the old ExecuteRemoteCommand are migrated (LoadProduct, GetCurrentProductName, GetCurrentProductID, GetCurrentProductSpeed, etc.).
+    /// 远程命令服务:
+    /// 负责解析并执行外部传入的命令字符串(例如 LoadProduct、视觉流程触发、清料任务等)。
+    /// 该类封装了原 Management.ExecuteRemoteCommand 的精简迁移实现,依赖于领域服务(产品、配置、校准、Feeder、相机、机器人、系统数据库等)。
+    /// 线程安全:通过内部计数器和 ConcurrentDictionary 跟踪正在运行的任务。
     /// </summary>
     public class RemoteCommandService : IRemoteCommandService, INotifyPropertyChanged
     {
+        // 依赖服务,由构造函数注入
         private readonly IEventAggregator _eventAggregator;
         private readonly IProductService _productService;
         private readonly IConfigService _configService;
@@ -51,35 +53,60 @@ namespace TeamAAS_VP.Services
         private readonly ISystemDatabaseService _systemDatabaseService;
 
         /// <summary>
-        /// 计时器任务取消令牌
+        /// 节拍计时任务的取消令牌,用于 StartCycleTiming/StopCycleTiming。
         /// </summary>
         private CancellationTokenSource _cycleTimingCts;
+
         /// <summary>
-        /// 清料任务取消令牌字典,Key为任务编号
+        /// 清料任务的取消令牌字典,Key 为任务编号(TaskCode)。
+        /// 当需要取消特定清料任务时,通过该字典查找并调用 Cancel。
         /// </summary>
         private readonly ConcurrentDictionary<int, CancellationTokenSource> _clearMaterialCtsDict = new ConcurrentDictionary<int, CancellationTokenSource>();
 
-        // 字段
+        // 内部执行状态计数器和任务跟踪集合
         private int _activeCount;
         private int _visionActiveCount;
         private readonly ConcurrentDictionary<Guid, Task> _runningTasks = new ConcurrentDictionary<Guid, Task>();
 
-        // 实现 INotifyPropertyChanged
+        // INotifyPropertyChanged 实现
         public event PropertyChangedEventHandler PropertyChanged;
         private void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
 
-        // 公开属性(只读)
+        /// <summary>
+        /// 标识是否存在任意正在执行的命令(非视觉与视觉命令)。
+        /// 由 TrackExecutionAsync 自动维护。
+        /// </summary>
         public bool IsExecuting => Volatile.Read(ref _activeCount) > 0;
+
+        /// <summary>
+        /// 标识是否存在正在执行的视觉指令。
+        /// 由 TrackExecutionAsync 在 isVision 参数为 true 时自动维护。
+        /// </summary>
         public bool IsExecutingVisionCommand => Volatile.Read(ref _visionActiveCount) > 0;
 
-        // 可选事件(如果不想用 INotifyPropertyChanged)
+        /// <summary>
+        /// 当执行状态发生变化(开始/结束)时触发(便于传统事件订阅)。
+        /// </summary>
         public event EventHandler ExecutionStateChanged;
 
         /// <summary>
-        /// 计时器计时通知事件
+        /// 节拍计时的定时回调事件,通知订阅者当前已用时。
         /// </summary>
         public event EventHandler<TimeSpan> CycleTimingTicked;
 
+        /// <summary>
+        /// 构造函数,注入必要的系统服务。
+        /// </summary>
+        /// <param name="eventAggregator">事件聚合器,用于发布 UI 渲染/消息等事件。</param>
+        /// <param name="regionManager">区域管理器,用于 UI 区域导航(在脚本中可能使用)。</param>
+        /// <param name="productService">产品服务,用于加载/查询产品信息。</param>
+        /// <param name="configService">配置服务,用于读取机器人/任务/Feeder 等配置。</param>
+        /// <param name="calibrationService">校准服务,用于像素到机器人坐标的转换。</param>
+        /// <param name="feederService">Feeder 服务,控制料仓/振动等。</param>
+        /// <param name="cameraService">相机服务,用于图像采集。</param>
+        /// <param name="robotService">机器人服务,提供机器人状态码/调试模式等信息。</param>
+        /// <param name="container">IOC 容器提供者,用于脚本执行时解析依赖。</param>
+        /// <param name="systemDatabaseService">系统数据库服务,用于记录产量/报警等。</param>
         public RemoteCommandService(IEventAggregator eventAggregator, IRegionManager regionManager, IProductService productService, IConfigService configService, ICalibrationService calibrationService,
             IFeederService feederService, ICameraService cameraService, IRobotService robotService, IContainerProvider container, ISystemDatabaseService systemDatabaseService)
         {
@@ -95,15 +122,27 @@ namespace TeamAAS_VP.Services
             _systemDatabaseService = systemDatabaseService;
         }
 
+        /// <summary>
+        /// 占位 Dispose 方法(当前无资源释放逻辑)。
+        /// </summary>
         public void Dispose() { }
 
-        // 通用包裹器:包装并追踪任意异步工作
+        /// <summary>
+        /// 通用的异步执行包装器。该方法:
+        /// - 为每次执行生成唯一 id 并加入运行任务集合以便跟踪
+        /// - 维护 _activeCount 和 _visionActiveCount
+        /// - 在任务开始/结束时触发属性变化和 ExecutionStateChanged 事件
+        /// - 返回工作 delegate 的结果
+        /// </summary>
+        /// <typeparam name="T">工作返回类型</typeparam>
+        /// <param name="work">以 CancellationToken 为参数的异步工作委托</param>
+        /// <param name="isVision">指示该工作是否为视觉命令(影响视觉计数器)</param>
+        /// <param name="ct">外部取消令牌,传递给工作委托</param>
+        /// <returns>工作委托的返回值</returns>
         private async Task<T> TrackExecutionAsync<T>(Func<CancellationToken, Task<T>> work, bool isVision, CancellationToken ct)
         {
-            // create id for this run (可用于日志/诊断)
             var id = Guid.NewGuid();
 
-            // 增加计数器并通知
             Interlocked.Increment(ref _activeCount);
             if (isVision) Interlocked.Increment(ref _visionActiveCount);
             OnPropertyChanged(nameof(IsExecuting));
@@ -112,7 +151,6 @@ namespace TeamAAS_VP.Services
 
             try
             {
-                // 记录 task(便于外部查询)
                 var task = work(ct);
                 _runningTasks.TryAdd(id, task);
                 var result = await task.ConfigureAwait(false);
@@ -120,32 +158,32 @@ namespace TeamAAS_VP.Services
             }
             finally
             {
-                // 减少计数器并通知
                 Interlocked.Decrement(ref _activeCount);
                 if (isVision) Interlocked.Decrement(ref _visionActiveCount);
-
-                // 移除记录(如果仍存在)
                 _runningTasks.TryRemove(id, out _);
-
                 OnPropertyChanged(nameof(IsExecuting));
                 OnPropertyChanged(nameof(IsExecutingVisionCommand));
                 ExecutionStateChanged?.Invoke(this, EventArgs.Empty);
             }
         }
 
-
+        /// <summary>
+        /// 解析并执行传入的命令字符串。命令以逗号分隔,第一个字段为命令名称,后续为参数。
+        /// 支持多种内建命令(示例:LoadProduct、GetProductName、CycleTime、Record、StartClearMaterial 等),以及映射到产品 CameraProcedures 的自定义视觉触发命令。
+        /// </summary>
+        /// <param name="command">原始命令字符串</param>
+        /// <param name="cancellationToken">外部取消令牌</param>
+        /// <param name="callback">用于将执行结果回传(比如发送到调用方)的回调,参数为单行响应字符串</param>
+        /// <returns>完成任务</returns>
         public async Task ExecuteAsync(string command, CancellationToken cancellationToken, Action<string> callback)
         {
             var items = (command ?? string.Empty).Split(new[] { ',' }, StringSplitOptions.None).Select(s => s.Trim()).ToArray();
             if (items.Length == 0) return;
 
-            //触发命令的第一个参数作为命令类型
             string triggerCommand = items[0];
-            //剩余参数作为命令参数
             string[] commandParams = items.Skip(1).ToArray();
 
             string response = string.Empty;
-            //根据命令类型执行不同的命令
             switch (triggerCommand)
             {
                 case "LoadProduct":
@@ -162,7 +200,6 @@ namespace TeamAAS_VP.Services
                     callback?.Invoke($"LoadProduct,{response}");
                     break;
                 case "GetProductName":
-                    //如果没有参数,则获取当前产品名称
                     if (commandParams.Length < 1)
                     {
                         response = await TrackExecutionAsync(async ct =>
@@ -179,7 +216,6 @@ namespace TeamAAS_VP.Services
                         }
                         break;
                     }
-                    //如果有参数,则通过产品编号获取产品名称
                     else if (commandParams.Length == 1)
                     {
                         if (!int.TryParse(commandParams[0], out int productNumber))
@@ -207,7 +243,6 @@ namespace TeamAAS_VP.Services
                         return;
                     }
                 case "GetProductID":
-                    //如果没有参数,则获取当前产品编号
                     if (commandParams.Length < 1)
                     {
                         response = await TrackExecutionAsync(async ct =>
@@ -225,7 +260,6 @@ namespace TeamAAS_VP.Services
                         }
                         break;
                     }
-                    //如果有参数,则通过产品名称获取产品编号
                     else if (commandParams.Length == 1)
                     {
                         response = await TrackExecutionAsync(async ct =>
@@ -490,7 +524,6 @@ namespace TeamAAS_VP.Services
                     }, isVision: false, cancellationToken);
                     callback?.Invoke($"StopClearMaterial,{response}");
                     break;
-                //获取机器人是否进入调试模式
                 case "RobotIsDebugMode":
                     if (commandParams.Length < 1)
                     {
@@ -505,21 +538,17 @@ namespace TeamAAS_VP.Services
                     callback?.Invoke($"RobotIsDebugMode,{response}");
                     break;
                 default:
-                    //获取当前产品
                     var currentProduct = _productService.GetCurrentProduct();
                     if (currentProduct == null) return;
 
-                    // 查找对应的流程
                     var procedure = currentProduct.CameraProcedures
                         .SelectMany(cp => cp.ProcedureModels)
                         .FirstOrDefault(p => p.TriggerCommand == triggerCommand);
                     if (procedure == null) return;
 
-                    // 假设 ExecuteFeederVisionTaskAsync 返回 (bool, string[])
                     var visionResult = await TrackExecutionAsync(async ct =>
                     {
                         var r = await ExecuteVisionTaskAsync(procedure, commandParams, ct);
-                        // 如果需要转换类型,包装成 Tuple<bool, string[]>
                         return Tuple.Create(r.IsSucceed, r.Response);
                     }, isVision: true, cancellationToken);
                     if (visionResult.Item1)
@@ -532,7 +561,7 @@ namespace TeamAAS_VP.Services
                     else
                     {
                         string msg = string.Empty;
-                        if (visionResult.Item2 != null && visionResult.Item2.Length>0)
+                        if (visionResult.Item2 != null && visionResult.Item2.Length > 0)
                         {
                             msg = visionResult.Item2[0];
                         }
@@ -543,30 +572,29 @@ namespace TeamAAS_VP.Services
             }
         }
 
-        //加载产品
+        /// <summary>
+        /// 根据产品名称或编号加载并设置当前产品。
+        /// 若传入字符串可解析为 int,则先按编号查找产品,否则按名称查找。
+        /// </summary>
+        /// <param name="productName">产品名称或编号字符串</param>
+        /// <param name="cancellationToken">取消令牌</param>
+        /// <returns>加载成功返回 true,否则 false</returns>
         public async Task<bool> LoadProductAsync(string productName, CancellationToken cancellationToken)
         {
-            //检查是否存在该名称的产品
             bool exists = _productService.ContainsProductByName(productName);
             if (exists)
             {
-                //获取产品名称对应的产品
                 var product = _productService.GetAllProducts().FirstOrDefault(p => p.Name == productName);
-                //加载产品
                 await _productService.LoadProductAsync(product.ID);
-                //设置为当前产品
                 _productService.SetCurrentProduct(product.ID);
                 return true;
             }
-            //获取产品编号对应的产品
             if (int.TryParse(productName, out int productNumber))
             {
                 var product = _productService.GetAllProducts().FirstOrDefault(p => p.NumberCode == productNumber);
                 if (product != null)
                 {
-                    //加载产品
                     await _productService.LoadProductAsync(product.ID);
-                    //设置为当前产品
                     _productService.SetCurrentProduct(product.ID);
                     return true;
                 }
@@ -574,7 +602,9 @@ namespace TeamAAS_VP.Services
             return false;
         }
 
-        //通过产品编号获取产品名称
+        /// <summary>
+        /// 通过产品编号获取产品名称。
+        /// </summary>
         public Task<string> GetProductNameByNumberAsync(int productNumber, CancellationToken cancellationToken)
         {
             var product = _productService.GetAllProducts().FirstOrDefault(p => p.NumberCode == productNumber);
@@ -586,7 +616,9 @@ namespace TeamAAS_VP.Services
             return Task.FromResult(string.Empty);
         }
 
-        //获取当前的产品名称
+        /// <summary>
+        /// 获取当前被设置为活动的产品名称。如果没有则返回空字符串。
+        /// </summary>
         public Task<string> GetCurrentProductNameAsync(CancellationToken cancellationToken)
         {
             var currentProduct = _productService.GetCurrentProduct();
@@ -597,21 +629,23 @@ namespace TeamAAS_VP.Services
             return Task.FromResult(string.Empty);
         }
 
-        //通过产品名称获取产品编号
+        /// <summary>
+        /// 通过产品名称获取其编号(NumberCode)。找不到则返回 -1。
+        /// </summary>
         public Task<int> GetProductNumberByNameAsync(string productName, CancellationToken cancellationToken)
         {
-            //检查是否存在该名称的产品
             bool exists = _productService.ContainsProductByName(productName);
             if (exists)
             {
-                //获取产品名称对应的产品
                 var product = _productService.GetAllProducts().FirstOrDefault(p => p.Name == productName);
                 return Task.FromResult(product.NumberCode);
             }
             return Task.FromResult(-1);
         }
 
-        //获取当前的产品编号
+        /// <summary>
+        /// 获取当前产品编号(NumberCode)。找不到则返回 -1。
+        /// </summary>
         public Task<int> GetCurrentProductNumberAsync(CancellationToken cancellationToken)
         {
             var currentProduct = _productService.GetCurrentProduct();
@@ -622,44 +656,33 @@ namespace TeamAAS_VP.Services
             return Task.FromResult(-1);
         }
 
-        //通过机器人编号和点位编号获取机器人点位信息
+        /// <summary>
+        /// 根据机器人编号和点位编号获取点位信息(字符串表示),查找逻辑基于当前产品的 RobotPoint 配置。
+        /// </summary>
         public Task<string> GetRobotPointInfoAsync(int robotNumber, int pointNumber, CancellationToken cancellationToken)
         {
-            //获取当前产品
             var currentProduct = _productService.GetCurrentProduct();
             if (currentProduct == null) return Task.FromResult(string.Empty);
-            //获取机器人编号对应的机器人信息
             var robot = _configService.GetAllRobots().FirstOrDefault(r => r.RobotNo == robotNumber);
             if (robot == null) return Task.FromResult(string.Empty);
-            //获取机器人点位信息
             var robotPoint = currentProduct.RobotPoint.Robots.FirstOrDefault(rp => rp.Id == robot.Id);
             if (robotPoint == null) return Task.FromResult(string.Empty);
-            //获取点位信息
             var point = robotPoint.Points.FirstOrDefault(p => p.Number == pointNumber);
             if (point == null) return Task.FromResult(string.Empty);
             return Task.FromResult(point.ToString());
         }
 
-        // <summary>
-        /// 通过机器人编号和托盘编号、点编号获取机器人托盘点位信息
+        /// <summary>
+        /// 通过机器人编号、托盘编号和点编号获取托盘点位信息,返回格式化字符串。
         /// </summary>
-        /// <param name="robotNumber">机器人编号</param>
-        /// <param name="palletNumber">托盘编号</param>
-        /// <param name="pointNumber">点编号</param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
         public Task<string> GetRobotPalletPointInfoAsync(int robotNumber, int palletNumber, int pointNumber, CancellationToken cancellationToken)
         {
-            // 获取当前产品
             var currentProduct = _productService.GetCurrentProduct();
             if (currentProduct == null) return Task.FromResult(string.Empty);
-            //获取机器人编号对应的机器人信息
             var robot = _configService.GetAllRobots().FirstOrDefault(r => r.RobotNo == robotNumber);
             if (robot == null) return Task.FromResult(string.Empty);
-            //获取机器人点位信息
             var robotPoint = currentProduct.RobotPoint.Robots.FirstOrDefault(rp => rp.Id == robot.Id);
             if (robotPoint == null) return Task.FromResult(string.Empty);
-            //获取托盘
             var pallet = robotPoint.Pallets.FirstOrDefault(p => p.PalletNo == palletNumber);
             if (pallet == null) return Task.FromResult(string.Empty);
             PalletTool palletTool = new PalletTool(pallet);
@@ -669,11 +692,12 @@ namespace TeamAAS_VP.Services
             return Task.FromResult(response);
         }
 
-        //开始节拍计时
+        /// <summary>
+        /// 开始节拍计时。返回是否成功(始终 true)。
+        /// 会启动后台定时任务并周期性触发 CycleTimingTicked 事件。
+        /// </summary>
         public Task<bool> StartCycleTimingAsync(CancellationToken cancellationToken)
         {
-            //TODO: 实现节拍计时开始逻辑
-            //如果当前已经在计时,则取消之前的计时,然后重新开始计时
             if (_cycleTimingCts != null)
             {
                 _cycleTimingCts.Cancel();
@@ -688,13 +712,15 @@ namespace TeamAAS_VP.Services
                 {
                     var elapsed = DateTime.UtcNow - startTime;
                     OnCycleTimingTicked(elapsed);
-                    await Task.Delay(100, token); // 每100毫秒更新一次
+                    await Task.Delay(100, token);
                 }
             }, token);
             return Task.FromResult(true);
         }
 
-        //停止节拍计时
+        /// <summary>
+        /// 停止节拍计时并清理取消令牌。
+        /// </summary>
         public Task<bool> StopCycleTimingAsync(CancellationToken cancellationToken)
         {
             if (_cycleTimingCts != null)
@@ -706,7 +732,9 @@ namespace TeamAAS_VP.Services
             return Task.FromResult(true);
         }
 
-        //创建一条生产计数,传入计数项
+        /// <summary>
+        /// 新增一条生产计数记录,数量固定为 1。
+        /// </summary>
         public Task<bool> CreateProductionCountAsync(string countItem, CancellationToken cancellationToken)
         {
             _systemDatabaseService.RecordProductionAsync(new ProductionRecord
@@ -719,30 +747,32 @@ namespace TeamAAS_VP.Services
             return Task.FromResult(true);
         }
 
-        //获取当前生产计数
+        /// <summary>
+        /// 查询当前产品在指定分类下的累计产量。
+        /// </summary>
         public async Task<int> GetCurrentProductionCountAsync(string countItem, CancellationToken cancellationToken)
         {
-            //获取当前产品
             var currentProduct = _productService.GetCurrentProduct();
             if (currentProduct == null) return 0;
-            //获取分类集合
             var records = _systemDatabaseService.GetProductCategoriesAsync(currentProduct.Name);
-            //计算指定分类的总产能
-            int totalCount =await _systemDatabaseService.GetTotalProductionAsync(currentProduct.Name, new List<string> { countItem });
+            int totalCount = await _systemDatabaseService.GetTotalProductionAsync(currentProduct.Name, new List<string> { countItem });
             return totalCount;
         }
 
-        //重置生产计数
+        /// <summary>
+        /// 重置当前产品的生产统计(通过系统 DB 服务)。
+        /// </summary>
         public async Task<bool> ResetProductionCountAsync(string countItem, CancellationToken cancellationToken)
         {
-            //获取当前产品
             var currentProduct = _productService.GetCurrentProduct();
             if (currentProduct == null) return false;
             await _systemDatabaseService.ResetProductionAsync(currentProduct.Name);
             return true;
         }
 
-        //创建一条报警记录
+        /// <summary>
+        /// 记录一条报警信息到系统数据库,并通过事件发布错误级别的任务消息。
+        /// </summary>
         public Task<bool> CreateAlarmRecordAsync(string alarmCode, string alarmDescription, CancellationToken cancellationToken)
         {
             SendTaskMessage(alarmDescription, MessageLevel.Error);
@@ -756,21 +786,23 @@ namespace TeamAAS_VP.Services
             return Task.FromResult(true);
         }
 
-        //创建一条运行信息记录
+        /// <summary>
+        /// 发布运行信息日志(仅消息推送,不持久化)。
+        /// </summary>
         public Task<bool> CreateOperationLogAsync(string logMessage, CancellationToken cancellationToken)
         {
             SendTaskMessage(logMessage, MessageLevel.Info);
             return Task.FromResult(true);
         }
 
-        //获取当前产品指定编号的全局参数
+        /// <summary>
+        /// 获取当前产品的全局参数,支持按索引(1-4)返回指定子项,或返回完整 ToString。
+        /// </summary>
         public Task<string> GetGlobalParameterAsync(int parameterNumber, int itemIndex, CancellationToken cancellationToken)
         {
-            //获取当前产品
             var currentProduct = _productService.GetCurrentProduct();
             if (currentProduct == null) return Task.FromResult(string.Empty);
             if (currentProduct.GlobalParamListCollection == null) return Task.FromResult(string.Empty);
-            //获取产品全局参数
             var globalParam = currentProduct.GlobalParamListCollection.FirstOrDefault(p => p.Number == parameterNumber);
             if (globalParam == null) return Task.FromResult(string.Empty);
             string response = string.Empty;
@@ -797,28 +829,28 @@ namespace TeamAAS_VP.Services
             return Task.FromResult(response);
         }
 
-        //获取当前产品的运行速度
+        /// <summary>
+        /// 返回当前产品的速度/加速度等运行参数的格式化字符串。
+        /// </summary>
         public Task<string> GetOperationSpeedAsync(CancellationToken cancellationToken)
         {
-            //获取当前产品
             var currentProduct = _productService.GetCurrentProduct();
             if (currentProduct == null) return Task.FromResult(string.Empty);
             string response = $"{currentProduct.Speed},{currentProduct.Accel},{currentProduct.Speeds},{currentProduct.Accels},{Convert.ToInt16(currentProduct.Power)},{Convert.ToInt16(currentProduct.IsUserUpCamera)}";
             return Task.FromResult(response);
         }
 
-        //获取末端工具信息,传入流程触发命令和工具编号
+        /// <summary>
+        /// 获取某流程指定工具编号的工具参数(格式化字符串),找不到返回空字符串。
+        /// </summary>
         public Task<string> GetToolParamAsync(string triggerCommand, int toolNumber, CancellationToken cancellationToken)
         {
-            //获取当前产品
             var currentProduct = _productService.GetCurrentProduct();
             if (currentProduct == null) return Task.FromResult(string.Empty);
-            //查找对应的流程
             ProcedureModel procedure = currentProduct.CameraProcedures
                 .SelectMany(cp => cp.ProcedureModels)
                 .FirstOrDefault(p => p.TriggerCommand == triggerCommand);
             if (procedure == null || procedure.ToolInfo == null) return Task.FromResult(string.Empty);
-            //查找对应的工具
             var tool = procedure.ToolInfo.FirstOrDefault(t => t.Number == toolNumber);
             if (tool == null) return Task.FromResult(string.Empty);
             if (tool.Tool == null) return Task.FromResult(string.Empty);
@@ -826,13 +858,13 @@ namespace TeamAAS_VP.Services
             return Task.FromResult(response);
         }
 
-        //获取流程参数信息,传入流程触发命令
+        /// <summary>
+        /// 获取流程参数(例如 WaitPhoto 与用户自定义参数等),以逗号分隔的字符串返回。
+        /// </summary>
         public Task<string> GetProcessParamAsync(string triggerCommand, CancellationToken cancellationToken)
         {
-            //获取当前产品
             var currentProduct = _productService.GetCurrentProduct();
             if (currentProduct == null) return Task.FromResult(string.Empty);
-            //查找对应的流程
             ProcedureModel procedure = currentProduct.CameraProcedures
                 .SelectMany(cp => cp.ProcedureModels)
                 .FirstOrDefault(p => p.TriggerCommand == triggerCommand);
@@ -841,8 +873,6 @@ namespace TeamAAS_VP.Services
             response.Append($"{procedure.WaitPhoto}");
             if (procedure.UserDefineParameters != null)
             {
-                //附加用户自定义参数
-                //遍历所有用户自定义参数,以分号分隔
                 foreach (var param in procedure.UserDefineParameters)
                 {
                     response.Append("," + param.ToString());
@@ -851,11 +881,16 @@ namespace TeamAAS_VP.Services
             return Task.FromResult(response.ToString());
         }
 
-        //执行清料任务
+        /// <summary>
+        /// 执行指定编号的清料任务(异步)。流程:
+        /// - 根据配置获取任务
+        /// - 检查 Feeder 连接性
+        /// - 使用专用 CancellationTokenSource 跟踪任务并支持停止
+        /// - 循环执行开挡板、振动、关闭挡板等步骤,检查取消状态
+        /// </summary>
         public async Task<bool> ExecuteClearMaterialTaskAsync(int taskNumber, CancellationToken cancellationToken)
         {
-            //获取清料任务
-            var feederClearWork=_configService.GetClearanceTaskByNumber(taskNumber);
+            var feederClearWork = _configService.GetClearanceTaskByNumber(taskNumber);
             if (feederClearWork == null)
             {
                 return false;
@@ -867,7 +902,6 @@ namespace TeamAAS_VP.Services
             {
                 return false;
             }
-            //如果当前已有该任务在执行,则返回失败
             if (_clearMaterialCtsDict.ContainsKey(feederClearWork.TaskCode))
             {
                 return false;
@@ -876,7 +910,6 @@ namespace TeamAAS_VP.Services
             _clearMaterialCtsDict[feederClearWork.TaskCode] = new CancellationTokenSource();
             if (cancellationToken != null)
             {
-                //将外部取消令牌与内部取消令牌关联
                 cancellationToken.Register(() =>
                 {
                     if (_clearMaterialCtsDict.ContainsKey(feederClearWork.TaskCode))
@@ -889,10 +922,9 @@ namespace TeamAAS_VP.Services
 
             for (int i = 0; i < feederClearWork.ClearCount; i++)
             {
-                //1.Feeder振动之前,打开挡板
                 await Feeder.OutputAsync((int)feederClearWork.BeforeOutput + 1, 0);
                 await Task.Delay(200, token);
-                if ((cancellationToken != null && cancellationToken.IsCancellationRequested) || (token!=null && token.IsCancellationRequested))
+                if ((cancellationToken != null && cancellationToken.IsCancellationRequested) || (token != null && token.IsCancellationRequested))
                 {
                     await Feeder.StopOutputAsync();
                     if (_clearMaterialCtsDict.ContainsKey(feederClearWork.TaskCode))
@@ -903,7 +935,6 @@ namespace TeamAAS_VP.Services
                     return false;
                 }
 
-                //2.执行配置的Feeder振动任务
                 for (int j = 0; j < feederClearWork.VibrationCount; j++)
                 {
                     await ExecuteFeederWorks(Feeder, feederClearWork.FeederWorks, token, true);
@@ -918,7 +949,6 @@ namespace TeamAAS_VP.Services
                         return false;
                     }
 
-                    //如果料仓停止了,则打开料仓运动
                     if (!await Feeder.GetOutputStatusAsync())
                     {
                         await Feeder.OutputAsync((int)feederClearWork.BeforeOutput + 1, 0);
@@ -946,7 +976,6 @@ namespace TeamAAS_VP.Services
                     return false;
                 }
 
-                //3.Feeder振动之后,关闭挡板
                 await Feeder.StopOutputAsync();
                 await Task.Delay(300, token);
                 if ((cancellationToken != null && cancellationToken.IsCancellationRequested) || (token != null && token.IsCancellationRequested))
@@ -960,7 +989,7 @@ namespace TeamAAS_VP.Services
                     return false;
                 }
             }
-            //3.Feeder振动之后,关闭挡板
+
             await Feeder.StopOutputAsync();
             await Task.Delay(300, token);
             if ((cancellationToken != null && cancellationToken.IsCancellationRequested) || (token != null && token.IsCancellationRequested))
@@ -981,10 +1010,11 @@ namespace TeamAAS_VP.Services
             return true;
         }
 
-        //终止清料任务
+        /// <summary>
+        /// 停止指定编号的清料任务(如果正在运行)。
+        /// </summary>
         public Task<bool> StopClearMaterialTaskAsync(int taskNumber, CancellationToken cancellationToken)
         {
-            //获取清料任务
             var feederClearWork = _configService.GetClearanceTaskByNumber(taskNumber);
             if (feederClearWork == null)
             {
@@ -1000,10 +1030,8 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 获取机器人是否进入调试模式
+        /// 判断指定编号的机器人是否处于调试模式。
         /// </summary>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
         public Task<bool> IsRobotInDebugModeAsync(int robotNumber, CancellationToken cancellationToken)
         {
             var robotInfo = _configService.GetAllRobots().FirstOrDefault(r => r.RobotNo == robotNumber);
@@ -1017,25 +1045,17 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 执行视觉任务
+        /// 执行视觉任务的统一入口:
+        /// - 如果为产品局部脚本存在,则调用脚本
+        /// - 否则根据 procedure.FeederId 调用 Feeder 引导的视觉任务或普通视觉任务
+        /// 返回是否成功以及字符串数组形式的响应(用于发送多条结果)。
         /// </summary>
-        /// <param name="triggerCommand">流程触发命令</param>
-        /// <param name="doVibrateFirst">是否先振动</param>
-        /// <param name="doCaptureFirst">是否先拍照</param>
-        /// <param name="robotCurrentPosition">机器人当前点位</param>
-        /// <param name="visionToolInputParams">视觉工具输入参数</param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
         public async Task<(bool IsSucceed, string[] Response)> ExecuteVisionTaskAsync(ProcedureModel procedure, string[] items, CancellationToken cancellationToken)
         {
-            //获取当前产品
             var currentProduct = _productService.GetCurrentProduct();
-
-            //检测当前流程的本地是否存在流程执行脚本文件,如果存在则调用脚本执行视觉任务
             string scriptFilePath = FilePath.ProductsPath + "//" + currentProduct.Name + "//" + procedure.CameraName + "//" + procedure.Name + ".cs";
             if (File.Exists(scriptFilePath))
             {
-                //调用脚本执行视觉任务
                 try
                 {
                     string script = FileHelper.ReadFile(scriptFilePath);
@@ -1065,7 +1085,6 @@ namespace TeamAAS_VP.Services
             }
             else
             {
-                //正常执行模块,每次拍照,会返回所有的结果数据
                 IVoiceCoilMotorFeeder Feeder = null;
                 if (procedure.FeederId != Guid.Empty)
                 {
@@ -1081,14 +1100,16 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 更新计时器通知
+        /// 内部:触发节拍计时回调。
         /// </summary>
-        /// <param name="elapsed"></param>
         private void OnCycleTimingTicked(TimeSpan elapsed)
         {
             CycleTimingTicked?.Invoke(this, elapsed);
         }
 
+        /// <summary>
+        /// 发布任务消息到事件聚合器(通过 UI Dispatcher 保证线程安全)。
+        /// </summary>
         private void SendTaskMessage(string msg, MessageLevel level)
         {
             App.Current.Dispatcher.Invoke(() =>
@@ -1100,13 +1121,12 @@ namespace TeamAAS_VP.Services
         #region 执行视觉任务辅助方法
 
         /// <summary>
-        /// 执行Feeder视觉引导任务
+        /// 执行 Feeder 引导的视觉任务。负责:
+        /// - 解析输入命令(是否先振动/先拍照、SetInputs)
+        /// - 在拍照前后执行对应的 Feeder 操作(开灯、振动、供料等)
+        /// - 多次尝试拍照并运行视觉工具,直到成功或达到失败次数上限
+        /// - 返回布尔成功标记及字符串数组形式的响应项
         /// </summary>
-        /// <param name="Feeder"></param>
-        /// <param name="procedure"></param>
-        /// <param name="items"></param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
         public async Task<(bool IsSucceed, string[] Response)> ExecuteFeederVisionTaskAsync(IVoiceCoilMotorFeeder feeder, ProcedureModel procedure, string[] items, CancellationToken cancellationToken)
         {
             // 如果输入的items中是否包含Vibration和NoVibration,这两个分别对应是否先振动和是否先拍照
@@ -1307,12 +1327,8 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 执行普通视觉任务
+        /// 执行非 Feeder 引导的普通视觉任务,返回单字符串响应。
         /// </summary>
-        /// <param name="procedure"></param>
-        /// <param name="items"></param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
         public async Task<(bool IsSucceed, string Response)> ExecuteNormalVisionTaskAsync(ProcedureModel procedure, string[] items, CancellationToken cancellationToken)
         {
             // 开始执行前的时间点
@@ -1324,7 +1340,7 @@ namespace TeamAAS_VP.Services
 
                 // 解析触发命令传入的输入终端参数
                 //判断传入参数中是否存在“SetInputs”
-                if (items != null && items.Length>1 && items[0] == "SetInputs")
+                if (items != null && items.Length > 1 && items[0] == "SetInputs")
                 {
                     InputTerminal = items.Skip(1)
                         .Select(x => x.Split('='))
@@ -1387,14 +1403,10 @@ namespace TeamAAS_VP.Services
                             var (IsPoint, Value) = Output[output.Name];
                             if (IsPoint)
                             {
-                                //Value是点位数据,类型:List<string[]>,string[]是一个点位,里面是X、Y,U等数据
-
-                                //将所有点位数据和单个点位数据中的坐标之间都用逗号分隔,多个点位之间用逗号分隔
                                 List<string[]> points = Value as List<string[]>;
                                 List<string> allpoints = new List<string>();
                                 for (int j = 0; j < points.Count; j++)
                                 {
-                                    //单个点位内坐标之间逗号分隔
                                     allpoints.Add(string.Join(",", points[j]));
                                 }
                                 //多个点位之间逗号分隔
@@ -1424,12 +1436,9 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 执行相机拍照
+        /// 执行相机拍照并运行 ToolBlock,返回是否成功并通过 out 参数返回 Outputs。
+        /// 该方法会发布 RenderUpdateNotification 以更新 UI。
         /// </summary>
-        /// <param name="procedure"></param>
-        /// <param name="InputTerminal">输入终端参数</param>
-        /// <param name="outputCollection">输出终端结果</param>
-        /// <returns></returns>
         public bool ExecutePhoto(ProcedureModel procedure, Dictionary<string, string> InputTerminal, out CogToolBlockTerminalCollection outputCollection)
         {
             try
@@ -1541,12 +1550,9 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 获取需要输出项
+        /// 从 ToolBlock 的输出集合中根据 Procedure 配置提取需要发送的输出项并做必要的坐标转换。
+        /// 返回元组:是否成功、错误信息(失败时)和名称->(是否点位, 值) 的字典。
         /// </summary>
-        /// <param name="procedure"></param>
-        /// <param name="outputCollection"></param>
-        /// <param name="IsCheckLastPoints"></param>
-        /// <returns>是否成功,错误返回内容,<项的名称,<是否是点位数据,值>></returns>
         public (bool IsSucceed, string ErrMsg, Dictionary<string, (bool IsPoint, object Value)> Output) GetVisionOutput(ProcedureModel procedure, CogToolBlockTerminalCollection outputCollection, double[] robotPos)
         {
             RobotBrand robotBrand = RobotBrand.Default;
@@ -1556,9 +1562,8 @@ namespace TeamAAS_VP.Services
                 robotBrand = robot.RobotBrand;
             }
             List<string> SendResults = new List<string>();
-            //<输出项的名称,<是否点位,每项输出集合值>>
             Dictionary<string, (bool IsPoint, object Value)> ResultCollection = new Dictionary<string, (bool IsPoint, object Value)>();
-            foreach (var output in procedure.ProcedureOutputs)      //遍历配置的所有需要输出项
+            foreach (var output in procedure.ProcedureOutputs)
             {
                 //根据输出类型进行区分输出
                 if (output.ValueType == typeof(string))
@@ -1663,14 +1668,8 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// Feeder拍照前,执行Feeder
+        /// 拍照前针对 Feeder 的准备动作:设置灯、必要时供料或振动等。
         /// </summary>
-        /// <param name="Feeder">Feeder</param>
-        /// <param name="procedure">流程</param>
-        /// <param name="doVibrateFirst">是否先振动</param>
-        /// <param name="doCaptureFirst">是否先拍照</param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
         public async Task ExecuteFeederForPhotoBefore(IVoiceCoilMotorFeeder Feeder, ProcedureModel procedure, bool doVibrateFirst, bool doCaptureFirst, CancellationToken cancellationToken)
         {
             // 1. 设置Feeder光源亮度、打开光源
@@ -1748,11 +1747,8 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// Feeder拍照后,拍照失败时,执行Feeder
+        /// 拍照后在 NG 情况下的 Feeder 后处理,例如供料或振动。
         /// </summary>
-        /// <param name="Feeder"></param>
-        /// <param name="procedure"></param>
-        /// <returns></returns>
         public async Task ExecuteFeederForPhotoLater(IVoiceCoilMotorFeeder Feeder, ProcedureModel procedure, CancellationToken cancellationToken)
         {
             //根据斑点面积决定是否料仓供料
@@ -1779,11 +1775,8 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 执行Feeder料仓供料及振动集合
+        /// 并行执行料仓供料和振动任务。
         /// </summary>
-        /// <param name="feeder"></param>
-        /// <param name="works"></param>
-        /// <returns></returns>
         public async Task ExecuteFeederStockWorks(IVoiceCoilMotorFeeder feeder, FeederStockWorks works, CancellationToken cancellationToken, bool isUpdateParame = true)
         {
             //同时执行料仓供料和Feeder振动任务
@@ -1793,12 +1786,8 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 执行料仓振动任务
+        /// 执行料仓振动任务序列(仅料仓相关动作)。
         /// </summary>
-        /// <param name="feeder"></param>
-        /// <param name="works"></param>
-        /// <param name="cancellationToken"></param>
-        /// <returns></returns>
         public async Task ExecuteFeederStockWorksOnly(IVoiceCoilMotorFeeder feeder, FeederStockWork[] works, CancellationToken cancellationToken)
         {
             //执行配置的料仓振动任务
@@ -1813,12 +1802,7 @@ namespace TeamAAS_VP.Services
                     }
                     else if (stockwork.RegisterIndex == 1)
                     {
-                        //if (BgModbusTcpCommunicate != null)
-                        //{
-                        //    BgModbusTcpCommunicate.Slave.DataStore.HoldingRegisters[stockwork.Address] = 1;
-                        //    await Task.Delay(stockwork.Duration);
-                        //    BgModbusTcpCommunicate.Slave.DataStore.HoldingRegisters[stockwork.Address] = 0;
-                        //}
+                        // 保留原注释的位置给 Modbus 操作
                     }
                     if (cancellationToken != null && cancellationToken.IsCancellationRequested)
                     {
@@ -1848,13 +1832,8 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 执行Feeder振动任务
+        /// 执行单独的 Feeder 振动动作序列(仅振动相关)。
         /// </summary>
-        /// <param name="feeder"></param>
-        /// <param name="feederwork"></param>
-        /// <param name="cancellationToken"></param>
-        /// <param name="isUpdateParame"></param>
-        /// <returns></returns>
         public async Task ExecuteFeederWorkOnly(IVoiceCoilMotorFeeder feeder, FeederWork[] works, CancellationToken cancellationToken, bool isUpdateParame = true)
         {
             if (works == null)
@@ -1919,16 +1898,10 @@ namespace TeamAAS_VP.Services
         }
 
         /// <summary>
-        /// 执行Feeder振动集合
+        /// 执行 Feeder 振动集合(逐个执行 FeederWorkList 中的动作)。
         /// </summary>
-        /// <param name="feeder"></param>
-        /// <param name="works"></param>
-        /// <param name="cancellationToken"></param>
-        /// <param name="isUpdateParame"></param>
-        /// <returns></returns>
         public async Task ExecuteFeederWorks(IVoiceCoilMotorFeeder feeder, FeederWorks works, CancellationToken cancellationToken, bool isUpdateParame = true)
         {
-            //执行配置的Feeder振动任务
             if (feeder.FeederBrand == FeederBrand.AFAG)
             {
                 foreach (var feederwork in works.FeederWorkList)

+ 50 - 542
TeamAAS-VM/ViewModels/Calibration/CalibToolViewModel.cs

@@ -31,6 +31,7 @@ using static OpenCvSharp.LineIterator;
 using TeamAAS_VP.Resources.Languages;
 using TeamAAS_VP.Models.Calibration;
 using TeamAAS_VP.Services;
+using System.Threading;
 
 namespace TeamAAS_VP.ViewModels.Calibration
 {
@@ -45,6 +46,9 @@ namespace TeamAAS_VP.ViewModels.Calibration
         IRobotService _robotService;
         ICameraService _cameraService;
         IFeederService _feederService;
+        ICameraCalibrationService _cameraCalibrationService;
+
+        private CancellationTokenSource _cts;
         #region 属性
 
         private ICogImage _Image;
@@ -130,7 +134,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
         #endregion
 
         public CalibToolViewModel(IRegionManager regionManager, IEventAggregator ea, IRegionNavigationService regionNavigationService, IContainerProvider container, IDialogService dialogService,
-            IConfigService configService, IRobotService robotService, ICameraService cameraService, IFeederService feederService)
+            IConfigService configService, IRobotService robotService, ICameraService cameraService, IFeederService feederService, ICameraCalibrationService cameraCalibrationService)
         {
             _regionManager = regionManager;
             _regionNavigationService = regionNavigationService;
@@ -141,6 +145,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
             _robotService = robotService;
             _cameraService = cameraService;
             _feederService = feederService;
+            _cameraCalibrationService = cameraCalibrationService;
         }
 
         #region 方法
@@ -184,43 +189,34 @@ namespace TeamAAS_VP.ViewModels.Calibration
             {
                 if (Angle == 0)
                     Angle = 10;
+                _cts=new CancellationTokenSource();
                 var view = new ShowMessage(Lang.Tool校准, Lang.是否开始自动校准工具坐标);
                 //show the dialog
-                var result = await DialogHost.Show(view, "CalibToolDialog", null, null, null);
+                var result = await DialogHost.Show(view, "CalibToolDialog", null, null, (sender,e) =>{
+                    try
+                    {
+                        if (_cts != null && !_cts.IsCancellationRequested)
+                            _cts.Cancel();
+                        _cts.Dispose();
+                        _cts = null;
+                    }
+                    catch (Exception)
+                    {
+
+                    }
+                });
                 if (!((bool)result)) return;
                 IsRunning = false;
-                //发送校准的参数
-                await Robot.CalibParameAsync(SelectCalibration.Pick, SelectCalibration.Speed, SelectCalibration.Accel, SelectCalibration.Power, SelectCalibration.WaitSuction, SelectCalibration.WaitBlow);
-
-                Tuple<bool, Matrix<double>, double, double> calibresult=new Tuple<bool, Matrix<double>, double, double>(false,null,0,0);
-                if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-                {
-                    calibresult = await CalibToolForFixedDown();
-                }
-                else if (SelectCalibration.CameraMount == Enums.CameraMount.FixedUp)
-                {
-                    calibresult = await CalibToolForFixedUp();
-                }
-                else if (SelectCalibration.CameraMount == Enums.CameraMount.MobileJ4)
-                {
-                    calibresult = await CalibToolForMobileJ4();
-                }
-                else
-                {
-                    view = new ShowMessage(Lang.Tool校准, Lang.暂不支持此安装方式的相机, false);
-                    result = await DialogHost.Show(view, "CalibToolDialog", null, null, null);
-                    return;
-                }
-                
-                if (calibresult.Item1)
+                var toolResult = await _cameraCalibrationService.CalibrateUncalibratedCameraTool(SelectCalibration, Robot, _cts.Token);
+                if (toolResult.IsSuccess)
                 {
-                    if (Tool==null)
+                    if (Tool == null)
                     {
                         Tool = new RobotTool();
                     }
-                    Tool.X = calibresult.Item3;
-                    Tool.Y = calibresult.Item4;
-                    RobotCameraRotationMatrix = calibresult.Item2;
+                    Tool.X = toolResult.ToolX;
+                    Tool.Y = toolResult.ToolY;
+                    RobotCameraRotationMatrix = toolResult.RotationMatrix;
 
                     //MessageBox.Show("校准完成");
                     view = new ShowMessage(Lang.Tool校准, Lang.校准完成, false);
@@ -229,6 +225,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
                     IDialogParameters parameters = new DialogParameters();
                     parameters.Add("Tool", Tool);
                     parameters.Add("RobotCameraRotationMatrix", RobotCameraRotationMatrix);
+                    parameters.Add("Angle", Angle);
                     RequestClose?.Invoke(new DialogResult(ButtonResult.OK, parameters));
                 }
                 else
@@ -236,19 +233,30 @@ namespace TeamAAS_VP.ViewModels.Calibration
                     view = new ShowMessage(Lang.Tool校准, Lang.校准失败, false);
                     //show the dialog
                     await DialogHost.Show(view, "CalibToolDialog", null, null, null);
-                    
                 }
-                IsRunning = true;
             }
             catch (Exception ex)
             {
-                IsRunning = true;
                 LogHelper.WriteLogError("相机校准-校准验证界面示加载时出错!", ex);
-                //MessageBox.Show(ex.Message);
                 var view = new ShowMessage(Lang.Tool校准, $"{ex.Message}", false);
                 //show the dialog
                 var result = await DialogHost.Show(view, "CalibToolDialog", null, null, null);
             }
+            finally
+            {
+                try
+                {
+                    if (_cts != null && !_cts.IsCancellationRequested)
+                        _cts.Cancel();
+                    _cts.Dispose();
+                    _cts = null;
+                }
+                catch (Exception)
+                {
+
+                }
+                IsRunning = true;
+            }
         }
 
         public bool CanCloseDialog()
@@ -258,7 +266,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
 
         public void OnDialogClosed()
         {
-            
+            _cameraCalibrationService.CaptureAndProcessCallback = null;
         }
 
         public void OnDialogOpened(IDialogParameters parameters)
@@ -275,153 +283,21 @@ namespace TeamAAS_VP.ViewModels.Calibration
                 }
                 Angle = SelectCalibration.Angle;
             }
-        }
-
-        /// <summary>
-        /// 移动机器人3个点计算旋转矩阵
-        /// </summary>
-        /// <returns>(旋转矩阵,PixelScaleX,PixelScaleY)</returns>
-        private async Task<Tuple<Matrix<double>,double,double>> MoveRobotThreePointComputCroodTrans(RPoint rPoint, float distance)
-        {
-            //-------------------------------------------P0-----------------------------------
-            //移动机器人至中心点
-
-            if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-            {
-                await Robot.CalibMotionAsync(rPoint, 0);
-                //关闭吸气
-                await Robot.CalibOutIOAsync(false);
-            }
-            else
-            {
-                await Robot.CalibMotionAsync(rPoint, rPoint.Z);
-            }
-            
-
-            RPoint RobotP0= await Robot.GetRobotPosAsync();
-            RPoint RobotP1 = RobotP0.Clone();
-            RPoint RobotP2 = RobotP0.Clone();
-            RobotP1.X += distance;
-            RobotP2.Y += distance;
-
-            if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-            {
-                //移动机器人至待机位置
-                await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-            }
-
-            var P0=Photo();
-            //-------------------------------------------P1-----------------------------------
-            if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-            {
-                //移动机器人至P0
-                await Robot.CalibMotionAsync(RobotP0, 0);
-                //打开吸气
-                await Robot.CalibOutIOAsync(true);
-                //移动机器人至P1
-                await Robot.CalibMotionAsync(RobotP1, 0);
-                //关闭吸气
-                await Robot.CalibOutIOAsync(false);
-                //移动机器人至待机位置
-                await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-            }
-            else
-            {
-                //移动机器人至P1
-                await Robot.CalibMotionAsync(RobotP1, RobotP1.Z);
-            }
-            var P1 = Photo();
-            
-            //-------------------------------------------P2-----------------------------------
-            if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-            {
-                //移动机器人至P1
-                await Robot.CalibMotionAsync(RobotP1, 0);
-                //打开吸气
-                await Robot.CalibOutIOAsync(true);
-                //移动机器人至P2
-                await Robot.CalibMotionAsync(RobotP2, 0);
-                //关闭吸气
-                await Robot.CalibOutIOAsync(false);
-                //移动机器人至待机位置
-                await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-            }
-            else
-            {
-                //移动机器人至P2
-                await Robot.CalibMotionAsync(RobotP2, RobotP2.Z);
-            }
-            var P2 = Photo();
-
-            //-------------------------------------------P0--------------------------------------
-            if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-            {
-                //移动机器人至P2
-                await Robot.CalibMotionAsync(RobotP2, 0);
-                //打开吸气
-                await Robot.CalibOutIOAsync(true);
-                //移动机器人至P0
-                await Robot.CalibMotionAsync(RobotP0, 0);
-                //关闭吸气
-                await Robot.CalibOutIOAsync(false);
-                //移动机器人至待机位置
-                await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-            }
-            else
-            {
-                //移动机器人至P0
-                await Robot.CalibMotionAsync(RobotP0, RobotP0.Z);
-            }
-
-            //-----------------------------------------计算旋转矩阵------------------------------
-            Matrix<double> M= ComputRotationMatrix(P0, P1, P2);
-            //Theta = Acos(M11)
-            //相机与机器人坐标系角度:Theta * 180 / PI
-            //-----------------------------------------计算像素和毫米比例-----------------------
-            Vector<double> P11= Vector<double>.Build.Dense(new double[] { distance, 0 });
-            Vector<double> P21 = Vector<double>.Build.Dense(new double[] { P1.X, P1.Y }) - Vector<double>.Build.Dense(new double[] { P0.X, P0.Y });
-            double PixelScaleX = Math.Abs((M * P11)[0] / P21[0]);       //mm/pixel
-
-            double PixelScaleY = Math.Abs((M * P11)[1] / P21[1]);      //mm/pixel
-            return new Tuple<Matrix<double>, double, double>(M, PixelScaleX, PixelScaleY);
-        }
-
-        /// <summary>
-        /// 计算旋转矩阵
-        /// </summary>
-        /// <param name="P0"></param>
-        /// <param name="P1"></param>
-        /// <param name="P2"></param>
-        /// <returns></returns>
-        private Matrix<double> ComputRotationMatrix(PointF P0, PointF P1, PointF P2)
-        {
-            Vector<double>  Point0 = Vector<double>.Build.Dense(new double[] { (double)P0.X, (double)P0.Y });
-            Vector<double> Point1 = Vector<double>.Build.Dense(new double[] { (double)P1.X, (double)P1.Y });
-            Vector<double> Point2 = Vector<double>.Build.Dense(new double[] { (double)P2.X, (double)P2.Y });
-
-            //2.计算坐标系的基向量:
-            Vector<double>  E1 = (Point1 - Point0) / (Point1 - Point0).L2Norm();
-            Vector<double>  E2 = (Point2 - Point0 - E1.DotProduct(Point2 - Point0) * E1) / (Point2 - Point0 - E1.DotProduct(Point2 - Point0) * E1).L2Norm();
-
-            //3.计算旋转矩阵:我们可以使用基向量组成的矩阵作为旋转矩阵。具体地,我们可以将基向量 e1 和 e2 分别作为新坐标系下的 $x$ 和 $y$ 轴,然后组成一个矩阵,再对这个矩阵求逆即可得到旋转矩阵。代码实现:
-            // Compute rotation matrix
-            Matrix<double>  R = Matrix<double>.Build.DenseOfColumnVectors(E1, E2);
-            return R;
+            _cameraCalibrationService.CaptureAndProcessCallback = CaptureAndProcess;
         }
 
         /// <summary>
         /// 相机拍照
         /// </summary>
         /// <returns></returns>
-        /// <exception cref="Exception"></exception>
-        private PointF Photo()
+        private async Task<(bool IsSuccess, double X, double Y, double U, int ImageWidth, int ImageHeight)> CaptureAndProcess()
         {
             if (Feeder != null)
             {
                 Feeder.OpenLight();
                 Feeder.SetLightTimeOut((ushort)SelectCalibration.Brightness);
             }
-            Task.Delay(200).Wait();
+            await Task.Delay(200);
             //采集图像
             if (Camera != null)
             {
@@ -437,381 +313,13 @@ namespace TeamAAS_VP.ViewModels.Calibration
             {
                 var strpoints = outputCollection["Point"].Value.ToString();
                 string[] items = strpoints.Split(',');
-                return new PointF(float.Parse(items[0]), float.Parse(items[1]));
-            }
-            else
-            {
-                throw new Exception(Lang.拍照失败);
-            }
-        }
-
-        /// <summary>
-        /// 固定向下相机校准工具坐标
-        /// </summary>
-        /// <returns>(结果,旋转矩阵,Tool.X,Tool.Y)</returns>
-        private async Task<Tuple<bool,Matrix<double>, double, double>> CalibToolForFixedDown()
-        {
-            //移动机器人至待机位置
-            await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-
-            
-            if (SelectCalibration.Pick.PickPlaceModel == Enums.PickPlaceModel.Inhal)
-            {
-                //打开吸气
-                await Robot.CalibOutIOAsync(true);
-            }
-            else
-            {
-                //张开夹爪
-                //夹爪张开为false,夹爪闭合为True
-                await Robot.CalibOutIOAsync(false);
-            }
-            var view = new ShowMessage(Lang.提示, Lang.请将校准块放置在吸嘴中心);
-            var result = await DialogHost.Show(view, "CalibToolDialog", null, null, null);
-            if (!((bool)result)) { IsRunning = true; return new Tuple<bool, Matrix<double>, double, double>(false,null,0,0); }
-            if (SelectCalibration.Pick.PickPlaceModel == Enums.PickPlaceModel.Clamp)
-            {
-                //夹紧夹爪
-                await Robot.CalibOutIOAsync(true);
-            }
-
-            //计算相机与机器人坐标系的旋转矩阵
-            var rst = await MoveRobotThreePointComputCroodTrans(SelectCalibration.CenterPoint, ((float)SelectCalibration.Height) / 3);
-            SelectCalibration.PixelScaleX = rst.Item2;
-            SelectCalibration.PixelScaleY = rst.Item3;
-            var P0 = Photo();       //拍照获取中点像素坐标
-
-            RPoint RobotU = SelectCalibration.CenterPoint.Clone();
-            RobotU.U += (float)Angle;
-            //移动机器人中心点
-            await Robot.CalibMotionAsync(SelectCalibration.CenterPoint, 0);
-            //打开吸气
-            await Robot.CalibOutIOAsync(true);
-
-            //机器人U轴旋转
-            await Robot.CalibMotionAsync(RobotU, 0);
-
-            //关闭吸气
-            await Robot.CalibOutIOAsync(false);
-            //移动机器人至待机位置
-            await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-
-            var curPixel = Photo();
-
-            bool IsFinish = false;
-            for (int i = 0; i < 10; i++)
-            {
-                //计算需要移动的偏移量,目的是逼近中心点
-                double offsetx = (P0.X - curPixel.X) * rst.Item2;
-                double offsety = (P0.Y - curPixel.Y) * rst.Item3;
-                var pos = rst.Item1.Inverse() * Vector<double>.Build.Dense(new double[] { offsetx, offsety });
-                //移动机器人取标定块
-                await Robot.CalibMotionAsync(RobotU, 0);
-                //打开吸气
-                await Robot.CalibOutIOAsync(true);
-
-                RobotU.X += (float)pos[0];
-                RobotU.Y += (float)pos[1];
-                //机器人移动
-                await Robot.CalibMotionAsync(RobotU, 0);
-
-                //关闭吸气
-                await Robot.CalibOutIOAsync(false);
-                //移动机器人至待机位置
-                await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-
-                curPixel = Photo();
-                offsetx = P0.X - curPixel.X;
-                offsety = P0.Y - curPixel.Y;
-                //await Console.Out.WriteLineAsync($"移动后的差异:{offsetx},{offsety}");
-                if (Math.Abs(offsetx) < 1 && Math.Abs(offsetx) < 1)
-                {
-                    //移动机器人取标定块
-                    await Robot.CalibMotionAsync(RobotU, 0);
-                    //打开吸气
-                    await Robot.CalibOutIOAsync(true);
-                    //移动机器人至待机位置
-                    await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-                    IsFinish = true;
-                    break;
-                }
-            }
-
-            if (IsFinish)
-            {
-                Vector<double> p0 = Vector<double>.Build.Dense(new double[] { SelectCalibration.CenterPoint.X, SelectCalibration.CenterPoint.Y });
-                Vector<double> p1 = Vector<double>.Build.Dense(new double[] { RobotU.X, RobotU.Y });
-                ToolCoord toolCoord = new ToolCoord();
-                double u1 = SelectCalibration.CenterPoint.U;
-                double u2 = RobotU.U;
-                if (Robot.Brand == Enums.RobotBrand.Schneider)
-                {
-                    u1 *= -1;
-                    u2 *= -1;
-                }
-                toolCoord.ComputeTool(p0, p1, u1, u2);
-                if (Tool == null)
-                {
-                    Tool = new RobotTool();
-                }
-                Tool.X = toolCoord.X;
-                Tool.Y = toolCoord.Y;
-                RobotCameraRotationMatrix = rst.Item1;
-                return new Tuple<bool, Matrix<double>, double, double>(true, RobotCameraRotationMatrix, Tool.X, Tool.Y);
-            }
-            else
-            {
-                return new Tuple<bool, Matrix<double>, double, double>(false, null,0,0);
-            }
-        }
-
-        /// <summary>
-        /// 固定向上相机校准工具坐标
-        /// </summary>
-        /// <returns>(结果,旋转矩阵,Tool.X,Tool.Y)</returns>
-        private async Task<Tuple<bool, Matrix<double>, double, double>> CalibToolForFixedUp()
-        {
-            //移动机器人至中心位置
-            await Robot.CalibMotionAsync(SelectCalibration.CenterPoint, 0);
-
-            if (SelectCalibration.Pick.PickPlaceModel == Enums.PickPlaceModel.Inhal)
-            {
-                //打开吸气
-                await Robot.CalibOutIOAsync(true);
-            }
-            else
-            {
-                //张开夹爪
-                await Robot.CalibOutIOAsync(false);
-            }
-
-            var view = new ShowMessage(Lang.提示, Lang.请将校准块放置在吸嘴中心);
-            var result = await DialogHost.Show(view, "CalibToolDialog", null, null, null);
-            if (!((bool)result)) { IsRunning = true; return new Tuple<bool, Matrix<double>, double, double>(false,null,0,0); }
-            if (SelectCalibration.Pick.PickPlaceModel == Enums.PickPlaceModel.Clamp)
-            {
-                //夹紧夹爪
-                await Robot.CalibOutIOAsync(true);
-            }
-
-            //计算相机与机器人坐标系的旋转矩阵
-            var rst = await MoveRobotThreePointComputCroodTrans(SelectCalibration.CenterPoint, ((float)SelectCalibration.Height) / 3);
-            SelectCalibration.PixelScaleX = rst.Item2;
-            SelectCalibration.PixelScaleY = rst.Item3;
-            var imagecenter = Camera.Grab();
-            PointF P0 = new PointF((float)(imagecenter.Width/2), (float)(imagecenter.Height / 2));       //中心点像素坐标
-
-            //先找相机中心点,校准中心点坐标
-            bool IsFinish = false;
-            var curPixel = Photo();
-            var curpos = Robot.GetRobotPos();
-            for (int i = 0; i < 10; i++)
-            {
-                //计算需要移动的偏移量,目的是逼近中心点
-                double offsetx = (P0.X - curPixel.X) * rst.Item2;
-                double offsety = (P0.Y - curPixel.Y) * rst.Item3;
-                var pos = rst.Item1.Inverse() * Vector<double>.Build.Dense(new double[] { offsetx, offsety });
-                curpos.X += (float)pos[0];
-                curpos.Y += (float)pos[1];
-                //机器人移动
-                await Robot.CalibMotionAsync(curpos, curpos.Z);
-
-                curPixel = Photo();
-                offsetx = P0.X - curPixel.X;
-                offsety = P0.Y - curPixel.Y;
-                //await Console.Out.WriteLineAsync($"移动后的差异:{offsetx},{offsety}");
-                if (Math.Abs(offsetx) < 1 && Math.Abs(offsetx) < 1)
-                {
-                    IsFinish = true;
-                    break;
-                }
-            }
-            if (IsFinish==true)
-            {
-                SelectCalibration.CenterPoint= Robot.GetRobotPos();
-            }
-            else
-            {
-                return new Tuple<bool, Matrix<double>, double, double>(false, null, 0, 0);
-            }
-
-            
-            RPoint RobotU = SelectCalibration.CenterPoint.Clone();
-            RobotU.U += (float)Angle;
-
-            //机器人U轴旋转
-            await Robot.CalibMotionAsync(RobotU, RobotU.Z);
-            curPixel = Photo();
-
-
-            IsFinish = false;
-            for (int i = 0; i < 10; i++)
-            {
-                //计算需要移动的偏移量,目的是逼近中心点
-                double offsetx = (P0.X - curPixel.X) * rst.Item2;
-                double offsety = (P0.Y - curPixel.Y) * rst.Item3;
-                var pos = rst.Item1.Inverse() * Vector<double>.Build.Dense(new double[] { offsetx, offsety });
-
-                RobotU.X += (float)pos[0];
-                RobotU.Y += (float)pos[1];
-                //机器人移动
-                await Robot.CalibMotionAsync(RobotU, 0);
-
-                curPixel = Photo();
-                offsetx = P0.X - curPixel.X;
-                offsety = P0.Y - curPixel.Y;
-                //await Console.Out.WriteLineAsync($"移动后的差异:{offsetx},{offsety}");
-                if (Math.Abs(offsetx) < 1 && Math.Abs(offsetx) < 1)
-                {
-                    IsFinish = true;
-                    break;
-                }
-            }
-
-            if (IsFinish)
-            {
-                Vector<double> p0 = Vector<double>.Build.Dense(new double[] { SelectCalibration.CenterPoint.X, SelectCalibration.CenterPoint.Y });
-                Vector<double> p1 = Vector<double>.Build.Dense(new double[] { RobotU.X, RobotU.Y });
-                ToolCoord toolCoord = new ToolCoord();
-                double u1 = SelectCalibration.CenterPoint.U;
-                double u2 = RobotU.U;
-                if (Robot.Brand== Enums.RobotBrand.Schneider)
-                {
-                    u1 *= -1;
-                    u2 *= -1;
-                }
-                toolCoord.ComputeTool(p0, p1, u1, u2);
-                if (Tool == null)
-                {
-                    Tool = new RobotTool();
-                }
-
-                /*double toolx= toolCoord.X;
-                if (Robot.Brand == Enums.RobotBrand.Schneider)
-                {
-                    toolx *= -1;
-                }*/
-                Tool.X = toolCoord.X;
-                Tool.Y = toolCoord.Y;
-                RobotCameraRotationMatrix = rst.Item1;
-                return new Tuple<bool, Matrix<double>, double, double>(true, RobotCameraRotationMatrix, Tool.X, Tool.Y);
-            }
-            else
-            {
-                return new Tuple<bool, Matrix<double>, double, double>(false, null, 0, 0);
-            }
-        }
-
-        /// <summary>
-        /// J4移动机器人校准工具坐标
-        /// </summary>
-        /// <returns>(结果,旋转矩阵,Tool.X,Tool.Y)</returns>
-        private async Task<Tuple<bool, Matrix<double>, double, double>> CalibToolForMobileJ4()
-        {
-            //移动机器人至中心位置
-            await Robot.CalibMotionAsync(SelectCalibration.CenterPoint, 0);
-
-            //计算相机与机器人坐标系的旋转矩阵
-            var rst = await MoveRobotThreePointComputCroodTrans(SelectCalibration.CenterPoint, ((float)SelectCalibration.Height) / 3);
-            SelectCalibration.PixelScaleX = rst.Item2;
-            SelectCalibration.PixelScaleY = rst.Item3;
-
-            var imagecenter = Camera.Grab();
-            PointF P0 = new PointF((float)(imagecenter.Width / 2), (float)(imagecenter.Height / 2));       //中心点像素坐标
-
-            //先找相机中心点,校准中心点坐标
-            bool IsFinish = false;
-            var curPixel = Photo();
-            var curpos = Robot.GetRobotPos();
-            for (int i = 0; i < 10; i++)
-            {
-                //计算需要移动的偏移量,目的是逼近中心点
-                double offsetx = (P0.X - curPixel.X) * rst.Item2;
-                double offsety = (P0.Y - curPixel.Y) * rst.Item3;
-                var pos = rst.Item1.Inverse() * Vector<double>.Build.Dense(new double[] { offsetx, offsety });
-                curpos.X += (float)pos[0];
-                curpos.Y += (float)pos[1];
-                //机器人移动
-                await Robot.CalibMotionAsync(curpos, curpos.Z);
-
-                curPixel = Photo();
-                offsetx = P0.X - curPixel.X;
-                offsety = P0.Y - curPixel.Y;
-                //await Console.Out.WriteLineAsync($"移动后的差异:{offsetx},{offsety}");
-                if (Math.Abs(offsetx) < 1 && Math.Abs(offsetx) < 1)
-                {
-                    IsFinish = true;
-                    break;
-                }
-            }
-            if (IsFinish == true)
-            {
-                SelectCalibration.CenterPoint = Robot.GetRobotPos();
-            }
-            else
-            {
-                return new Tuple<bool, Matrix<double>, double, double>(false, null, 0, 0);
-            }
-
-            RPoint RobotU = SelectCalibration.CenterPoint.Clone();
-            RobotU.U += (float)Angle;
-
-            //机器人U轴旋转
-            await Robot.CalibMotionAsync(RobotU, RobotU.Z);
-            //计算相机与机器人坐标系的旋转矩阵
-            var rst1 = await MoveRobotThreePointComputCroodTrans(RobotU, 1);
-
-            curPixel = Photo();
-
-            IsFinish = false;
-            for (int i = 0; i < 10; i++)
-            {
-                //计算需要移动的偏移量,目的是逼近中心点
-                double offsetx = (P0.X - curPixel.X) * rst1.Item2;
-                double offsety = (P0.Y - curPixel.Y) * rst1.Item3;
-                var pos = rst1.Item1.Inverse() * Vector<double>.Build.Dense(new double[] { offsetx, offsety });
-
-                RobotU.X += (float)pos[0];
-                RobotU.Y += (float)pos[1];
-                //机器人移动
-                await Robot.CalibMotionAsync(RobotU, RobotU.Z);
-
-                curPixel = Photo();
-                offsetx = P0.X - curPixel.X;
-                offsety = P0.Y - curPixel.Y;
-                //await Console.Out.WriteLineAsync($"移动后的差异:{offsetx},{offsety}");
-                if (Math.Abs(offsetx) < 1 && Math.Abs(offsetx) < 1)
-                {
-                    IsFinish = true;
-                    break;
-                }
-            }
-
-            if (IsFinish)
-            {
-                Vector<double> p0 = Vector<double>.Build.Dense(new double[] { SelectCalibration.CenterPoint.X, SelectCalibration.CenterPoint.Y });
-                Vector<double> p1 = Vector<double>.Build.Dense(new double[] { RobotU.X, RobotU.Y });
-                ToolCoord toolCoord = new ToolCoord();
-                double u1 = SelectCalibration.CenterPoint.U;
-                double u2 = RobotU.U;
-                if (Robot.Brand == Enums.RobotBrand.Schneider)
-                {
-                    u1 *= -1;
-                    u2 *= -1;
-                }
-                toolCoord.ComputeTool(p0, p1, u1, u2);
-                if (Tool == null)
-                {
-                    Tool = new RobotTool();
-                }
-                Tool.X = toolCoord.X;
-                Tool.Y = toolCoord.Y;
-                RobotCameraRotationMatrix = rst.Item1;
-                return new Tuple<bool, Matrix<double>, double, double>(true, RobotCameraRotationMatrix, Tool.X, Tool.Y);
+                double x = double.Parse(items[0]);
+                double y = double.Parse(items[1]);
+                return (true, x, y, 0, Image.Width, Image.Height);
             }
             else
             {
-                return new Tuple<bool, Matrix<double>, double, double>(false, null, 0, 0);
+                return (false, 0, 0, 0, Image.Width, Image.Height);
             }
         }
         #endregion

+ 33 - 587
TeamAAS-VM/ViewModels/Calibration/CalibrationAutoViewModel.cs

@@ -26,6 +26,7 @@ using TeamAAS_VP.Resources.Languages;
 using TeamAAS_VP.Models.Calibration;
 using TeamAAS_VP.Models.Robot;
 using TeamAAS_VP.Services;
+using System.Threading;
 
 namespace TeamAAS_VP.ViewModels.Calibration
 {
@@ -40,7 +41,8 @@ namespace TeamAAS_VP.ViewModels.Calibration
         IFeederService _feederService;
         ICameraService _cameraService;
         ISystemDatabaseService _systemDatabaseService;
-
+        ICameraCalibrationService _cameraCalibrationService;
+        private CancellationTokenSource _cts;
         #region 属性
         private ICogImage _Image;
 
@@ -110,7 +112,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
         #endregion
 
         public CalibrationAutoViewModel(IRegionManager regionManager, IEventAggregator ea, IRegionNavigationService regionNavigationService, IContainerProvider container, IDialogService dialogService,
-            IRobotService robotService, IFeederService feederService,ICameraService cameraService,ISystemDatabaseService systemDatabaseService)
+            IRobotService robotService, IFeederService feederService,ICameraService cameraService,ISystemDatabaseService systemDatabaseService, ICameraCalibrationService cameraCalibrationService)
         {
             _regionManager = regionManager;
             _regionNavigationService = regionNavigationService;
@@ -121,6 +123,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
             _feederService = feederService;
             _cameraService = cameraService;
             _systemDatabaseService = systemDatabaseService;
+            _cameraCalibrationService = cameraCalibrationService;
         }
 
         #region 方法
@@ -151,378 +154,19 @@ namespace TeamAAS_VP.ViewModels.Calibration
                 if (MessageBox.Show(Lang.是否开始自动校准, $"{Lang.自动校准}", MessageBoxButton.OKCancel, MessageBoxImage.Question) != MessageBoxResult.OK) return;
 
                 IsRunning = false;
-                double IntervalX = SelectCalibration.Width / 2;
-                double IntervalY = SelectCalibration.Height / 2;
-                Vector<double> P1 = Vector<double>.Build.Dense(new double[] { -IntervalX, -IntervalY });
-                Vector<double> P2 = Vector<double>.Build.Dense(new double[] { 0, -IntervalY });
-                Vector<double> P3 = Vector<double>.Build.Dense(new double[] { IntervalX, -IntervalY });
-                Vector<double> P4 = Vector<double>.Build.Dense(new double[] { IntervalX, 0 });
-                Vector<double> P5 = Vector<double>.Build.Dense(new double[] { 0, 0 });
-                Vector<double> P6 = Vector<double>.Build.Dense(new double[] { -IntervalX, 0 });
-                Vector<double> P7 = Vector<double>.Build.Dense(new double[] { -IntervalX, IntervalY });
-                Vector<double> P8 = Vector<double>.Build.Dense(new double[] { 0, IntervalY });
-                Vector<double> P9 = Vector<double>.Build.Dense(new double[] { IntervalX, IntervalY });
-
-                //移动相机标定时,需要先记住特征点的绝对坐标(相对于机器人坐标系) P0
-                //if (SelectCalibration.CameraMount == Enums.CameraMount.MobileJ4)
-                //{
-                //    P1[0] *=-1; P1[1] *= -1;
-                //    P2[0] *= -1; P2[1] *= -1;
-                //    P3[0] *= -1; P3[1] *= -1;
-                //    P4[0] *= -1; P4[1] *= -1;
-                //    P5[0] *= -1; P5[1] *= -1;
-                //    P6[0] *= -1; P6[1] *= -1;
-                //    P7[0] *= -1; P7[1] *= -1;
-                //    P8[0] *= -1; P8[1] *= -1;
-                //    P9[0] *= -1; P9[1] *= -1;
-                //}
-
-                var P0 = Vector<double>.Build.Dense(new double[] { SelectCalibration.CenterPoint.X, SelectCalibration.CenterPoint.Y });
-
-
-                if (SelectCalibration.CameraMount == Enums.CameraMount.MobileDown_XYPlatform)
-                {
-                    var res = await AutoTechP0(SelectCalibration.CalibTechP0Mode);
-                    if (!res)
-                    {
-                        MessageBox.Show(Lang.自动示教P0失败);
-                        return;
-                    }
-                }
-                else if (Robot.Brand== Enums.RobotBrand.XYZ_Platform)
-                {
-                    var res = await AutoTechP0(SelectCalibration.CalibTechP0Mode);
-                    if (!res)
-                    {
-                        MessageBox.Show(Lang.自动示教P0失败);
-                        return;
-                    }
-                }
-
-                    /// <summary>
-                    /// 机器人与相机之间的旋转矩阵
-                    /// </summary>
-                    Matrix<double> RobotCameraRotationMatrix = Matrix<double>.Build.DenseOfArray(SelectCalibration.RobotCameraRotationMatrix);
-                //1
-                P1 = RobotCameraRotationMatrix.Inverse() * P1 + P0;
-                //2
-                P2 = RobotCameraRotationMatrix.Inverse() * P2 + P0;
-                //3
-                P3 = RobotCameraRotationMatrix.Inverse() * P3 + P0;
-                //4
-                P4 = RobotCameraRotationMatrix.Inverse() * P4 + P0;
-                //5
-                P5 = RobotCameraRotationMatrix.Inverse() * P5 + P0;
-                //6
-                P6 = RobotCameraRotationMatrix.Inverse() * P6 + P0;
-                //7
-                P7 = RobotCameraRotationMatrix.Inverse() * P7 + P0;
-                //8
-                P8 = RobotCameraRotationMatrix.Inverse() * P8 + P0;
-                //9
-                P9 = RobotCameraRotationMatrix.Inverse() * P9 + P0;
-
-                List<RPoint> rPoints = new List<RPoint>();
-                rPoints.Add(new RPoint()
-                {
-                    Number = 1,
-                    X = (float)P1[0],
-                    Y = (float)P1[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                rPoints.Add(new RPoint()
-                {
-                    Number = 2,
-                    X = (float)P2[0],
-                    Y = (float)P2[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                rPoints.Add(new RPoint()
-                {
-                    Number = 3,
-                    X = (float)P3[0],
-                    Y = (float)P3[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                rPoints.Add(new RPoint()
-                {
-                    Number = 4,
-                    X = (float)P4[0],
-                    Y = (float)P4[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                rPoints.Add(new RPoint()
+                _cts=new CancellationTokenSource();
+                (bool IsSuccess, Matrix<double> AffineTransformationMaterial, RobotPixelPoint[] NinePoint, CalibrationResult Result) = await _cameraCalibrationService.AutoNinePointCalibration(SelectCalibration, Robot, _cts.Token);
+                if (!IsSuccess)
                 {
-                    Number = 5,
-                    X = (float)P5[0],
-                    Y = (float)P5[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                rPoints.Add(new RPoint()
-                {
-                    Number = 6,
-                    X = (float)P6[0],
-                    Y = (float)P6[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                rPoints.Add(new RPoint()
-                {
-                    Number = 7,
-                    X = (float)P7[0],
-                    Y = (float)P7[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                rPoints.Add(new RPoint()
-                {
-                    Number = 8,
-                    X = (float)P8[0],
-                    Y = (float)P8[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                rPoints.Add(new RPoint()
-                {
-                    Number = 9,
-                    X = (float)P9[0],
-                    Y = (float)P9[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                List<PointF> PixelPoslist = new List<PointF>();
-                if (SelectCalibration.CalibPoints == null)
-                {
-                    SelectCalibration.CalibPoints = new System.Collections.ObjectModel.ObservableCollection<RobotPixelPoint>();
-                }
-                else
-                {
-                    SelectCalibration.CalibPoints.Clear();
+                    MessageBox.Show("相机校准失败", $"{Lang.自动校准}", MessageBoxButton.OK, MessageBoxImage.Warning);
+                    return;
                 }
-
-                //移动相机标定时,需要先记住特征点的绝对坐标(相对于机器人坐标系) P0
-                if (SelectCalibration.CameraMount == Enums.CameraMount.MobileJ4)
-                {
-                    await TechP0();
-                }
-
-
-                for (int i = 0; i < rPoints.Count; i++)
-                {
-                    //移动机器人至i
-                    if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-                    {
-                        await Robot.CalibMotionAsync(rPoints[i], 0);
-                        //关闭吸气
-                        await Robot.CalibOutIOAsync(false);
-                        //移动机器人至待机位置
-                        await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-                    }
-                    else
-                    {
-                        await Robot.CalibMotionAsync(rPoints[i], rPoints[i].Z);
-                    }
-                    PixelPoslist.Add(Photo());
-
-                    if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-                    {
-                        //移动机器人至i
-                        await Robot.CalibMotionAsync(rPoints[i], 0);
-                        //打开吸气
-                        await Robot.CalibOutIOAsync(true);
-                    }
-
-                    SelectCalibration.CalibPoints.Add(new RobotPixelPoint()
-                    {
-                        Number = i + 1,
-                        Robot = new System.Drawing.PointF(rPoints[i].X, rPoints[i].Y),
-                        Pixel = new System.Drawing.PointF(PixelPoslist[i].X, PixelPoslist[i].Y)
-                    }); ; ;
-                }
-                if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-                {
-                    //移动机器人至待机位置
-                    await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-                }
-
-                Mat matPixel = new Mat(9, 2, MatType.CV_64F);
-                Mat matRobot = new Mat(9, 2, MatType.CV_64F);
-
-                for (int i = 0; i < 9; i++)
-                {
-                    matPixel.Set<double>(i, 0, PixelPoslist[i].X);
-                    matPixel.Set<double>(i, 1, PixelPoslist[i].Y);
-                    //如果是移动相机拍照需要将机器人的9点进行一个变换,为了达到Mark点动,相机不动的目的
-                    if (SelectCalibration.CameraMount == Enums.CameraMount.MobileJ4)
-                    {
-                        //计算每个点相对于第一个点移动的偏移量,将这个偏移量
-                        double ox = rPoints[i].X - rPoints[0].X;
-                        double oy = rPoints[i].Y - rPoints[0].Y;
-                        matRobot.Set<double>(i, 0, SelectCalibration.MarkPoint.X - ox);
-                        matRobot.Set<double>(i, 1, SelectCalibration.MarkPoint.Y - oy);
-                    }
-                    else if (SelectCalibration.CameraMount == Enums.CameraMount.MobileDown_XYPlatform)
-                    {
-                        //计算每个点相对于第一个点移动的偏移量,将这个偏移量
-                        double ox = rPoints[i].X - rPoints[0].X;
-                        double oy = rPoints[i].Y - rPoints[0].Y;
-                        matRobot.Set<double>(i, 0, SelectCalibration.MarkPoint.X - ox);
-                        matRobot.Set<double>(i, 1, SelectCalibration.MarkPoint.Y - oy);
-                    }
-                    else
-                    {
-                        //将tool 0下的坐标转换成Tool n下的坐标
-                        if (SelectCalibration.Tool != null)
-                        {
-                            ToolCoord toolCoord = new ToolCoord();
-                            toolCoord.SetTool(SelectCalibration.Tool.X, SelectCalibration.Tool.Y);
-                            double u1 = rPoints[i].U;
-                            if (Robot.Brand == Enums.RobotBrand.Schneider)
-                            {
-                                u1 *= -1;
-                            }
-                            var trans = toolCoord.GetToolnCoord(Vector<double>.Build.Dense(new double[] { rPoints[i].X, rPoints[i].Y }), u1);
-                            matRobot.Set<double>(i, 0, trans[0]);
-                            matRobot.Set<double>(i, 1, trans[1]);
-                        }
-                        else
-                        {
-                            matRobot.Set<double>(i, 0, rPoints[i].X);
-                            matRobot.Set<double>(i, 1, rPoints[i].Y);
-                        }
-                    }
-
-                }
-                Mat mat2d = Cv2.EstimateAffine2D(matPixel, matRobot);
-                double[,] vv = { { mat2d.Get<double>(0, 0), mat2d.Get<double>(0, 1), mat2d.Get<double>(0, 2) }, { mat2d.Get<double>(1, 0), mat2d.Get<double>(1, 1), mat2d.Get<double>(1, 2) } };
-                Matrix<double> matrix = Matrix<double>.Build.DenseOfArray(vv);
-                SelectCalibration.AffineTransformationMaterial = matrix.ToArray();
-
-
-                //mat2d.Get<double>(0, 0):表示x方向上的缩放。大于 1,则表示进行了放大操作;小于 1,则表示进行了缩小操作;等于 1,则表示没有进行缩放操作。
-                //mat2d.Get<double>(0, 1):表示垂直错切参数,与M[1,0]一起用于计算旋转角度
-                //mat2d.Get<double>(0, 2):表示x方向上的平移。
-                //mat2d.Get<double>(1, 0):表示水平错切参数,与M[0,1]一起用于计算旋转角度。
-                //mat2d.Get<double>(1, 1):表示y方向上的缩放。大于 1,则表示进行了放大操作;小于 1,则表示进行了缩小操作;等于 1,则表示没有进行缩放操作。
-                //mat2d.Get<double>(1, 2):表示y方向上的平移。
-                //double RotationAngle= Math.Atan2(mat2d.Get<double>(1, 0), mat2d.Get<double>(0, 1))*180/Math.PI; //旋转角度
-                //RMS(均方根值) 标定偏差
-                double sumX = 0, sumY = 0;
-                List<double> differenceX = new List<double>();
-                List<double> differenceY = new List<double>();
-                for (int i = 0; i < 9; i++)
-                {
-                    var pixel = Vector<double>.Build.Dense(new double[] { PixelPoslist[i].X, PixelPoslist[i].Y, 1 });
-                    if (SelectCalibration.CameraMount == Enums.CameraMount.MobileJ4)
-                    {
-                        //计算每个点相对于第一个点移动的偏移量,Mark点减去这个偏移量及是图像中对应的实际坐标
-                        double ox = rPoints[i].X - rPoints[0].X;
-                        double oy = rPoints[i].Y - rPoints[0].Y;
-                        differenceX.Add((SelectCalibration.MarkPoint.X - ox) - (matrix * pixel)[0]);
-                        differenceY.Add((SelectCalibration.MarkPoint.Y - oy) - (matrix * pixel)[1]);
-                    }
-                    else if (SelectCalibration.CameraMount == Enums.CameraMount.MobileDown_XYPlatform)
-                    {
-                        double ox = rPoints[i].X - rPoints[0].X;
-                        double oy = rPoints[i].Y - rPoints[0].Y;
-                        differenceX.Add((SelectCalibration.MarkPoint.X - ox) - (matrix * pixel)[0]);
-                        differenceY.Add((SelectCalibration.MarkPoint.Y - oy) - (matrix * pixel)[1]);
-                    }
-                    else
-                    {
-                        //将tool 0下的坐标转换成Tool n下的坐标
-                        if (SelectCalibration.Tool != null)
-                        {
-                            ToolCoord toolCoord = new ToolCoord();
-                            toolCoord.SetTool(SelectCalibration.Tool.X, SelectCalibration.Tool.Y);
-                            double u1 = rPoints[i].U;
-                            if (Robot.Brand == Enums.RobotBrand.Schneider)
-                            {
-                                u1 *= -1;
-                            }
-                            var trans = toolCoord.GetToolnCoord(Vector<double>.Build.Dense(new double[] { rPoints[i].X, rPoints[i].Y }), u1);
-                            differenceX.Add(trans[0] - (matrix * pixel)[0]);
-                            differenceY.Add(trans[1] - (matrix * pixel)[1]);
-                        }
-                        else
-                        {
-                            differenceX.Add(rPoints[i].X - (matrix * pixel)[0]);
-                            differenceY.Add(rPoints[i].Y - (matrix * pixel)[1]);
-                        }
-                    }
-
-
-                    sumX += Math.Pow(differenceX[differenceX.Count - 1], 2);
-                    sumY += Math.Pow(differenceY[differenceY.Count - 1], 2);
-                }
-                double rmsX, rmsY;
-                rmsX = Math.Sqrt(sumX / 9);
-                rmsY = Math.Sqrt(sumY / 9);
-
-                CalibrationResult calibrationResult = new CalibrationResult();
-                calibrationResult.ScaleX = mat2d.Get<double>(0, 0);
-                calibrationResult.ScaleY = mat2d.Get<double>(1, 1);
-                calibrationResult.TranslationX = mat2d.Get<double>(0, 2);
-                calibrationResult.TranslationY = mat2d.Get<double>(1, 2);
-                calibrationResult.Rotate = Math.Atan2(mat2d.Get<double>(1, 0), mat2d.Get<double>(0, 1)) * 180 / Math.PI;
-                calibrationResult.RMSX = rmsX;
-                calibrationResult.RMSY = rmsY;
-                calibrationResult.MaxErrorValueX = differenceX.Max();
-                calibrationResult.MaxErrorValueY = differenceY.Max();
-                SelectCalibration.CalibrationResult = calibrationResult;
+                SelectCalibration.AffineTransformationMaterial = AffineTransformationMaterial.ToArray();
+                SelectCalibration.CalibPoints = new System.Collections.ObjectModel.ObservableCollection<RobotPixelPoint>(NinePoint);
+                SelectCalibration.CalibrationResult = Result;
 
                 DialogParameters parameters = new DialogParameters();
-                parameters.Add("Result", calibrationResult);
+                parameters.Add("Result", Result);
                 _dialogService.Show("ShowCalibrationResult", parameters, rst =>
                 {
                     //对话框关闭之后的回调函数,可以在这解析结果。
@@ -536,8 +180,6 @@ namespace TeamAAS_VP.ViewModels.Calibration
                     }
 
                 });
-
-                //MessageBox.Show($"校准完成:RmsX:{rmsX}-RmsY:{rmsY}");
             }
             catch (Exception ex)
             {
@@ -546,31 +188,21 @@ namespace TeamAAS_VP.ViewModels.Calibration
             }
             finally
             {
+                try
+                {
+                    if (_cts != null && !_cts.IsCancellationRequested)
+                        _cts.Cancel();
+                    _cts.Dispose();
+                    _cts = null;
+                }
+                catch (Exception)
+                {
+
+                }
                 IsRunning = true;
             }
         }
 
-        /// <summary>
-        /// 记录P0点(移动相机校准时有效)
-        /// </summary>
-        async Task TechP0()
-        {
-            //移动机器人至中心位置
-            await Robot.CalibMotionAsync(SelectCalibration.CenterPoint, 0);
-
-            //将当前机器人Tool 0下的坐标转换成工具坐标下的坐标,并记录下来
-            ToolCoord toolCoord = new ToolCoord();
-            toolCoord.SetTool(SelectCalibration.Tool.X, SelectCalibration.Tool.Y);
-            Vector<double> p = Vector<double>.Build.Dense(new double[] { SelectCalibration.CenterPoint.X, SelectCalibration.CenterPoint.Y }); ;
-            var pn = toolCoord.GetToolnCoord(p, SelectCalibration.CenterPoint.U);
-            if (SelectCalibration.MarkPoint == null)
-            {
-                SelectCalibration.MarkPoint = new RPoint();
-            }
-            SelectCalibration.MarkPoint = SelectCalibration.CenterPoint.Clone();
-            SelectCalibration.MarkPoint.X = (float)pn[0];
-            SelectCalibration.MarkPoint.Y = (float)pn[1];
-        }
 
         /// <summary>
         /// 下一步
@@ -607,15 +239,14 @@ namespace TeamAAS_VP.ViewModels.Calibration
         /// 相机拍照
         /// </summary>
         /// <returns></returns>
-        /// <exception cref="Exception"></exception>
-        private PointF Photo()
+        private async Task<(bool IsSuccess, double X, double Y, double U, int ImageWidth, int ImageHeight)> CaptureAndProcess()
         {
             if (Feeder != null)
             {
                 Feeder.OpenLight();
                 Feeder.SetLightTimeOut((ushort)SelectCalibration.Brightness);
             }
-            Task.Delay(200).Wait();
+            await Task.Delay(200);
             //采集图像
             if (Camera != null)
             {
@@ -631,202 +262,16 @@ namespace TeamAAS_VP.ViewModels.Calibration
             {
                 var strpoints = outputCollection["Point"].Value.ToString();
                 string[] items = strpoints.Split(',');
-                return new PointF(float.Parse(items[0]), float.Parse(items[1]));
-            }
-            else
-            {
-                throw new Exception(Lang.拍照失败);
-            }
-        }
-
-        /// <summary>
-        /// 自动示教P0点
-        /// </summary>
-        /// <returns></returns>
-        private async Task<bool> AutoTechP0(Enums.CalibTechP0Mode calibTechP0)
-        {
-            // 移动机器人至中心位置
-            await Robot.CalibMotionAsync(SelectCalibration.CenterPoint, 0);
-            //计算相机与机器人坐标系的旋转矩阵
-            var rst = await MoveRobotThreePointComputCroodTrans(SelectCalibration.CenterPoint, ((float)SelectCalibration.Height) / 3);
-            SelectCalibration.RobotCameraRotationMatrix = rst.Item1.ToArray();
-            SelectCalibration.PixelScaleX = rst.Item2;
-            SelectCalibration.PixelScaleY = rst.Item3;
-            if (calibTechP0== Enums.CalibTechP0Mode.TechP0)
-            {
-                return true;
-            }
-
-            var imagecenter = Camera.Grab();
-            PointF P0 = new PointF((float)(imagecenter.Width / 2), (float)(imagecenter.Height / 2));       //中心点像素坐标
-
-            //先找相机中心点,校准中心点坐标
-            bool IsFinish = false;
-            var curPixel = Photo();
-            var curpos = Robot.GetRobotPos();
-            for (int i = 0; i < 10; i++)
-            {
-                //计算需要移动的偏移量,目的是逼近中心点
-                double offsetx = (P0.X - curPixel.X) * rst.Item2;
-                double offsety = (P0.Y - curPixel.Y) * rst.Item3;
-                var pos = rst.Item1.Inverse() * Vector<double>.Build.Dense(new double[] { offsetx, offsety });
-                curpos.X += (float)pos[0];
-                curpos.Y += (float)pos[1];
-                //机器人移动
-                await Robot.CalibMotionAsync(curpos, curpos.Z);
-
-                curPixel = Photo();
-                offsetx = P0.X - curPixel.X;
-                offsety = P0.Y - curPixel.Y;
-                //await Console.Out.WriteLineAsync($"移动后的差异:{offsetx},{offsety}");
-                if (Math.Abs(offsetx) < 1 && Math.Abs(offsetx) < 1)
-                {
-                    IsFinish = true;
-                    break;
-                }
-            }
-            if (IsFinish == true)
-            {
-                SelectCalibration.CenterPoint = Robot.GetRobotPos();
-                SelectCalibration.MarkPoint= SelectCalibration.CenterPoint.Clone();
-                return true;
-            }
-            else
-            {
-                return false;
-            }
-        }
-        /// <summary>
-        /// 移动机器人3个点计算旋转矩阵
-        /// </summary>
-        /// <returns>(旋转矩阵,PixelScaleX,PixelScaleY)</returns>
-        private async Task<Tuple<Matrix<double>, double, double>> MoveRobotThreePointComputCroodTrans(RPoint rPoint, float distance)
-        {
-            //-------------------------------------------P0-----------------------------------
-            //移动机器人至中心点
-
-            if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-            {
-                await Robot.CalibMotionAsync(rPoint, 0);
-                //关闭吸气
-                await Robot.CalibOutIOAsync(false);
-            }
-            else
-            {
-                await Robot.CalibMotionAsync(rPoint, rPoint.Z);
-            }
-
-
-            RPoint RobotP0 = await Robot.GetRobotPosAsync();
-            RPoint RobotP1 = RobotP0.Clone();
-            RPoint RobotP2 = RobotP0.Clone();
-            RobotP1.X += distance;
-            RobotP2.Y += distance;
-
-            if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-            {
-                //移动机器人至待机位置
-                await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-            }
-
-            var P0 = Photo();
-            //-------------------------------------------P1-----------------------------------
-            if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-            {
-                //移动机器人至P0
-                await Robot.CalibMotionAsync(RobotP0, 0);
-                //打开吸气
-                await Robot.CalibOutIOAsync(true);
-                //移动机器人至P1
-                await Robot.CalibMotionAsync(RobotP1, 0);
-                //关闭吸气
-                await Robot.CalibOutIOAsync(false);
-                //移动机器人至待机位置
-                await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
+                double x = double.Parse(items[0]);
+                double y = double.Parse(items[1]);
+                return (true, x, y, 0, Image.Width, Image.Height);
             }
             else
             {
-                //移动机器人至P1
-                await Robot.CalibMotionAsync(RobotP1, RobotP1.Z);
+                return (false, 0, 0, 0, Image.Width, Image.Height);
             }
-            var P1 = Photo();
-
-            //-------------------------------------------P2-----------------------------------
-            if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-            {
-                //移动机器人至P1
-                await Robot.CalibMotionAsync(RobotP1, 0);
-                //打开吸气
-                await Robot.CalibOutIOAsync(true);
-                //移动机器人至P2
-                await Robot.CalibMotionAsync(RobotP2, 0);
-                //关闭吸气
-                await Robot.CalibOutIOAsync(false);
-                //移动机器人至待机位置
-                await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-            }
-            else
-            {
-                //移动机器人至P2
-                await Robot.CalibMotionAsync(RobotP2, RobotP2.Z);
-            }
-            var P2 = Photo();
-
-            //-------------------------------------------P0--------------------------------------
-            if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-            {
-                //移动机器人至P2
-                await Robot.CalibMotionAsync(RobotP2, 0);
-                //打开吸气
-                await Robot.CalibOutIOAsync(true);
-                //移动机器人至P0
-                await Robot.CalibMotionAsync(RobotP0, 0);
-                //关闭吸气
-                await Robot.CalibOutIOAsync(false);
-                //移动机器人至待机位置
-                await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-            }
-            else
-            {
-                //移动机器人至P0
-                await Robot.CalibMotionAsync(RobotP0, RobotP0.Z);
-            }
-
-            //-----------------------------------------计算旋转矩阵------------------------------
-            Matrix<double> M = ComputRotationMatrix(P0, P1, P2);
-            //Theta = Acos(M11)
-            //相机与机器人坐标系角度:Theta * 180 / PI
-            //-----------------------------------------计算像素和毫米比例-----------------------
-            Vector<double> P11 = Vector<double>.Build.Dense(new double[] { distance, 0 });
-            Vector<double> P21 = Vector<double>.Build.Dense(new double[] { P1.X, P1.Y }) - Vector<double>.Build.Dense(new double[] { P0.X, P0.Y });
-            double PixelScaleX = Math.Abs((M * P11)[0] / P21[0]);       //mm/pixel
-
-            double PixelScaleY = Math.Abs((M * P11)[1] / P21[1]);      //mm/pixel
-            return new Tuple<Matrix<double>, double, double>(M, PixelScaleX, PixelScaleY);
         }
 
-        /// <summary>
-        /// 计算旋转矩阵
-        /// </summary>
-        /// <param name="P0"></param>
-        /// <param name="P1"></param>
-        /// <param name="P2"></param>
-        /// <returns></returns>
-        private Matrix<double> ComputRotationMatrix(PointF P0, PointF P1, PointF P2)
-        {
-            Vector<double> Point0 = Vector<double>.Build.Dense(new double[] { (double)P0.X, (double)P0.Y });
-            Vector<double> Point1 = Vector<double>.Build.Dense(new double[] { (double)P1.X, (double)P1.Y });
-            Vector<double> Point2 = Vector<double>.Build.Dense(new double[] { (double)P2.X, (double)P2.Y });
-
-            //2.计算坐标系的基向量:
-            Vector<double> E1 = (Point1 - Point0) / (Point1 - Point0).L2Norm();
-            Vector<double> E2 = (Point2 - Point0 - E1.DotProduct(Point2 - Point0) * E1) / (Point2 - Point0 - E1.DotProduct(Point2 - Point0) * E1).L2Norm();
-
-            //3.计算旋转矩阵:我们可以使用基向量组成的矩阵作为旋转矩阵。具体地,我们可以将基向量 e1 和 e2 分别作为新坐标系下的 $x$ 和 $y$ 轴,然后组成一个矩阵,再对这个矩阵求逆即可得到旋转矩阵。代码实现:
-            // Compute rotation matrix
-            Matrix<double> R = Matrix<double>.Build.DenseOfColumnVectors(E1, E2);
-            return R;
-        }
         #endregion
 
         #region 继承
@@ -857,6 +302,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
                 }
             }
             Camera = _cameraService.GetCamera(SelectCalibration.CameraID);
+            _cameraCalibrationService.CaptureAndProcessCallback= CaptureAndProcess;
         }
 
         /// <summary>
@@ -875,7 +321,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
         /// <param name="navigationContext"></param>
         public void OnNavigatedFrom(NavigationContext navigationContext)
         {
-
+            _cameraCalibrationService.CaptureAndProcessCallback = null;
         }
         #endregion
 

+ 4 - 3
TeamAAS-VM/ViewModels/Calibration/CalibrationSelectToolViewModel.cs

@@ -27,7 +27,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
         IRegionNavigationService _regionNavigationService;
         IContainerProvider _container;
         IDialogService _dialogService;
-        IEventAggregator _eventAggregator; 
+        IEventAggregator _eventAggregator;
         IConfigService _configService;
         IRobotService _robotService;
         ICameraService _cameraService;
@@ -61,7 +61,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
         private DelegateCommand _CalibToolCommand;
         public DelegateCommand CalibToolCommand =>
             _CalibToolCommand ?? (_CalibToolCommand = new DelegateCommand(ExecuteCalibToolCommand));
-        
+
         #endregion
 
         #region 事件
@@ -105,7 +105,8 @@ namespace TeamAAS_VP.ViewModels.Calibration
             {
                 IDialogParameters parameters = new DialogParameters();
                 parameters.Add("SelectCalibration", SelectCalibration);
-                _dialogService.Show("CalibTool", parameters, rst => {
+                _dialogService.Show("CalibTool", parameters, rst =>
+                {
                     //对话框关闭之后的回调函数,可以在这解析结果。
                     ButtonResult result1 = rst.Result;
 

+ 30 - 217
TeamAAS-VM/ViewModels/Calibration/CalibrationVerificationViewModel.cs

@@ -23,6 +23,7 @@ using TeamAAS_VP;
 using TeamAAS_VP.Resources.Languages;
 using TeamAAS_VP.Models.Calibration;
 using TeamAAS_VP.Services;
+using System.Threading;
 
 namespace TeamAAS_VP.ViewModels.Calibration
 {
@@ -38,6 +39,8 @@ namespace TeamAAS_VP.ViewModels.Calibration
         ICameraService _cameraService;
         IFeederService _feederService;
         ISystemDatabaseService _systemDatabaseService;
+        ICameraCalibrationService _cameraCalibrationService;
+        private CancellationTokenSource _cts;
 
         #region 属性
         private ICogImage _Image;
@@ -107,7 +110,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
         #endregion
 
         public CalibrationVerificationViewModel(IRegionManager regionManager, IEventAggregator ea, IRegionNavigationService regionNavigationService, IContainerProvider container, IDialogService dialogService,
-            IConfigService configService, IRobotService robotService, ICameraService cameraService, IFeederService feederService, ISystemDatabaseService systemDatabaseService)
+            IConfigService configService, IRobotService robotService, ICameraService cameraService, IFeederService feederService, ISystemDatabaseService systemDatabaseService, ICameraCalibrationService cameraCalibrationService)
         {
             _regionManager = regionManager;
             _regionNavigationService = regionNavigationService;
@@ -119,6 +122,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
             _cameraService = cameraService;
             _feederService = feederService;
             _systemDatabaseService = systemDatabaseService;
+            _cameraCalibrationService = cameraCalibrationService;
         }
 
         #region 方法
@@ -142,218 +146,15 @@ namespace TeamAAS_VP.ViewModels.Calibration
             try
             {
                 if (MessageBox.Show(Lang.是否开始测试, Lang.自动校准, MessageBoxButton.OKCancel, MessageBoxImage.Question) != MessageBoxResult.OK) return;
+                _cts = new CancellationTokenSource();
                 IsRunning = false;
-                double IntervalX = SelectCalibration.Width*0.90 / 2;
-                double IntervalY = SelectCalibration.Height * 0.90 / 2;
-                Vector<double> P1 = Vector<double>.Build.Dense(new double[] { -IntervalX, -IntervalY });
-                Vector<double> P2 = Vector<double>.Build.Dense(new double[] { IntervalX, -IntervalY });
-                Vector<double> P3 = Vector<double>.Build.Dense(new double[] { 0, 0 });
-                Vector<double> P4 = Vector<double>.Build.Dense(new double[] { -IntervalX, IntervalY });
-                Vector<double> P5 = Vector<double>.Build.Dense(new double[] { IntervalX, IntervalY });
-
-                //移动相机标定时,需要先记住特征点的绝对坐标(相对于机器人坐标系) P0
-                //if (SelectCalibration.CameraMount == Enums.CameraMount.MobileJ4)
-                //{
-                //    P1[0] *= -1; P1[1] *= -1;
-                //    P2[0] *= -1; P2[1] *= -1;
-                //    P3[0] *= -1; P3[1] *= -1;
-                //    P4[0] *= -1; P4[1] *= -1;
-                //    P5[0] *= -1; P5[1] *= -1;
-                //}
-
-                var P0 = Vector<double>.Build.Dense(new double[] { SelectCalibration.CenterPoint.X, SelectCalibration.CenterPoint.Y });
-                /// <summary>
-                /// 机器人与相机之间的旋转矩阵
-                /// </summary>
-                Matrix<double> RobotCameraRotationMatrix = Matrix<double>.Build.DenseOfArray(SelectCalibration.RobotCameraRotationMatrix);
-                //1
-                P1 = RobotCameraRotationMatrix.Inverse() * P1 + P0;
-                //2
-                P2 = RobotCameraRotationMatrix.Inverse() * P2 + P0;
-                //3
-                P3 = RobotCameraRotationMatrix.Inverse() * P3 + P0;
-                //4
-                P4 = RobotCameraRotationMatrix.Inverse() * P4 + P0;
-                //5
-                P5 = RobotCameraRotationMatrix.Inverse() * P5 + P0;
-                List<RPoint> rPoints = new List<RPoint>();
-                rPoints.Add(new RPoint()
-                {
-                    Number = 1,
-                    X = (float)P1[0],
-                    Y = (float)P1[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                rPoints.Add(new RPoint()
-                {
-                    Number = 2,
-                    X = (float)P2[0],
-                    Y = (float)P2[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                rPoints.Add(new RPoint()
-                {
-                    Number = 3,
-                    X = (float)P3[0],
-                    Y = (float)P3[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                rPoints.Add(new RPoint()
-                {
-                    Number = 4,
-                    X = (float)P4[0],
-                    Y = (float)P4[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-                rPoints.Add(new RPoint()
-                {
-                    Number = 5,
-                    X = (float)P5[0],
-                    Y = (float)P5[1],
-                    Z = SelectCalibration.CenterPoint.Z,
-                    U = SelectCalibration.CenterPoint.U,
-                    V = SelectCalibration.CenterPoint.V,
-                    W = SelectCalibration.CenterPoint.W,
-                    Hand = SelectCalibration.CenterPoint.Hand,
-                    Local = SelectCalibration.CenterPoint.Local,
-                    Tool = SelectCalibration.CenterPoint.Tool
-                });
-
-                List<Vector<double>> TestPos = new List<Vector<double>>();
-                for (int i = 0; i < rPoints.Count; i++)
-                {
-                    if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-                    {
-                        //移动机器人至i
-                        await Robot.CalibMotionAsync(rPoints[i], 0);
-                        //关闭吸气
-                        await Robot.CalibOutIOAsync(false);
-                        //移动机器人至待机位置
-                        await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-                    }
-                    else
-                    {
-                        //移动机器人至i
-                        await Robot.CalibMotionAsync(rPoints[i], rPoints[i].Z);
-                    }
-                    var pixelpos = Photo();
-                    Matrix<double> matrix = Matrix<double>.Build.DenseOfArray(SelectCalibration.AffineTransformationMaterial);
-                    var rpos = matrix * Vector<double>.Build.Dense(new double[] { pixelpos.X, pixelpos.Y ,1});
-                    TestPos.Add(rpos);
-                    if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-                    {
-                        //移动机器人至i
-                        await Robot.CalibMotionAsync(rPoints[i], 0);
-                        //打开吸气
-                        await Robot.CalibOutIOAsync(true);
-                    }
-                }
-
-                if (SelectCalibration.CameraMount == Enums.CameraMount.FixedDown)
-                {
-                    //移动机器人至待机位置
-                    await Robot.CalibMotionAsync(SelectCalibration.HomePoint, 0);
-                }
-
-                //RMS(均方根值) 标定偏差
-                double sumX = 0, sumY = 0;
-                List<double> differenceX = new List<double>();
-                List<double> differenceY = new List<double>();
-                for (int i = 0; i < 5; i++)
+                (bool IsSuccess, CalibrationTestResult Result) = await _cameraCalibrationService.ExecuteCalibrationValidation(SelectCalibration, Robot, _cts.Token);
+                if (!IsSuccess)
                 {
-                    if (SelectCalibration.CameraMount == Enums.CameraMount.MobileJ4)
-                    {
-                        //像素直接转换成mm时的点位
-                        double Robot_X, Robot_Y;
-                        Robot_X = TestPos[i][0];
-                        Robot_Y= TestPos[i][1];
-
-                        //机器人当前TOOL 0下的坐标
-                        double curpos_x = 0, curpos_y = 0, curpos_u = 0;
-                        curpos_x = rPoints[i].X;
-                        curpos_y = rPoints[i].Y;
-                        curpos_u = rPoints[i].U;
-
-                        //得到旋转矩阵
-                        double angle = Math.PI * (curpos_u - SelectCalibration.MarkPoint.U) / 180;
-                        // 创建T矩阵
-                        Matrix<double> rotationMatrix = Matrix<double>.Build.DenseOfArray(new double[,]
-                        {
-                                { Math.Cos(angle), -Math.Sin(angle) },
-                                { Math.Sin(angle), Math.Cos(angle) }
-                        });
-
-                        Vector<double> p3 = Vector<double>.Build.Dense(new double[] { Robot_X - SelectCalibration.CalibPoints[0].Robot.X, Robot_Y - SelectCalibration.CalibPoints[0].Robot.Y });
-                        Vector<double> phere = Vector<double>.Build.Dense(new double[] { curpos_x, curpos_y });
-
-                        var cc = rotationMatrix * p3 + phere;
-
-                        differenceX.Add(cc[0] - SelectCalibration.MarkPoint.X);
-                        differenceY.Add(cc[1] - SelectCalibration.MarkPoint.Y);
-                    }
-                    else
-                    {
-                        //将tool 0下的坐标转换成Tool n下的坐标
-                        if (SelectCalibration.Tool != null)
-                        {
-                            ToolCoord toolCoord = new ToolCoord();
-                            toolCoord.SetTool(SelectCalibration.Tool.X, SelectCalibration.Tool.Y);
-                            double u1 = rPoints[i].U;
-                            if (Robot.Brand == Enums.RobotBrand.Schneider)
-                            {
-                                u1 *= -1;
-                            }
-                            var trans = toolCoord.GetToolnCoord(Vector<double>.Build.Dense(new double[] { rPoints[i].X, rPoints[i].Y }), u1);
-                            differenceX.Add(trans[0] - TestPos[i][0]);
-                            differenceY.Add(trans[1] - TestPos[i][1]);
-                        }
-                        else
-                        {
-                            differenceX.Add(rPoints[i].X - TestPos[i][0]);
-                            differenceY.Add(rPoints[i].Y - TestPos[i][1]);
-                        }
-                    }
-                    
-                    sumX += Math.Pow(differenceX[differenceX.Count - 1], 2);
-                    sumY += Math.Pow(differenceY[differenceY.Count - 1], 2);
+                    MessageBox.Show(Lang.失败);
+                    return;
                 }
-                double rmsX, rmsY;
-                rmsX = Math.Sqrt(sumX / 5);
-                rmsY = Math.Sqrt(sumY / 5);
-
-                CalibrationTestResult calibrationTestResult = new CalibrationTestResult();
-                calibrationTestResult.MaxErrorValueX = differenceX.Max();
-                calibrationTestResult.MinErrorValueX = differenceX.Min();
-                calibrationTestResult.MaxErrorValueY = differenceY.Max();
-                calibrationTestResult.MinErrorValueY = differenceY.Min();
-                calibrationTestResult.RMSEX = rmsX;
-                calibrationTestResult.RMSEY = rmsY;
-
-                SelectCalibration.CalibrationTestResult = calibrationTestResult;
+                SelectCalibration.CalibrationTestResult = Result;
                 MessageBox.Show(Lang.验证完成);
                 NavigationParameters param = new NavigationParameters();
                 param.Add("SelectCalibration", SelectCalibration);
@@ -367,6 +168,17 @@ namespace TeamAAS_VP.ViewModels.Calibration
             }
             finally
             {
+                try
+                {
+                    if (_cts != null && !_cts.IsCancellationRequested)
+                        _cts.Cancel();
+                    _cts.Dispose();
+                    _cts = null;
+                }
+                catch (Exception)
+                {
+
+                }
                 IsRunning = true;
             }
         }
@@ -395,22 +207,20 @@ namespace TeamAAS_VP.ViewModels.Calibration
         /// 相机拍照
         /// </summary>
         /// <returns></returns>
-        /// <exception cref="Exception"></exception>
-        private PointF Photo()
+        private async Task<(bool IsSuccess, double X, double Y, double U, int ImageWidth, int ImageHeight)> CaptureAndProcess()
         {
             if (Feeder != null)
             {
                 Feeder.OpenLight();
                 Feeder.SetLightTimeOut((ushort)SelectCalibration.Brightness);
             }
-            Task.Delay(200).Wait();
+            await Task.Delay(200);
             //采集图像
             if (Camera != null)
             {
                 SelectCalibration.ToolBlock.Inputs["InputImage"].Value = Camera.Grab();
                 Image = (ICogImage)SelectCalibration.ToolBlock.Inputs["InputImage"].Value;
             }
-
             Graphic = null;
             SelectCalibration.ToolBlock.Run();
             CogToolBlockTerminalCollection outputCollection = SelectCalibration.ToolBlock.Outputs;
@@ -420,11 +230,13 @@ namespace TeamAAS_VP.ViewModels.Calibration
             {
                 var strpoints = outputCollection["Point"].Value.ToString();
                 string[] items = strpoints.Split(',');
-                return new PointF(float.Parse(items[0]), float.Parse(items[1]));
+                double x = double.Parse(items[0]);
+                double y = double.Parse(items[1]);
+                return (true, x, y, 0, Image.Width, Image.Height);
             }
             else
             {
-                throw new Exception(Lang.拍照失败);
+                return (false, 0, 0, 0, Image.Width, Image.Height);
             }
         }
         #endregion
@@ -458,6 +270,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
 
             }
             Camera = _cameraService.GetCamera(SelectCalibration.CameraID);
+            _cameraCalibrationService.CaptureAndProcessCallback= CaptureAndProcess;
         }
 
         /// <summary>
@@ -476,7 +289,7 @@ namespace TeamAAS_VP.ViewModels.Calibration
         /// <param name="navigationContext"></param>
         public void OnNavigatedFrom(NavigationContext navigationContext)
         {
-
+            _cameraCalibrationService.CaptureAndProcessCallback = null;
         }
         #endregion