using System.Configuration; using System.Data; using System.Diagnostics; using System.IO; using System.Text; using System.Windows; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; using System.Windows.Threading; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using SparkClient.Model.Attributes; using SparkClient.Model.Common; using SparkClient.Model.Entity.ApiEntity; using SparkClient.Model.Extension; using SparkClient.Model.Helper; using SparkClient.Model.Services; using SparkClient.ViewModel.BaseWindow; using SparkClient.ViewModel.Configuration; using SparkClient.Views.Dialog; using SparkDotNetCore.DiamondScanner; using SparkDotNetCore.DiamondScanner.Entity; using MessageBox = SparkClient.Views.Dialog.MessageBox; namespace SparkClient.ViewModel.Grading; public class GradingLoadingVM : BaseViewModel,IDisposable { private double _progress; public AlgorithmResultEntity Parameter; /// /// 进度 /// public double Progress { get => _progress; set { _progress = value; OnPropertyChanged(nameof(Progress)); } } public ICommand StopCommand { get; } private string _diamondCode; private string _diamnondType; private bool _disposed; private CancellationTokenSource _progressCts; private CancellationTokenSource? _playbackCts; private Diamond _diamond; private Scanner _scanner; private CancellationTokenSource _completionCts; string strImageModeImagePath = string.Empty; #region 图片播放控制 private PlayStatus _currentStatus = PlayStatus.Stopped; public PlayStatus CurrentStatus { get => _currentStatus; set { _currentStatus = value; OnPropertyChanged(nameof(CurrentStatus)); OnPropertyChanged(nameof(ButtonText)); // 状态变更时更新按钮文本 } } public string ButtonText => CurrentStatus switch { PlayStatus.Playing => MultilingualHelper.getString("GradingLoadingPaused"), PlayStatus.Paused => MultilingualHelper.getString("GradingLoadingContinue"), _ => MultilingualHelper.getString("GradingLoadingReplay") // Stopped 状态 }; private CancellationTokenSource _cts; private int _playDelay = 100; // 默认播放速度 public ICommand PlayControlCommand { get; } public ICommand PreviousCommand { get; } public ICommand NextCommand { get; } private int _currentIndex; public int CurrentIndex { get => _currentIndex; set { _currentIndex = value; OnPropertyChanged(nameof(CurrentIndex)); } } public string[] ImagePaths { get; set; } private ImageSource _currentImage; public ImageSource CurrentImage { get => _currentImage; private set { _currentImage = value; OnPropertyChanged(nameof(CurrentImage)); } } private bool _imageIsEnable; public bool ImageIsEnable { get => _imageIsEnable; private set { _imageIsEnable = value; OnPropertyChanged(nameof(ImageIsEnable)); // 触发通知 } } #endregion private bool _isCancel = false; private Timer _monitorTimer; private static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1); public GradingLoadingVM(string diamnondType, string diamondCode) { _diamondCode = diamondCode; _diamnondType = diamnondType; StopCommand = new RelayCommand(Stop); PlayControlCommand = new RelayCommand(async _ => await HandlePlayControl()); PreviousCommand = new RelayCommand(_ => MovePrevious()); NextCommand = new RelayCommand(_ => MoveNext()); ImageIsEnable = false; _progressCts = new CancellationTokenSource(); _playbackCts = new CancellationTokenSource(); _completionCts = new CancellationTokenSource(); Logger.Info("周期检查舱门,周期500ms"); if (_monitorTimer == null) { _monitorTimer = new Timer(CheckSpeedCallback, null, 500, Timeout.Infinite); } } private async void CheckSpeedCallback(object state) { try { GpioStatus gpioStatus = await SOCClientService.Service.GetGpioStatus(); Application.Current.Dispatcher.Invoke(() => { if (gpioStatus.LensGpioIsOpen() || gpioStatus.DiamondGpioIsOpen()) { new MessageBox().Show(MultilingualHelper.getString("OpenOfTheHatch")); WindowManager.mainViewModel.Content = WindowManager.PreviousVM(); _scanner?.Cancel(); _isCancel = true; _progressCts.Cancel(); this.Dispose(); _monitorTimer.Dispose(); SOCClientService.Service.OpenPump(false); return; } _monitorTimer.Change(500, Timeout.Infinite); }, DispatcherPriority.Normal); } catch (Exception ex) { Logger.Info($"监控异常: {ex.Message}"); } } /// /// 开始检测 /// [Log] public async Task Start(int type = 0) { try { var progress = RunProgressAsync(_progressCts.Token); if (type == 11) { JsonImport jsonImport = new JsonImport(); bool? a = jsonImport.ShowDialog(); if (a ?? false) { string fileName = jsonImport.FilePath.Text; string[] lines = File.ReadAllLines(fileName); StringBuilder stringBuilder = new StringBuilder(); Logger.Info($"Json模式,文件路径:{fileName}"); foreach (var line in lines) { stringBuilder.Append(line); } var settings = new JsonSerializerSettings { ContractResolver = new DefaultValueContractResolver(), NullValueHandling = NullValueHandling.Ignore }; Parameter = JsonConvert.DeserializeObject(stringBuilder.ToString(),settings); Parameter.DiamondCode = _diamondCode; Parameter.Standard = getStandardName(); Parameter.Shape = _diamnondType.Split(" ")[0]; Parameter.CrownType = _diamnondType.Split(" ")[1]; Parameter.PavType = _diamnondType.Split(" ")[2]; Parameter.ErrorMsg = _diamnondType; await CompleteProgressQuicklyAsync(); return 0; } else { return -1; } } if(type == 0) { await SOCClientService.Service.OpenPump(true); // if (!StatusCodes.Success.Equals(openpupmStatus)) // { // new MessageBox().Show("气泵开启失败!"); // return -1; // } Task processImage = SOCClientService.Service.ProcessImageCollectionAsync(); //通知页面可以播放图片 LoadDefaultImages(); await processImage; await Task.Delay(1000); // // 采集状态接口 // string acquisitionStatus = await SOCClientService.Service.CollectStatusAsync(); // // 成功 // if (acquisitionStatus != StatusCodes.Success) // { // Logger.Debug($"acquisitionStatus != StatusCodes.Success : {acquisitionStatus}"); // _progressCts.Cancel(); // new MessageBox().Show( // MultilingualHelper.getString(StatusCodes.GetConstantNameByValue(acquisitionStatus))); // return -1; // } if (!("ok".Equals(processImage.Result.Status) || "S000".Equals(processImage.Result.Status))) { _progressCts.Cancel(); new MessageBox().Show( MultilingualHelper.getString(StatusCodes.GetConstantNameByValue(processImage.Result.Status))); return -1; } // LoadImages(processImage.Result.Images); } if (type == 1) { ImageSelect jsonImport = new ImageSelect(); bool? a = jsonImport.ShowDialog(); if (a ?? false) { strImageModeImagePath = jsonImport.FilePath.Text; string name = jsonImport.FileName.Text; List mnFiles = new List(); for (int i = 0; i < 100; i++) { mnFiles.Add(name.Replace("%d", i.ToString())); } Logger.Info($"Image模式,图片路径:{strImageModeImagePath}"); LoadImages(mnFiles); } else { return -1; } StartPlayback(); } ImageIsEnable = true; // StartPlayback(); if (_isCancel == true) { SOCClientService.Service.OpenPump(false); return -100; } Diamond diamond = new Diamond(); string circleSql = $"SELECT VALUE FROM CUTTER_CONFIG WHERE KEY = 'half_circle'"; DataTable circleTable = DataBaseHelper.ExecuteQuery(circleSql); object halfCircleValue = circleTable.Rows[0][0]; bool.TryParse(halfCircleValue.ToString(), out bool boolResult); bool half_circle = boolResult; //算法配置参数,初始化算法配置数据并获取 AlgorithmConfigJson var _algorithmConfigVM = new AlgorithmConfigVM(); _algorithmConfigVM.InitAlgorithmData(_diamnondType); // string algo_config = _algorithmConfigVM.AlgorithmConfigJson; string algo_config = _algorithmConfigVM.GetAlgorithmConfig(_diamnondType); string imageFileBasePath = string.Empty; //图片根目录 if (type == 1) { imageFileBasePath = strImageModeImagePath; } else { imageFileBasePath = ConfigurationManager.AppSettings["ImageFileBasePath"]; } if (string.IsNullOrEmpty(imageFileBasePath)) { throw new InvalidOperationException("ImageFileBasePath is not configured in AppSettings."); } // 获取 log4net 日志文件所在的目录 string? log4NetLogDirectory = Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location); // 构建C++ DLL 日志文件路径 string algorithm_log_path = Path.Combine(log4NetLogDirectory, "logs"); // 将所有变量拼接成一个 JSON 对象 JObject jsonData = new JObject( new JProperty("shape", _diamnondType.Split(" ")[0]), new JProperty("shape_mode", _diamnondType.Split(" ")[1]), new JProperty("image_file_base_path", imageFileBasePath), new JProperty("image_files", ImagePaths), new JProperty("half_circle", half_circle), new JProperty("algorithm_log_path", algorithm_log_path), new JProperty("algo_config", JObject.Parse(algo_config)) ); Logger.Info($"算法启动,输入参数(Encrypt):{AESHelper.Encrypt(jsonData.ToString())}"); _scanner = new Scanner(diamond); var detectTask = _scanner.DetectAsyncByJsonStr(jsonData.ToString()); Logger.Info($"开始等待算法结果"); await detectTask; if (detectTask.Status == TaskStatus.RanToCompletion) { return await ReslutGen(detectTask); } await progress; return await ReslutGen(detectTask); } catch(Exception ex) { Logger.Error(ex.Message); if (_isCancel) { return -100; } else { return -10; } } } [Log] private async Task ReslutGen(Task detectTask) { bool hasErr = false; Logger.Info($"算法运行完毕:{detectTask.Status}"); Logger.Info($"算法运行结果:[Status = {detectTask.Result.Status}; Message = {detectTask.Result.ErrorMsg}]"); try { CompleteProgressQuicklyAsync(); if (detectTask.Result != null) { switch (detectTask.Result.Status) { case StatusCodes.AlgorithmFailed: new MessageBox().Show(MultilingualHelper.getString("AlgorithmFailed")); hasErr = true; return -1; case StatusCodes.ImageFileReadFailure: new MessageBox().Show(MultilingualHelper.getString("ImageFileReadFailure")); hasErr = true; return -1; case StatusCodes.JsonParseFailure: new MessageBox().Show(MultilingualHelper.getString("JsonParseFailure")); hasErr = true; return -1; case StatusCodes.NoDiamond: new MessageBox().Show(MultilingualHelper.getString("NoDiamond")); hasErr = true; return -1; case "DETECTING_EXCEPTION": new MessageBox().Show(MultilingualHelper.getString("DETECTING_EXCEPTION") + detectTask.Result.ErrorMsg); hasErr = true; return -1; case "DETECTING_RESULT_ISNULL": new MessageBox().Show(MultilingualHelper.getString("DETECTING_RESULT_ISNULL") + detectTask.Result.ErrorMsg); hasErr = true; return -1; } Progress = (100.00); string strParam = JsonConvert.SerializeObject(detectTask.Result); //Logger.Info("序列化字符串:" + strParam); AlgorithmResultEntity parameter = JsonConvert.DeserializeObject(strParam); if (parameter == null && _isCancel == false) { new MessageBox().Show(MultilingualHelper.getString("JsonParseFailure")); hasErr = true; return -1; } parameter.DiamondCode = _diamondCode; parameter.Standard = getStandardName(); parameter.Shape = _diamnondType.Split(" ")[0]; parameter.CrownType = _diamnondType.Split(" ")[1]; parameter.PavType = _diamnondType.Split(" ")[2]; parameter.ErrorMsg = _diamnondType; //实验室模式关闭气泵 if (Common.RunMode == 0) { SOCClientService.Service.OpenPump(false); } _monitorTimer.Dispose(); try { Logger.Info($"算法结果Json单独保存至log/result"); string parameterJson = JsonConvert.SerializeObject(parameter); parameterJson = JToken.Parse(parameterJson).ToString(); string outputPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs", "result"); if (!Directory.Exists(outputPath)) Directory.CreateDirectory(outputPath); string outputFilePath = $"{outputPath}/{_diamondCode}-{DateTime.Now:yyyyMMdd_HHmmss}.json"; byte[] data = Encoding.Unicode.GetBytes(parameterJson); byte[] outData = AESHelper.Encrypt(data); File.WriteAllBytes(outputFilePath, outData); //using (var file = File.Create(outputFilePath)) // using (StreamWriter stream = new StreamWriter(file)) // { // stream.Write(parameterJson); // } Logger.Info($"算法结果Json单独保存至log/result 完毕!"); } catch (Exception ex) { Logger.Error("output输出失败:" + ex.Message); } Parameter = parameter; if (_isCancel == true) { SOCClientService.Service.OpenPump(false); return -100; } ; if (parameter.Status == StatusCodes.Recheck) { new MessageBox().Show(MultilingualHelper.getString("Recheck")); } return 0; } return -10; } finally { string strSafeSpace = ConfigurationHelper.ReadConfigValue("SafeSpaceReservation"); if (hasErr || "0".Equals(strSafeSpace) || Parameter.Status == StatusCodes.Recheck) { DiamondSelectVM.HandleAlgorithmFailure(ImagePaths, _diamondCode, _diamnondType); } } } [Log] private async Task CompleteProgressQuicklyAsync() { // 取消原进度任务 _progressCts.Cancel(); // 快速完成剩余进度(0.5秒内完成) int current = (int)Progress; int remaining = 100 - current; if (remaining <= 0) return; int stepTime = Math.Max(50, 500 / remaining); // 动态计算步长 while (current < 100) { current = Math.Min(current + 1, 100); UpdateProgress(current); await Task.Delay(stepTime/2); } } [Log] private void UpdateProgress(int value) { // UI线程安全更新 Application.Current.Dispatcher.Invoke(() => { Progress = value; }); } [Log] private void Stop(object param) { //询问?停止:忽略 try { MessageBox messageBox = new MessageBox(); MessageBoxResult showAsk = messageBox.ShowAsk(MultilingualHelper.getString("GradingLoadingStopAsk")); if (showAsk == MessageBoxResult.OK) { WindowManager.mainViewModel.Content = WindowManager.PreviousVM(); _scanner?.Cancel(); _monitorTimer.Dispose(); _isCancel = true; _progressCts.Cancel(); this.Dispose(); } } catch (Exception ex) { } } [Log] private async Task RunProgressAsync(CancellationToken token) { // var configValue = ConfigurationHelper.ReadConfigValue("ProgressTime"); int totalDuration = 50000; int stepTime = totalDuration / 97; // 使用 IProgress 实现线程安全的进度报告 var progress = new Progress(value => { if (!token.IsCancellationRequested) Progress = value; }); await Task.Run(async () => { for (int i = 0; i <= 97; i++) { token.ThrowIfCancellationRequested(); // 报告进度 ((IProgress)progress).Report(i); // 使用可取消的延迟 await Task.Delay(stepTime, token); } }, token); } [Log] public void Dispose() { _monitorTimer.Dispose(); Dispose(true); GC.SuppressFinalize(this); } [Log] protected virtual void Dispose(bool disposing) { if (_disposed) return; if (disposing) { // 取消所有操作 _progressCts?.Cancel(); _playbackCts?.Cancel(); // 释放托管资源 _progressCts?.Dispose(); _playbackCts?.Dispose(); } _disposed = true; } [Log] private string getStandardName() { string sql = $"select\r\nRULE_NAME AS NAME,\r\nRULE_EN_NAME AS EN_NAME\r\nfrom\r\nsetting\r\nleft join rule\r\non setting.SETTING_P = rule.RULE_ID\r\nwhere\r\nsetting.SETTING_ID = 'RuleId'\r\n"; DataTable dataTable = DataBaseHelper.ExecuteQuery(sql); if (dataTable == null || dataTable.Rows.Count == 0) { return ""; } else { return dataTable.Rows[0][MultilingualHelper.getString("NameType")].ToString()??""; } } #region 图片播放处理 [Log] public void LoadDefaultImages() { List imagePaths = new List(); string? savePath = ConfigurationHelper.ReadConfigValue("ImageFileBasePath"); int imageTotal = ConfigurationHelper.ReadConfigValueToInteger("DetectImageTotal", 100); for (int i = 0; i < imageTotal; i++) { imagePaths.Add($"image_{i}.bmp"); } ImagePaths = imagePaths.ToArray(); CurrentIndex = 0; CurrentStatus = PlayStatus.Stopped; MonitorAndPlayImagesAsync(savePath); } [Log] public void LoadImages(List folderPath) { ImagePaths = folderPath.ToArray(); CurrentIndex = 0; UpdateCurrentImage(); CurrentStatus = PlayStatus.Stopped; } [Log] private async Task MonitorAndPlayImagesAsync(string folderPath) { var sw = new Stopwatch(); for (int i = 0; i < 100; i++) { sw.Restart(); // 等待文件就绪 var filePath = Path.Combine(folderPath, $"image_{i}.bmp"); while (!await IsFileReady(filePath)) { await Task.Delay(50); // 每50ms检测一次文件状态 } // 加载图片到内存 var image = await LoadImageSafely(filePath); // 更新UI显示 await Application.Current.Dispatcher.InvokeAsync(() => { CurrentImage = image; OnPropertyChanged(nameof(CurrentImage)); }); // 强制最小显示间隔 var remainTime = Math.Max(100 - (int)sw.ElapsedMilliseconds, 0); await Task.Delay(remainTime); } } [Log] private async Task IsFileReady(string path) { try { // 双重验证机制 if (!File.Exists(path)) return false; // 尝试读取文件头验证完整性 using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read)) { byte[] header = new byte[2]; await fs.ReadAsync(header, 0, 2); return header[0] == 0x42 && header[1] == 0x4D; // BMP文件头标识 } } catch { return false; } } [Log] private async Task LoadImageSafely(string path) { using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read)) { var bitmap = new BitmapImage(); await Application.Current.Dispatcher.InvokeAsync(() => { bitmap.BeginInit(); bitmap.CacheOption = BitmapCacheOption.OnLoad; bitmap.StreamSource = fs; bitmap.EndInit(); bitmap.Freeze(); // 跨线程安全 }); return bitmap; } } [Log] private async Task HandlePlayControl() { switch (CurrentStatus) { case PlayStatus.Stopped: await StartPlayback(); // 开始或重播 break; case PlayStatus.Playing: PausePlayback(); // 暂停 break; case PlayStatus.Paused: await ResumePlayback();// 继续 break; } } [Log] private async Task StartPlayback() { CurrentStatus = PlayStatus.Playing; _cts = new CancellationTokenSource(); try { for (CurrentIndex = 0; CurrentIndex < ImagePaths.Length; CurrentIndex++) { if (_cts.Token.IsCancellationRequested) break; UpdateCurrentImage(); await Task.Delay(_playDelay); } // 播放完成处理 if (CurrentIndex >= ImagePaths.Length) { CurrentStatus = PlayStatus.Stopped; CurrentIndex = 0; // 重置为初始位置 } } catch (TaskCanceledException) { /* 正常取消处理 */ } } [Log] private void PausePlayback() { _cts?.Cancel(); CurrentStatus = PlayStatus.Paused; } [Log] private async Task ResumePlayback() { CurrentStatus = PlayStatus.Playing; _cts = new CancellationTokenSource(); await StartPlayback(); } [Log] private void UpdateCurrentImage() { if (ImagePaths == null || CurrentIndex < 0 || CurrentIndex >= ImagePaths.Length) return; string? savePath = strImageModeImagePath.IsNullOrEmpty()? ConfigurationManager.AppSettings["ImageFileBasePath"]:strImageModeImagePath; var bitmap = new BitmapImage(); bitmap.BeginInit(); bitmap.CacheOption = BitmapCacheOption.OnLoad; bitmap.UriSource = new Uri(savePath + @"\" + ImagePaths[CurrentIndex]); bitmap.EndInit(); bitmap.Freeze(); // 确保跨线程安全# CurrentImage = bitmap; OnPropertyChanged(nameof(CurrentImage)); } [Log] private void MovePrevious() { if (CurrentStatus == PlayStatus.Playing) PausePlayback(); CurrentIndex = (CurrentIndex - 1 + ImagePaths.Length) % ImagePaths.Length; UpdateCurrentImage(); } [Log] private void MoveNext() { if (CurrentStatus == PlayStatus.Playing) PausePlayback(); CurrentIndex = (CurrentIndex + 1) % ImagePaths.Length; UpdateCurrentImage(); } #endregion } public enum PlayStatus { Stopped, // 初始/停止状态 Playing, // 播放中 Paused // 暂停中 }