From bb73b0f9c534ed175b0e68af2195570d9b209912 Mon Sep 17 00:00:00 2001 From: Administrator Date: Tue, 16 Dec 2025 10:14:44 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LineTraceabilitySystem.sln | 24 + README.md | 74 ++ src/LineTraceabilitySystem/App.xaml | 18 + src/LineTraceabilitySystem/App.xaml.cs | 75 ++ .../Constants/AppConstants.cs | 255 ++++ .../Constants/DialogResultType.cs | 12 + .../Constants/ProcessStep.cs | 12 + .../Constants/RegionNames.cs | 13 + .../Events/EventAggregator.cs | 84 ++ .../Events/PlcLogEventArgs.cs | 42 + .../Events/SerialDataReceivedEventArgs.cs | 24 + .../Events/SerialErrorEventArgs.cs | 31 + .../Events/SerialStatusChangedEventArgs.cs | 24 + .../Events/TraceabilityCodeScannedEvent.cs | 15 + .../Helpers/BarcodeScannerHelper.cs | 35 + .../Helpers/BoolToColorConverter.cs | 27 + .../Helpers/BoolToStringConverter.cs | 26 + .../Helpers/COMM/PLC/DemoUtils.cs | 63 + .../Helpers/COMM/PLC/Host.cs | 60 + .../Helpers/COMM/PLC/Tag.cs | 114 ++ .../Helpers/COMM/PLC/TagServer.cs | 1064 +++++++++++++++++ .../Helpers/CommonHelper.cs | 195 +++ .../Converters/StatusToColorConverter.cs | 38 + .../Converters/StatusToIconConverter.cs | 38 + .../Converters/StatusToImageConverter.cs | 38 + .../Helpers/SqlSugarHelper.cs | 55 + .../Helpers/StepToStringConverter.cs | 36 + .../Icons/CheckCircle.svg | 1 + .../Icons/ChevronDown.svg | 1 + src/LineTraceabilitySystem/Icons/Cpu.svg | 1 + .../Icons/ErrorCircle.svg | 1 + .../Icons/HelpCircle.svg | 1 + .../Icons/WarningCircle.svg | 1 + .../LineTraceabilitySystem.csproj | 43 + .../Models/OperationLog.cs | 49 + .../Models/trace/Dto/TraceConfigDto.cs | 46 + .../trace/Dto/TraceDictEolParameterDto.cs | 37 + .../trace/Dto/TraceDictZdParameterDto.cs | 37 + .../Models/trace/Dto/TraceLogDto.cs | 26 + .../Models/trace/Dto/TraceProjectDto.cs | 31 + .../Models/trace/Dto/TraceProjectPartDto.cs | 39 + .../Models/trace/Dto/TraceSnQcRecordDto.cs | 118 ++ .../Models/trace/Dto/TraceSnScanDto.cs | 29 + .../Models/trace/Dto/TraceSnSubScanDto.cs | 32 + .../Models/trace/Dto/TraceSnTransitDto.cs | 26 + .../Models/trace/TraceConfig.cs | 78 ++ .../Models/trace/TraceDictEolParameter.cs | 72 ++ .../Models/trace/TraceDictZdParameter.cs | 72 ++ .../Models/trace/TraceLog.cs | 47 + .../Models/trace/TraceProject.cs | 56 + .../Models/trace/TraceProjectPart.cs | 74 ++ .../Models/trace/TraceSnQcRecord.cs | 270 +++++ .../Models/trace/TraceSnScan.cs | 46 + .../Models/trace/TraceSnSubScan.cs | 58 + .../Models/trace/TraceSnTransit.cs | 41 + .../Services/AlertService.cs | 106 ++ .../Services/IAlertService.cs | 70 ++ .../Services/IS7PlcService.cs | 152 +++ .../Services/IScanService.cs | 30 + .../Services/ISerialPortService.cs | 114 ++ .../Services/ITraceMainService.cs | 71 ++ .../Services/S7PlcService.cs | 600 ++++++++++ .../Services/ScanService.cs | 78 ++ .../Services/SerialPortService.cs | 303 +++++ .../Services/TraceMainService.cs | 192 +++ .../ViewModels/HomeViewModel.cs | 265 ++++ .../ViewModels/MainWindowViewModel.cs | 769 ++++++++++++ .../ViewModels/TraceabilityViewModel.cs | 77 ++ .../Views/HomeView.xaml | 91 ++ .../Views/HomeView.xaml.cs | 15 + .../Views/MainWindow.xaml | 198 +++ .../Views/MainWindow.xaml.cs | 67 ++ .../Views/TraceabilityView.xaml | 147 +++ .../Views/TraceabilityView.xaml.cs | 15 + 74 files changed, 7185 insertions(+) create mode 100644 LineTraceabilitySystem.sln create mode 100644 README.md create mode 100644 src/LineTraceabilitySystem/App.xaml create mode 100644 src/LineTraceabilitySystem/App.xaml.cs create mode 100644 src/LineTraceabilitySystem/Constants/AppConstants.cs create mode 100644 src/LineTraceabilitySystem/Constants/DialogResultType.cs create mode 100644 src/LineTraceabilitySystem/Constants/ProcessStep.cs create mode 100644 src/LineTraceabilitySystem/Constants/RegionNames.cs create mode 100644 src/LineTraceabilitySystem/Events/EventAggregator.cs create mode 100644 src/LineTraceabilitySystem/Events/PlcLogEventArgs.cs create mode 100644 src/LineTraceabilitySystem/Events/SerialDataReceivedEventArgs.cs create mode 100644 src/LineTraceabilitySystem/Events/SerialErrorEventArgs.cs create mode 100644 src/LineTraceabilitySystem/Events/SerialStatusChangedEventArgs.cs create mode 100644 src/LineTraceabilitySystem/Events/TraceabilityCodeScannedEvent.cs create mode 100644 src/LineTraceabilitySystem/Helpers/BarcodeScannerHelper.cs create mode 100644 src/LineTraceabilitySystem/Helpers/BoolToColorConverter.cs create mode 100644 src/LineTraceabilitySystem/Helpers/BoolToStringConverter.cs create mode 100644 src/LineTraceabilitySystem/Helpers/COMM/PLC/DemoUtils.cs create mode 100644 src/LineTraceabilitySystem/Helpers/COMM/PLC/Host.cs create mode 100644 src/LineTraceabilitySystem/Helpers/COMM/PLC/Tag.cs create mode 100644 src/LineTraceabilitySystem/Helpers/COMM/PLC/TagServer.cs create mode 100644 src/LineTraceabilitySystem/Helpers/CommonHelper.cs create mode 100644 src/LineTraceabilitySystem/Helpers/Converters/StatusToColorConverter.cs create mode 100644 src/LineTraceabilitySystem/Helpers/Converters/StatusToIconConverter.cs create mode 100644 src/LineTraceabilitySystem/Helpers/Converters/StatusToImageConverter.cs create mode 100644 src/LineTraceabilitySystem/Helpers/SqlSugarHelper.cs create mode 100644 src/LineTraceabilitySystem/Helpers/StepToStringConverter.cs create mode 100644 src/LineTraceabilitySystem/Icons/CheckCircle.svg create mode 100644 src/LineTraceabilitySystem/Icons/ChevronDown.svg create mode 100644 src/LineTraceabilitySystem/Icons/Cpu.svg create mode 100644 src/LineTraceabilitySystem/Icons/ErrorCircle.svg create mode 100644 src/LineTraceabilitySystem/Icons/HelpCircle.svg create mode 100644 src/LineTraceabilitySystem/Icons/WarningCircle.svg create mode 100644 src/LineTraceabilitySystem/LineTraceabilitySystem.csproj create mode 100644 src/LineTraceabilitySystem/Models/OperationLog.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/Dto/TraceConfigDto.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/Dto/TraceDictEolParameterDto.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/Dto/TraceDictZdParameterDto.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/Dto/TraceLogDto.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/Dto/TraceProjectDto.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/Dto/TraceProjectPartDto.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/Dto/TraceSnQcRecordDto.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/Dto/TraceSnScanDto.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/Dto/TraceSnSubScanDto.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/Dto/TraceSnTransitDto.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/TraceConfig.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/TraceDictEolParameter.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/TraceDictZdParameter.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/TraceLog.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/TraceProject.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/TraceProjectPart.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/TraceSnQcRecord.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/TraceSnScan.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/TraceSnSubScan.cs create mode 100644 src/LineTraceabilitySystem/Models/trace/TraceSnTransit.cs create mode 100644 src/LineTraceabilitySystem/Services/AlertService.cs create mode 100644 src/LineTraceabilitySystem/Services/IAlertService.cs create mode 100644 src/LineTraceabilitySystem/Services/IS7PlcService.cs create mode 100644 src/LineTraceabilitySystem/Services/IScanService.cs create mode 100644 src/LineTraceabilitySystem/Services/ISerialPortService.cs create mode 100644 src/LineTraceabilitySystem/Services/ITraceMainService.cs create mode 100644 src/LineTraceabilitySystem/Services/S7PlcService.cs create mode 100644 src/LineTraceabilitySystem/Services/ScanService.cs create mode 100644 src/LineTraceabilitySystem/Services/SerialPortService.cs create mode 100644 src/LineTraceabilitySystem/Services/TraceMainService.cs create mode 100644 src/LineTraceabilitySystem/ViewModels/HomeViewModel.cs create mode 100644 src/LineTraceabilitySystem/ViewModels/MainWindowViewModel.cs create mode 100644 src/LineTraceabilitySystem/ViewModels/TraceabilityViewModel.cs create mode 100644 src/LineTraceabilitySystem/Views/HomeView.xaml create mode 100644 src/LineTraceabilitySystem/Views/HomeView.xaml.cs create mode 100644 src/LineTraceabilitySystem/Views/MainWindow.xaml create mode 100644 src/LineTraceabilitySystem/Views/MainWindow.xaml.cs create mode 100644 src/LineTraceabilitySystem/Views/TraceabilityView.xaml create mode 100644 src/LineTraceabilitySystem/Views/TraceabilityView.xaml.cs diff --git a/LineTraceabilitySystem.sln b/LineTraceabilitySystem.sln new file mode 100644 index 0000000..88e2305 --- /dev/null +++ b/LineTraceabilitySystem.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.32002.261 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LineTraceabilitySystem", "src\LineTraceabilitySystem\LineTraceabilitySystem.csproj", "{1A2F9F4A-7ABD-4DC9-91F7-38A0B99265D8}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1A2F9F4A-7ABD-4DC9-91F7-38A0B99265D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1A2F9F4A-7ABD-4DC9-91F7-38A0B99265D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1A2F9F4A-7ABD-4DC9-91F7-38A0B99265D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1A2F9F4A-7ABD-4DC9-91F7-38A0B99265D8}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4E0B0F23-3F8B-4F96-B6D8-6F77893D842F} + EndGlobalSection +EndGlobal \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..dbc4abf --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# 摄像头追溯程序 - WPF应用 + +## 项目概述 +该应用程序用于客户扫描追溯码和摄像头码,实现数据验证和绑定功能,并提供未完成操作的提醒机制。 + +## 技术栈 +- .NET 8.0 (或 .NET 7.0) +- WPF (Windows Presentation Foundation) +- C# 11 +- Entity Framework Core (可选,用于数据访问) +- MVVM模式 + +## 主要功能 +1. **追溯码验证**:扫描追溯码后,连接服务器验证数据是否存在 +2. **摄像头码绑定**:扫描摄像头码后,将其与追溯码绑定 +3. **提醒机制**:对于未完成的扫码操作,提供视觉和声音提醒 +4. **数据存储**:本地缓存最近的扫码记录 + +## 项目结构 +``` +U20-摄像头追溯程序/ +├── src/ +│ ├── CameraTraceabilityApp/ # 主应用程序 +│ │ ├── App.xaml # 应用入口 +│ │ ├── MainWindow.xaml # 主窗口 +│ │ ├── ViewModels/ # 视图模型 +│ │ ├── Views/ # 视图 +│ │ ├── Models/ # 数据模型 +│ │ ├── Services/ # 服务层 +│ │ ├── Helpers/ # 辅助类 +│ │ ├── Constants/ # 常量定义 +│ │ └── Resources/ # 资源文件 +│ └── CameraTraceabilityApp.Tests/ # 单元测试 +├── docs/ # 文档 +├── scripts/ # 构建脚本 +└── README.md # 项目说明 +``` + +## 模块划分 +1. **UI模块**:负责用户界面展示和交互 +2. **业务逻辑模块**:处理扫码验证和绑定逻辑 +3. **数据访问模块**:与服务器和本地数据交互 +4. **提醒模块**:处理未完成操作的提醒功能 +5. **配置模块**:管理应用程序配置 + +## 推荐框架 +1. **Prism**:优秀的MVVM框架,提供依赖注入、导航等功能 +2. **Caliburn.Micro**:轻量级MVVM框架,简洁易用 +3. **MahApps.Metro**:提供现代化UI组件和样式 +4. **MaterialDesignThemes**:Material Design风格的WPF控件库 +5. **Newtonsoft.Json**:JSON序列化/反序列化库 +6. **ZXing.Net**:条形码和二维码扫描库 + +## 开发指南 +1. 首先安装Visual Studio 2022及.NET 8 SDK +2. 创建WPF项目 +3. 添加所需的NuGet包 +4. 按照MVVM模式实现各模块 +5. 实现扫码功能和服务器验证逻辑 +6. 添加提醒机制 +7. 进行测试和调试 + +## 部署说明 +1. 编译Release版本 +2. 使用ClickOnce或传统安装包进行部署 +3. 配置服务器连接信息 +4. 确保目标机器安装了.NET 8运行时 + +## 注意事项 +1. 确保服务器连接安全性 +2. 处理网络异常情况 +3. 优化扫码识别速度 +4. 确保提醒机制不会过度干扰用户 +5. 定期备份本地缓存数据 \ No newline at end of file diff --git a/src/LineTraceabilitySystem/App.xaml b/src/LineTraceabilitySystem/App.xaml new file mode 100644 index 0000000..170e625 --- /dev/null +++ b/src/LineTraceabilitySystem/App.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + pack://application:,,,/;component/Fonts/#Segoe Fluent Icons + + + \ No newline at end of file diff --git a/src/LineTraceabilitySystem/App.xaml.cs b/src/LineTraceabilitySystem/App.xaml.cs new file mode 100644 index 0000000..6ee1a57 --- /dev/null +++ b/src/LineTraceabilitySystem/App.xaml.cs @@ -0,0 +1,75 @@ +using LineTraceabilitySystem.Services; +using LineTraceabilitySystem.ViewModels; +using LineTraceabilitySystem.Views; +using Prism.Unity; +using Prism.Ioc; +using System.Diagnostics; +using System.Windows; + +namespace LineTraceabilitySystem +{ + /// + /// App.xaml 的交互逻辑 + /// + public partial class App : PrismApplication + { + protected override Window CreateShell() + { + return Container.Resolve(); + } + + protected override void RegisterTypes(IContainerRegistry containerRegistry) + { + Debug.WriteLine("注册视图"); + // 注册服务 + containerRegistry.Register(); + containerRegistry.Register(); + containerRegistry.Register(); + containerRegistry.Register(); + containerRegistry.Register(); + + // 注册视图模型接口和实现 + containerRegistry.Register(); + containerRegistry.Register(); + + // 注册视图 + containerRegistry.RegisterForNavigation(); + containerRegistry.RegisterForNavigation(); + } + + protected override void OnExit(ExitEventArgs e) + { + // 应用程序退出时的清理工作 + try + { + // 关闭并释放串口服务 + //var serialPortService = Container.Resolve(); + //if (serialPortService is IDisposable disposableSerialPort) + //{ + // disposableSerialPort.Dispose(); + //} + + // 关闭并释放PLC服务 + //var plcService = Container.Resolve(); + //if (plcService is IDisposable disposablePlc) + //{ + // //plcService.WriteVaribleAsync("VD880", 0); + // //plcService.WriteVaribleAsync("VD884", 0); + // disposablePlc.Dispose(); + //} + + // 关闭并释放主窗口视图模型 + //var mainWindowViewModel = Container.Resolve(); + //if (mainWindowViewModel is IDisposable disposableMainWindowVM) + //{ + // disposableMainWindowVM.Dispose(); + //} + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"应用程序退出清理异常: {ex.Message}"); + OnExit(e); + } + } + } +} diff --git a/src/LineTraceabilitySystem/Constants/AppConstants.cs b/src/LineTraceabilitySystem/Constants/AppConstants.cs new file mode 100644 index 0000000..f5f2d78 --- /dev/null +++ b/src/LineTraceabilitySystem/Constants/AppConstants.cs @@ -0,0 +1,255 @@ +using System.IO.Ports; + +namespace LineTraceabilitySystem.Constants +{ + /// + /// 应用程序常量定义 + /// + public static class AppConstants + { + #region 应用程序信息 + + /// + /// 应用程序名称 + /// + public const string AppName = "产线追溯系统"; + + /// + /// 应用程序版本 + /// + public const string AppVersion = "1.0.0"; + + /// + /// 公司名称 + /// + public const string CompanyName = "BBB上海干巷"; + + #endregion + + #region 服务器配置 + + /// + /// 默认服务器URL配置键 + /// + public const string ServerUrlConfigKey = "ServerUrl"; + + /// + /// 默认服务器URL + /// + public const string DefaultServerUrl = "http://localhost:8080/api"; + + /// + /// 服务器基础URL + /// + public static readonly string BaseUrl = "http://192.168.200.250:7000"; + + /// + /// API基础URL + /// + public static readonly string BaseApiUrl = BaseUrl; + + /// + /// 数据库连接字符串 + /// + //public const string ConnectionString = "server=139.224.232.211;port=3308;database=gxassembly;user=root;password=doantech123;"; + public const string ConnectionString = "server=192.168.200.250;port=3306;database=gxassembly;user=root;password=123456;"; + + /// + /// 服务器连接超时时间(秒) + /// + public const int ServerTimeoutSeconds = 30; + + #endregion + + #region 缓存配置 + + /// + /// 缓存过期时间(分钟) + /// + public const int CacheExpirationMinutes = 60; + + /// + /// 最大缓存项数量 + /// + public const int MaxCacheItems = 1000; + + #endregion + + #region 提醒配置 + + /// + /// 未完成操作提醒间隔(秒) + /// + public const int UnfinishedOperationAlertIntervalSeconds = 30; + + /// + /// 最大提醒次数 + /// + public const int MaxAlertTimes = 5; + + #endregion + + #region 日志配置 + + /// + /// 最大保留日志数量 + /// + public const int MaxLogCount = 1000; + + /// + /// 显示的最近日志数量 + + #endregion + + #region PLC配置 + + /// + /// PLC IP地址 + /// + public const string PlcIpAddress = "192.168.200.100"; // 请根据实际PLC IP修改 + + /// + /// PLC机架号 + /// + public const int PlcRack = 0; + + /// + /// PLC槽号 + /// + public const int PlcSlot = 1; + + /// + /// PLC CPU类型 + /// + public const string PlcCpuType = "S71200"; // 请根据实际PLC型号修改 + + /// + /// PLC数据块号 + /// + public const int PlcDbNumber = 1; // 请根据实际数据块修改 + + /// + /// PLC写入地址 - VD880 + /// + public const int PlcWriteAddressVd880 = 880; + + /// + /// PLC写入地址 - VD884 + /// + public const int PlcWriteAddressVd884 = 884; + + /// + /// PLC读取地址 - VD900 (当前产品螺丝个数) + /// + public const int PlcReadAddressVd900 = 900; + + /// + /// PLC读取地址 - VD904 (当前已完成螺丝个数) + /// + public const int PlcReadAddressVd904 = 904; + + #endregion + + #region 显示配置 + + /// + /// 显示的最近日志数量 + /// + public const int DisplayRecentLogCount = 50; + + #endregion + + #region 配置键名 + + /// + /// 最后未完成操作配置键 + /// + public const string LastUnfinishedOperationKey = "LastUnfinishedOperation"; + + /// + /// 自动验证配置键 + /// + public const string AutoVerifyEnabledKey = "AutoVerifyEnabled"; + + /// + /// 自动绑定配置键 + /// + public const string AutoBindEnabledKey = "AutoBindEnabled"; + + /// + /// 串口名称配置键 + /// + public const string SerialPortNameKey = "SerialPortName"; + + /// + /// 串口波特率配置键 + /// + public const string SerialPortBaudRateKey = "SerialPortBaudRate"; + + #endregion + + #region 扫码配置 + + /// + /// 追溯码前缀 + /// + public const string TraceCodePrefix = "A"; + + #endregion + + #region 串口配置 + + /// + /// 串口服务 + /// + public static class SerialPort + { + /// + /// 默认串口名称 + /// + public const string DefaultPortName = "COM1"; + + /// + /// 默认波特率 + /// + public const int DefaultBaudRate = 9600; + + /// + /// 默认数据位 + /// + public const int DefaultDataBits = 8; + + /// + /// 默认停止位 + /// + public const StopBits DefaultStopBits = StopBits.One; + + /// + /// 默认校验位 + /// + public const Parity DefaultParity = Parity.None; + + /// + /// 默认握手协议 + /// + public const bool DefaultHandshake = false; + + /// + /// 默认读取超时 + /// + public const int DefaultReadTimeout = 500; + + /// + /// 默认写入超时 + /// + public const int DefaultWriteTimeout = 500; + + /// + /// 换行符(扫码枪数据结束标记) + /// + public const string NewLine = "\r"; + } + + #endregion + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Constants/DialogResultType.cs b/src/LineTraceabilitySystem/Constants/DialogResultType.cs new file mode 100644 index 0000000..4a904fa --- /dev/null +++ b/src/LineTraceabilitySystem/Constants/DialogResultType.cs @@ -0,0 +1,12 @@ +namespace LineTraceabilitySystem.Constants +{ + /// + /// 自定义对话框结果枚举,用于替代System.Windows.Forms.DialogResult + /// + public enum DialogResultType + { + None, + Yes, + No + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Constants/ProcessStep.cs b/src/LineTraceabilitySystem/Constants/ProcessStep.cs new file mode 100644 index 0000000..fb42206 --- /dev/null +++ b/src/LineTraceabilitySystem/Constants/ProcessStep.cs @@ -0,0 +1,12 @@ +namespace LineTraceabilitySystem.Constants +{ + /// + /// 流程步骤枚举 + /// + public enum ProcessStep + { + ScanTraceabilityCode = 0, + ScanCameraCode = 10, + End = 1000 + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Constants/RegionNames.cs b/src/LineTraceabilitySystem/Constants/RegionNames.cs new file mode 100644 index 0000000..26fe089 --- /dev/null +++ b/src/LineTraceabilitySystem/Constants/RegionNames.cs @@ -0,0 +1,13 @@ +namespace LineTraceabilitySystem.Constants +{ + /// + /// 区域名称常量类 + /// + public static class RegionNames + { + /// + /// 主区域 + /// + public const string MainRegion = "MainRegion"; + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Events/EventAggregator.cs b/src/LineTraceabilitySystem/Events/EventAggregator.cs new file mode 100644 index 0000000..939e115 --- /dev/null +++ b/src/LineTraceabilitySystem/Events/EventAggregator.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace LineTraceabilitySystem.Events +{ + /// + /// 事件聚合器,用于管理事件的发布和订阅 + /// + public class EventAggregator + { + private static readonly Lazy _instance = new Lazy(() => new EventAggregator()); + private readonly Dictionary> _subscribers = new Dictionary>(); + + /// + /// 单例实例 + /// + public static EventAggregator Instance => _instance.Value; + + private EventAggregator() { } + + /// + /// 订阅事件 + /// + /// 事件类型 + /// 事件处理方法 + public void Subscribe(Action handler) + { + var eventType = typeof(TEvent); + if (!_subscribers.TryGetValue(eventType, out var handlers)) + { + handlers = new List(); + _subscribers[eventType] = handlers; + } + handlers.Add(handler); + } + + /// + /// 取消订阅事件 + /// + /// 事件类型 + /// 事件处理方法 + public void Unsubscribe(Action handler) + { + var eventType = typeof(TEvent); + if (_subscribers.TryGetValue(eventType, out var handlers)) + { + handlers.Remove(handler); + if (handlers.Count == 0) + { + _subscribers.Remove(eventType); + } + } + } + + /// + /// 发布事件 + /// + /// 事件类型 + /// 事件实例 + public void Publish(TEvent @event) + { + var eventType = typeof(TEvent); + if (_subscribers.TryGetValue(eventType, out var handlers)) + { + foreach (var handler in handlers) + { + ((Action)handler).Invoke(@event); + } + } + } + + /// + /// 异步发布事件 + /// + /// 事件类型 + /// 事件实例 + /// 任务 + public async Task PublishAsync(TEvent @event) + { + await Task.Run(() => Publish(@event)); + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Events/PlcLogEventArgs.cs b/src/LineTraceabilitySystem/Events/PlcLogEventArgs.cs new file mode 100644 index 0000000..bad693c --- /dev/null +++ b/src/LineTraceabilitySystem/Events/PlcLogEventArgs.cs @@ -0,0 +1,42 @@ +using System; + +namespace LineTraceabilitySystem.Events +{ + /// + /// PLC日志事件参数 + /// + public class PlcLogEventArgs : EventArgs + { + /// + /// 日志消息 + /// + public string Message { get; set; } + + /// + /// 日志级别 + /// + public LogLevel Level { get; set; } + + /// + /// 构造函数 + /// + /// 日志消息 + /// 日志级别 + public PlcLogEventArgs(string message, LogLevel level) + { + Message = message; + Level = level; + } + } + + /// + /// 日志级别枚举 + /// + public enum LogLevel + { + Info, + Warning, + Error, + Exception + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Events/SerialDataReceivedEventArgs.cs b/src/LineTraceabilitySystem/Events/SerialDataReceivedEventArgs.cs new file mode 100644 index 0000000..3baf766 --- /dev/null +++ b/src/LineTraceabilitySystem/Events/SerialDataReceivedEventArgs.cs @@ -0,0 +1,24 @@ +using System; + +namespace LineTraceabilitySystem.Events +{ + /// + /// 串口数据接收事件参数 + /// + public class SerialDataReceivedEventArgs : EventArgs + { + /// + /// 接收到的数据 + /// + public string Data { get; } + + /// + /// 构造函数 + /// + /// 接收到的数据 + public SerialDataReceivedEventArgs(string data) + { + Data = data; + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Events/SerialErrorEventArgs.cs b/src/LineTraceabilitySystem/Events/SerialErrorEventArgs.cs new file mode 100644 index 0000000..6952f7a --- /dev/null +++ b/src/LineTraceabilitySystem/Events/SerialErrorEventArgs.cs @@ -0,0 +1,31 @@ +using System; + +namespace LineTraceabilitySystem.Events +{ + /// + /// 串口错误事件参数 + /// + public class SerialErrorEventArgs : EventArgs + { + /// + /// 错误消息 + /// + public string ErrorMessage { get; } + + /// + /// 异常对象 + /// + public Exception? Exception { get; } + + /// + /// 构造函数 + /// + /// 错误消息 + /// 异常对象 + public SerialErrorEventArgs(string errorMessage, Exception? exception = null) + { + ErrorMessage = errorMessage; + Exception = exception; + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Events/SerialStatusChangedEventArgs.cs b/src/LineTraceabilitySystem/Events/SerialStatusChangedEventArgs.cs new file mode 100644 index 0000000..cff9d19 --- /dev/null +++ b/src/LineTraceabilitySystem/Events/SerialStatusChangedEventArgs.cs @@ -0,0 +1,24 @@ +using System; + +namespace LineTraceabilitySystem.Events +{ + /// + /// 串口状态变化事件参数 + /// + public class SerialStatusChangedEventArgs : EventArgs + { + /// + /// 获取串口是否打开 + /// + public bool IsOpen { get; } + + /// + /// 初始化串口状态变化事件参数 + /// + /// 串口是否打开 + public SerialStatusChangedEventArgs(bool isOpen) + { + IsOpen = isOpen; + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Events/TraceabilityCodeScannedEvent.cs b/src/LineTraceabilitySystem/Events/TraceabilityCodeScannedEvent.cs new file mode 100644 index 0000000..9b29049 --- /dev/null +++ b/src/LineTraceabilitySystem/Events/TraceabilityCodeScannedEvent.cs @@ -0,0 +1,15 @@ +using Prism.Events; + +namespace LineTraceabilitySystem.Events +{ + /// + /// 追溯码扫描完成事件 + /// + public class TraceabilityCodeScannedEvent : EventBase + { + /// + /// 扫描到的追溯码 + /// + public string Code { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Helpers/BarcodeScannerHelper.cs b/src/LineTraceabilitySystem/Helpers/BarcodeScannerHelper.cs new file mode 100644 index 0000000..0bc62cb --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/BarcodeScannerHelper.cs @@ -0,0 +1,35 @@ +using System.Diagnostics; + +namespace LineTraceabilitySystem.Helpers +{ + /// + /// 扫码枪数据处理助手类 + /// + public static class BarcodeScannerHelper + { + /// + /// 处理扫码枪数据 + /// + /// 原始数据 + /// 处理后的扫码数据 + public static string ProcessScannerData(string rawData) + { + try + { + // 移除末尾的回车符 + if (rawData.EndsWith("\r")) + { + rawData = rawData.Substring(0, rawData.Length - 1); + } + + // 移除任何前导或尾随空白 + return rawData.Trim(); + } + catch (Exception ex) + { + Debug.WriteLine($"处理扫码数据失败: {ex.Message}"); + return string.Empty; + } + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Helpers/BoolToColorConverter.cs b/src/LineTraceabilitySystem/Helpers/BoolToColorConverter.cs new file mode 100644 index 0000000..2d91606 --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/BoolToColorConverter.cs @@ -0,0 +1,27 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; + +namespace LineTraceabilitySystem.Helpers +{ + /// + /// 将布尔值转换为颜色 + /// + public class BoolToColorConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is bool isOk && isOk) + { + return new SolidColorBrush(Colors.Green); + } + return new SolidColorBrush(Colors.Red); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Helpers/BoolToStringConverter.cs b/src/LineTraceabilitySystem/Helpers/BoolToStringConverter.cs new file mode 100644 index 0000000..817dd79 --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/BoolToStringConverter.cs @@ -0,0 +1,26 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace LineTraceabilitySystem.Helpers +{ + /// + /// 将0/1转换为"否"/"是" + /// + public class BoolToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is int isBack) + { + return isBack == 1 ? "是" : "否"; + } + return "未知"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Helpers/COMM/PLC/DemoUtils.cs b/src/LineTraceabilitySystem/Helpers/COMM/PLC/DemoUtils.cs new file mode 100644 index 0000000..3962d43 --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/COMM/PLC/DemoUtils.cs @@ -0,0 +1,63 @@ +using HslCommunication; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Controls; + +namespace LineTraceabilitySystem.Helpers.COMM.PLC +{ + public class DemoUtils + { + /// + /// 项目中最后调用读取的都在这里,统一的读取结果的数据解析,显示 + /// + /// 类型对象 + /// 读取的结果值 + /// 地址信息 + /// 输入的控件 + public static void ReadResultRender(OperateResult result, string address, out string results) + { + if (result.IsSuccess) + { + if (result.Content is Array) + { + results = HslCommunication.BasicFramework.SoftBasic.ArrayFormat(result.Content); + } + else + { + results = result.Content.ToString(); + } + } + else + { + results = ""; + } + } + + /// + /// 统一的数据写入的结果显示 + /// + /// 写入的结果信息 + /// 地址信息 + public static bool WriteResultRender(OperateResult result, string address) + { + if (result.IsSuccess) + { + return true; + } + else + { + return false; + } + } + + public static readonly string IpAddressInputWrong = "IpAddress input wrong"; + public static readonly string PortInputWrong = "Port input wrong"; + public static readonly string SlotInputWrong = "Slot input wrong"; + public static readonly string BaudRateInputWrong = "Baud rate input wrong"; + public static readonly string DataBitsInputWrong = "Data bit input wrong"; + public static readonly string StopBitInputWrong = "Stop bit input wrong"; + } +} diff --git a/src/LineTraceabilitySystem/Helpers/COMM/PLC/Host.cs b/src/LineTraceabilitySystem/Helpers/COMM/PLC/Host.cs new file mode 100644 index 0000000..2904d03 --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/COMM/PLC/Host.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LineTraceabilitySystem.Helpers.COMM.PLC +{ + public class Host + { + Dictionary _lstTagServer; + + public Host() + { + this._lstTagServer = new Dictionary(); + } + + /// + /// 创建一个PLC服务 + /// + /// + /// + public TagServer CreateTagServer(string hostIp) + { + if (GetTagServerByIp(hostIp) != null) return null; + + // 用带IP地址创建对象 + TagServer tagServer = new TagServer(hostIp); + + AddTagServer(hostIp, tagServer); + return tagServer; + } + + private void AddTagServer(string hostIp, TagServer tagServer) + { + this._lstTagServer.Add(hostIp, tagServer); + } + + /// + /// 根据IP返回一个PLC服务 + /// + /// + /// + public TagServer GetTagServerByIp(string hostIp) + { + return this._lstTagServer.ContainsKey(hostIp) ? this._lstTagServer[hostIp] : null; + } + + /// + /// 关闭所有 + /// + public void CloseConnect() + { + foreach (KeyValuePair item in this._lstTagServer) + { + item.Value.DisconnectPLC(); + } + } + } +} diff --git a/src/LineTraceabilitySystem/Helpers/COMM/PLC/Tag.cs b/src/LineTraceabilitySystem/Helpers/COMM/PLC/Tag.cs new file mode 100644 index 0000000..a60ab5d --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/COMM/PLC/Tag.cs @@ -0,0 +1,114 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LineTraceabilitySystem.Helpers.COMM.PLC +{ + public enum TagType + { + tagTypeBool = 0, + tagTypeByte, + tagTypeShortInt, + tagTypeUShortInt, + tagTypeInt, + tagTypeUInt, + tagTypeLong, + tagTypeULong, + tagTypeFloat, + tagTypeDouble + } + + public enum TagAccessType + { + Monitor = 0, + CycleRead, + ReadWrite + } + + public class Tag + { + /// + /// 所在主机名称 + /// + public string HostName { get; set; } + + /// + /// 所在主机IP + /// + public string HostIp { get; set; } + + /// + /// 变量名称 + /// + public string TagName { get; set; } + + /// + /// 变量地址 + /// + public string TagAddress { get; set; } + + /// + /// 变量值,以字符串形式表达 + /// + public string TagNewValue { get; set; } + + /// + /// 变量值,读取的上一次值 + /// + public string TagLastValue { get; set; } + + /// + /// 变量访问类型 + /// + public TagAccessType AccessType { get; set; } + + /// + /// 数据类型 + /// + public TagType tagType { get; set; } + + /// + /// 连续读取变量的数目 + /// + public int tagLen { get; set; } + + public bool CompareNewValueChange() + { + bool compareResult = false; + if (!string.IsNullOrEmpty(this.TagNewValue)) + { + if (!string.IsNullOrEmpty(this.TagLastValue)) + { + compareResult = !this.TagNewValue.Equals(this.TagLastValue); + } + else + compareResult = true; + } + return compareResult; + } + + /// + /// 字节数组类型,转换为字符串输出 + /// + /// + public string ConvertByteToString() + { + if (string.IsNullOrEmpty(this.TagNewValue)) return string.Empty; + + string[] buffer = this.TagNewValue.Trim(new char[2] { '[', ']' }).Split(new char[] { ',' }); + int length = int.Parse(buffer[1]); + + if (length <= 0) return string.Empty; + + byte[] buff = new byte[length]; + for (int i = 0; i < length; i++) + { + buff[i] = byte.Parse(buffer[i + 2]); + } + + return Encoding.ASCII.GetString(buff); + } + } +} diff --git a/src/LineTraceabilitySystem/Helpers/COMM/PLC/TagServer.cs b/src/LineTraceabilitySystem/Helpers/COMM/PLC/TagServer.cs new file mode 100644 index 0000000..4d589c4 --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/COMM/PLC/TagServer.cs @@ -0,0 +1,1064 @@ +using Azure; +using HslCommunication; +using HslCommunication.Core; +using HslCommunication.Core.Net; +using HslCommunication.LogNet; +using HslCommunication.Profinet.Omron; +using HslCommunication.Profinet.Siemens; +using Microsoft.Xaml.Behaviors.Layout; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Drawing.Drawing2D; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace LineTraceabilitySystem.Helpers.COMM.PLC +{ + public enum PLCType + { + Smart200 = 0, + S71200, + S71500, + OmronFins + } + + public delegate void PLCStateChange(string sender, string msg); + public delegate void PLCValueChange(string hostIp, string tagName, Tag tag); + public class TagServer + { + private object olock = new object(); + private AutoResetEvent autoResetEvent = new AutoResetEvent(false); + private static ConcurrentQueue> InsertPLCActions = new ConcurrentQueue>(); + + /// + /// 当前PLC中的地址对象 + /// + Dictionary _dicTags; + + string HostIp = string.Empty; + + NetworkDeviceBase _plcConnectNet; + IReadWriteNet _plcConnectObj; + + /// + /// 当前PLC连接对象 + /// + public IReadWriteNet PlcConnectObj { get => _plcConnectObj; } + + /// + /// 当前循环线程 + /// + public bool exitRun = false; + Thread IThread; + + /// + /// 定义PLC状态变化,和值变化 + /// + public PLCStateChange PLCStateChangeEvent; + public PLCValueChange PLCValueChangeEvent; + + public TagServer() + { + this._dicTags = new Dictionary(); + } + + public TagServer(string hostIp) : this() + { + this.HostIp = hostIp; + } + + /// + /// 连接西门子S7-1200 + /// + /// + /// + /// + private OperateResult SiemensConnectS71200(string IPAddress, int Port) + { + SiemensS7Net siemensTcpNet = new SiemensS7Net(SiemensPLCS.S1200); + siemensTcpNet.ConnectTimeOut = 3000; + siemensTcpNet.IpAddress = IPAddress; + siemensTcpNet.Port = Port; + this._plcConnectNet = siemensTcpNet; + this._plcConnectObj = siemensTcpNet; + + OperateResult connect = siemensTcpNet.ConnectServer(); + + return connect; + } + /// + /// 连接西门子S7-200Smart + /// + /// + /// + private OperateResult SiemensConnectSmart200(string IPAddress) + { + SiemensS7Net siemensTcpNet = new SiemensS7Net(SiemensPLCS.S200Smart); + siemensTcpNet.ConnectTimeOut = 3000; + siemensTcpNet.IpAddress = IPAddress; + //siemensTcpNet.Port = Port; + this._plcConnectNet = siemensTcpNet; + this._plcConnectObj = siemensTcpNet; + + OperateResult connect = siemensTcpNet.ConnectServer(); + + return connect; + } + + // 连接OMRON PLC + private OperateResult OmronConnectFins(string IPAddress, int Port) + { + OmronFinsNet omronFinsNet = new OmronFinsNet(); + omronFinsNet.IpAddress = IPAddress; + omronFinsNet.Port = Port; + OperateResult connect = omronFinsNet.ConnectServer(); + this._plcConnectObj = omronFinsNet; + this._plcConnectNet = omronFinsNet; + return connect; + } + + public bool ConnectPLC(PLCType plcType) + { + return ConnectPLC(this.HostIp, plcType); + } + + // 外部连接 + public bool ConnectPLC(string hostIp, PLCType plcType) + { + bool ret = false; + this.HostIp = hostIp; + + UpdateStateMsg(string.Format("开始连接PLC/{0}", this.HostIp)); + + OperateResult connect = new OperateResult(); + switch (plcType) + { + case PLCType.S71200: + connect = SiemensConnectS71200(hostIp, 102); + break; + case PLCType.S71500: + break; + case PLCType.Smart200: + connect = SiemensConnectSmart200(hostIp); + break; + case PLCType.OmronFins: + connect = OmronConnectFins(hostIp, 9600); + break; + default: + break; + } + + if (connect.IsSuccess) + { + UpdateStateMsg(string.Format("成功连接PLC/{0}", this.HostIp)); + + // 运行读取线程 + IThread = new Thread(Run); + IThread.IsBackground = true; + IThread.Start(); + + ret = true; + } + else + { + UpdateStateMsg(string.Format("连接PLC失败/{0}", this.HostIp)); + exitRun = true; + } + return ret; + } + + public void DisconnectPLC() + { + exitRun = true; + } + + /// + /// 判断PLC是否连上 + /// + /// + public bool IsConnected() + { + return IThread != null && exitRun == false; + } + + /// + /// 循环监视变量列表 + /// + public void Run() + { + //global.BPLCConnected = true; + try + { + while (!exitRun) + { + foreach (KeyValuePair item in this._dicTags) + { + Tag temp = item.Value; + if (temp.AccessType == TagAccessType.Monitor || temp.AccessType == TagAccessType.CycleRead) + { + // 设置新值 + temp.TagNewValue = ReadDataSingle(this.PlcConnectObj, temp.TagAddress, temp.tagLen, temp.tagType); + //需要监控的值要监测数据变化,变化后将新值更新到旧值 + if (CheckTagIsMonitor(temp) && temp.CompareNewValueChange()) + { + UpdateValueChange(temp); + temp.TagLastValue = temp.TagNewValue; + } + } + while (InsertPLCActions.TryDequeue(out Action action)) + { + // 现在需要消费一个写入操作 + action.Invoke(this.PlcConnectObj); + + } + } + Thread.Sleep(20); + } + } + catch (Exception ex) + { + + UpdateStateMsg("获取plc交互值出错" + ex.ToString()); + } + + //this._plcConnectNet.Dispose(); + //global.BPLCConnected = false; + } + + /// + /// 委托读取数据 + /// + /// PLC连接对象 + /// 采集数据长度 + /// 数据类型 + /// + public string ReadDataSingle(IReadWriteNet readWrite, string address, int len, TagType tagType) + { + switch (tagType) + { + case TagType.tagTypeBool: read_bool(readWrite, address, len); break; + case TagType.tagTypeByte: read_byte(readWrite, address, len); break; + case TagType.tagTypeShortInt: read_short(readWrite, address, len); break; + case TagType.tagTypeUShortInt: read_ushort(readWrite, address, len); break; + case TagType.tagTypeInt: read_int(readWrite, address, len); break; + case TagType.tagTypeUInt: read_uint(readWrite, address, len); break; + case TagType.tagTypeLong: read_long(readWrite, address, len); break; + case TagType.tagTypeULong: read_ulong(readWrite, address, len); break; + case TagType.tagTypeFloat: read_float(readWrite, address, len); break; + case TagType.tagTypeDouble: read_double(readWrite, address, len); break; + default: read_string(readWrite, address, len); break; + } + return Info; + } + public void ReadTag(Tag tag) + { + ReadDataSingle(this.PlcConnectObj, tag.TagAddress, tag.tagLen, tag.tagType); + } + public void addRead(Tag tag) + { + // 这里的接收事件是在其他的后台线程触发的,可以直接写入操作,适用于偶尔写入,不定时,低频的写入 + // 如果你的是winform窗体,这个事件可以想象为一个按钮点击的事件 + Action action = new Action(plc => + { + tag.TagNewValue = ReadDataSingle(this.PlcConnectObj, tag.TagAddress, tag.tagLen, tag.tagType); + }); + + InsertPLCActions.Enqueue(action); // 生产者,放入一个写入的操作,然后交给唯一的线程去执行写入 + } + public void InsertAnRWAction(Tag tag, bool bWaitExecuteCompleted = false) + { + if (tag == null) + { + return; + } + if (bWaitExecuteCompleted) + { + lock (olock) + { + // 这里的接收事件是在其他的后台线程触发的,可以直接写入操作,适用于偶尔写入,不定时,低频的写入 + Action action = new Action(plc => + { + tag.TagNewValue = ReadDataSingle(this.PlcConnectObj, tag.TagAddress, tag.tagLen, tag.tagType); + autoResetEvent.Set();//此处 + }); + + InsertPLCActions.Enqueue(action); // 生产者,放入一个写入的操作,然后交给唯一的线程去执行写入 + //需要在这里阻塞等待该动作被执行完毕(被消费) + autoResetEvent.WaitOne(); + } + } + else + { + + Action action = new Action(plc => + { + tag.TagNewValue = ReadDataSingle(this.PlcConnectObj, tag.TagAddress, tag.tagLen, tag.tagType); + }); + InsertPLCActions.Enqueue(action); + } + } + + // 在循环中判断是否读取 + private bool CheckTagIsMonitor(Tag tag) + { + // 监控类型,长度为1,数组类型的不读取 + bool ret = tag.AccessType == TagAccessType.Monitor && + tag.tagType == TagType.tagTypeShortInt && + tag.tagLen == 1; + return ret; + } + + // 更新状态变化 + private void UpdateStateMsg(string msg) + { + //if (PLCStateChangeEvent != null) + //{ + // PLCStateChangeEvent.BeginInvoke(this.HostIp, msg, null, null); + //} + } + + // 更新值变化 + private void UpdateValueChange(Tag tag) + { + if (PLCValueChangeEvent != null) + { + PLCValueChangeEvent.BeginInvoke(this.HostIp, tag.TagName, tag, new AsyncCallback(CallWhenDown), null); + } + } + + private void CallWhenDown(IAsyncResult iar) + { + PLCValueChangeEvent.EndInvoke(iar); + } + + /// + /// 增加一个变量 + /// + /// 主机IP地址 + /// 变量名称 + /// 变量地址 + /// 变量的数据类型 + /// 变量的数量 + /// 变量的访问类型 + public void AddTag(string hostIp, string tagName, string tagAddress, TagType tagType, int num, TagAccessType tagAccessType) + { + Tag tag = new Tag(); + tag.HostIp = hostIp; + tag.TagName = tagName; + tag.TagAddress = tagAddress; + tag.AccessType = tagAccessType; + tag.tagType = tagType; + tag.tagLen = num; + + if (!this._dicTags.ContainsKey(tagName)) this._dicTags.Add(tagName, tag); + + } + + /// + /// 根据名称返回变量对象 + /// + /// + /// + public Tag FindTagByName(string tagName) + { + Tag tag = null; + if (this._dicTags.ContainsKey(tagName)) + { + tag = this._dicTags[tagName]; + } + return tag; + } + + /// + /// Encoding定义 + /// + /// + /// + private static Encoding GetEncodingFromIndex(int index) + { + switch (index) + { + case 0: return Encoding.ASCII; + case 1: return Encoding.Unicode; + case 2: return Encoding.BigEndianUnicode; + case 3: return Encoding.UTF8; + case 4: return Encoding.UTF32; + case 5: return Encoding.Default; + case 6: return Encoding.GetEncoding("gb2312"); + default: return Encoding.ASCII; + } + } + + #region 读取 + private static string Info; + + /// + /// BOOL读取 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 长度 + /// 返回结果 + private string read_bool(IReadWriteNet readWriteNet, string addre, int Len) + { + if (Len == 1) + { + OperateResult read = readWriteNet.ReadBool(addre); + DemoUtils.ReadResultRender(read, addre, out Info); + } + else + { + DateTime start = DateTime.Now; + OperateResult read = readWriteNet.ReadBool(addre, (ushort)Len); + DemoUtils.ReadResultRender(read, addre, out Info); + } + return Info; + } + + /// + /// 读取byte + /// + /// PLC连接对象 + /// 寄存器地址 + /// 长度 + /// + private string read_byte(IReadWriteNet readWriteNet, string addre, int Len) + { + OperateResult readBytes = readWriteNet.Read(addre, (ushort)Len); + DemoUtils.ReadResultRender(readBytes, addre, out Info); + + return Info; + } + + /// + /// short读取 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 长度 + /// + private string read_short(IReadWriteNet readWriteNet, string addre, int Len) + { + if (Len == 1) + { + OperateResult read = readWriteNet.ReadInt16(addre); + DemoUtils.ReadResultRender(read, addre, out Info); + } + else + { + OperateResult read = readWriteNet.ReadInt16(addre, (ushort)Len); ; + DemoUtils.ReadResultRender(read, addre, out Info); + } + return Info; + } + public string ReadShortSingle(string addre) + { + return read_short(this._plcConnectObj, addre, 1); + } + + public string[] ReadShortArray(string addre, int len) + { + string temp = read_short(this._plcConnectObj, addre, len); + return temp.Trim(new char[2] { '[', ']' }).Split(new char[] { ',' }); + } + /// + /// ushort读取 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 长度 + /// + private string read_ushort(IReadWriteNet readWriteNet, string addre, int Len) + { + if (Len == 1) + { + OperateResult read = readWriteNet.ReadUInt16(addre); + DemoUtils.ReadResultRender(read, addre, out Info); + } + else + { + OperateResult read = readWriteNet.ReadUInt16(addre, (ushort)Len); + DemoUtils.ReadResultRender(read, addre, out Info); + } + return Info; + } + + /// + /// int 读取 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 长度 + /// + private string read_int(IReadWriteNet readWriteNet, string addre, int Len) + { + if (Len == 1) + { + OperateResult read = readWriteNet.ReadInt32(addre); + DemoUtils.ReadResultRender(read, addre, out Info); + } + else + { + OperateResult read = readWriteNet.ReadInt32(addre, (ushort)Len); + DemoUtils.ReadResultRender(read, addre, out Info); + } + return Info; + } + + /// + /// unit读取 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 长度 + /// + private string read_uint(IReadWriteNet readWriteNet, string addre, int Len) + { + if (Len == 1) + { + OperateResult read = readWriteNet.ReadUInt32(addre); + DemoUtils.ReadResultRender(read, addre, out Info); + } + else + { + OperateResult read = readWriteNet.ReadUInt32(addre, (ushort)Len); + DemoUtils.ReadResultRender(read, addre, out Info); + } + return Info; + } + + /// + /// long读取 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 长度 + /// + private string read_long(IReadWriteNet readWriteNet, string addre, int Len) + { + if (Len == 1) + { + OperateResult read = readWriteNet.ReadInt64(addre); + DemoUtils.ReadResultRender(read, addre, out Info); + } + else + { + OperateResult read = readWriteNet.ReadInt64(addre, (ushort)Len); + DemoUtils.ReadResultRender(read, addre, out Info); + } + return Info; + } + + /// + /// ulong读取 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 长度 + /// + private string read_ulong(IReadWriteNet readWriteNet, string addre, int Len) + { + if (Len == 1) + { + OperateResult read = readWriteNet.ReadUInt64(addre); + DemoUtils.ReadResultRender(read, addre, out Info); + } + else + { + OperateResult read = readWriteNet.ReadUInt64(addre, (ushort)Len); + DemoUtils.ReadResultRender(read, addre, out Info); + } + return Info; + } + + /// + /// float读取 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 长度 + /// + private string read_float(IReadWriteNet readWriteNet, string addre, int Len) + { + if (Len == 1) + { + OperateResult read = readWriteNet.ReadFloat(addre); + DemoUtils.ReadResultRender(read, addre, out Info); + } + else + { + OperateResult read = readWriteNet.ReadFloat(addre, (ushort)Len); + DemoUtils.ReadResultRender(read, addre, out Info); + } + return Info; + } + + public string ReadFloatSingle(string addre) + { + return read_float(this._plcConnectObj, addre, 1); + } + + public string[] ReadFloatArray(string addre, int len) + { + string temp = read_float(this._plcConnectObj, addre, len); + return temp.Trim(new char[2] { '[', ']' }).Split(new char[] { ',' }); + } + + /// + /// double读取 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 长度 + /// + private string read_double(IReadWriteNet readWriteNet, string addre, int Len) + { + if (Len == 1) + { + OperateResult read = readWriteNet.ReadDouble(addre); + DemoUtils.ReadResultRender(read, addre, out Info); + } + else + { + OperateResult read = readWriteNet.ReadDouble(addre, (ushort)Len); + DemoUtils.ReadResultRender(read, addre, out Info); + } + return Info; + } + + /// + /// string读取 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 长度,西门子系列设成0;欧姆龙系列需要根据实际需求输入读取长度 + /// encodeing类型(默认0-ASCII):编码类型0-ASCII,1-Unicode,2-Unicode-big,3-UTF8,4-UTF32,5-ANSI,6-GB2312 + /// + private string read_string(IReadWriteNet readWriteNet, string addre, int Len, int encod = 0) + { + OperateResult read = readWriteNet.ReadString(addre, (ushort)Len, GetEncodingFromIndex(encod)); + DemoUtils.ReadResultRender(read, addre, out Info); + return Info; + } + #endregion + + #region 写入 + + private bool ResultsBool = false; + /// + /// bool写入 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 写入内容,多参数必须此格式[1,2,3,4,5] + /// 返回bool + private bool write_bool(IReadWriteNet readWriteNet, string addre, string content) + { + try + { + if (content.StartsWith("[") && content.EndsWith("]")) + { + bool[] value = content.ToStringArray(); + + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + + } + else + { + bool value = false; + if (content == "1") + { + value = true; + } + else + { + value = false; + } + + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + + } + } + catch (Exception ex) + { + //LogNet.WriteError("BOOL写入失败", ex.ToString()); + + //MessageBox.Show("Bool Data is not corrent: " + textBox7.Text + Environment.NewLine + ex.Message); + } + return ResultsBool; + } + + /// + /// byte写入 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 写入内容 + /// 返回bool + private bool write_byte(IReadWriteNet readWriteNet, string addre, string content) + { + try + { + if (byte.TryParse(content, out byte value)) + { + //OperateResult write = (OperateResult)writeByteMethod.Invoke(readWriteNet, new object[] { addre, value }); + //ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + } + catch (Exception ex) + { + // LogNet.WriteError("Byte写入失败", ex.ToString()); + } + return ResultsBool; + } + + /// + /// short写入 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 写入内容,多参数必须此格式[1,2,3,4,5] + /// 返回Bool + private bool write_short(IReadWriteNet readWriteNet, string addre, string content) + { + try + { + if (content.StartsWith("[") && content.EndsWith("]")) + { + short[] value = content.ToStringArray(); + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + else + { + if (short.TryParse(content, out short value)) + { + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + } + } + catch (Exception ex) + { + //LogNet.WriteError("short写入失败", ex.ToString()); + } + return ResultsBool; + } + + public bool WriteShortSingle(string addre, int value) + { + return write_short(this._plcConnectObj, addre, value.ToString()); + } + + + public bool WriteBool(string addre, int value) + { + return write_bool(this._plcConnectObj, addre, value.ToString()); + } + + public bool WriteShortArray(string addre, int[] value) + { + if (value == null) return false; + if (value.Length == 0) return false; + return write_short(this._plcConnectObj, addre, value.ToArrayString()); + } + + /// + /// ushot写入 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 写入内容,多参数必须此格式[1,2,3,4,5] + /// 返回bool + private bool write_ushort(IReadWriteNet readWriteNet, string addre, string content) + { + try + { + if (content.StartsWith("[") && content.EndsWith("]")) + { + ushort[] value = content.ToStringArray(); + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + else + { + if (ushort.TryParse(content, out ushort value)) + { + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + } + } + catch (Exception ex) + { + //LogNet.WriteError("ushort写入失败", ex.ToString()); + } + return ResultsBool; + } + + /// + /// int 写入 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 写入内容,多参数必须此格式[1,2,3,4,5] + /// 返回Bool + private bool write_int(IReadWriteNet readWriteNet, string addre, string content) + { + try + { + if (content.StartsWith("[") && content.EndsWith("]")) + { + int[] value = content.ToStringArray(); + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + else + { + if (int.TryParse(content, out int value)) + { + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + } + } + catch (Exception ex) + { + //LogNet.WriteError("int写入失败", ex.ToString()); + } + return ResultsBool; + } + + public bool WriteIntSingle(string addre, int value) + { + return write_int(this._plcConnectObj, addre, value.ToString()); + } + + /// + /// uint写入 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 写入内容,多参数必须此格式[1,2,3,4,5] + /// 返回bool + private bool write_uint(IReadWriteNet readWriteNet, string addre, string content) + { + try + { + if (content.StartsWith("[") && content.EndsWith("]")) + { + uint[] value = content.ToStringArray(); + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + else + { + if (uint.TryParse(content, out uint value)) + { + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + } + } + catch (Exception ex) + { + //LogNet.WriteError("uint写入失败", ex.ToString()); + } + return ResultsBool; + } + + /// + /// long写入 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 写入内容,多参数必须此格式[1,2,3,4,5] + /// 返回Bool + private bool write_long(IReadWriteNet readWriteNet, string addre, string content) + { + try + { + if (content.StartsWith("[") && content.EndsWith("]")) + { + long[] value = content.ToStringArray(); + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + else + { + if (long.TryParse(content, out long value)) + { + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + } + } + catch (Exception ex) + { + //LogNet.WriteError("long写入失败", ex.ToString()); + } + return ResultsBool; + } + + /// + /// ulong写入 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 写入内容,多参数必须此格式[1,2,3,4,5] + /// 返回Bool + private bool write_ulong(IReadWriteNet readWriteNet, string addre, string content) + { + try + { + if (content.StartsWith("[") && content.EndsWith("]")) + { + ulong[] value = content.ToStringArray(); + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + else + { + if (ulong.TryParse(content, out ulong value)) + { + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + } + } + catch (Exception ex) + { + //LogNet.WriteError("ulong写入失败", ex.ToString()); + } + return ResultsBool; + } + + /// + /// float写入 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 写入内容,多参数必须此格式[1,2,3,4,5] + /// 返回bool + private bool write_float(IReadWriteNet readWriteNet, string addre, string content) + { + try + { + if (content.StartsWith("[") && content.EndsWith("]")) + { + float[] value = content.ToStringArray(); + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + else + { + if (float.TryParse(content, out float value)) + { + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + } + } + catch (Exception ex) + { + //LogNet.WriteError("float写入失败", ex.ToString()); + } + return ResultsBool; + } + + public bool WriteFloatSingle(string addre, float value) + { + return write_float(this._plcConnectObj, addre, value.ToString()); + } + + public bool WriteFloatArray(string addre, float[] value) + { + if (value == null) return false; + if (value.Length == 0) return false; + return write_float(this._plcConnectObj, addre, value.ToArrayString()); + } + + /// + /// double写入 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 写入内容,多参数必须此格式[1,2,3,4,5] + /// 返回Bool + private bool write_double(IReadWriteNet readWriteNet, string addre, string content) + { + try + { + if (content.StartsWith("[") && content.EndsWith("]")) + { + double[] value = content.ToStringArray(); + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + else + { + if (double.TryParse(content, out double value)) + { + OperateResult write = readWriteNet.Write(addre, value); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + } + } + catch (Exception ex) + { + //HslCommunication.LogNet.WriteError("double写入失败", ex.ToString()); + } + return ResultsBool; + } + + /// + /// string写入 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 写入内容 + /// encodeing类型(默认0-ASCII):编码类型0-ASCII,1-Unicode,2-Unicode-big,3-UTF8,4-UTF32,5-ANSI,6-GB2312 + /// 返回bool + private bool write_string(IReadWriteNet readWriteNet, string addre, string content, int encod = 6) + { + try + { + OperateResult write = readWriteNet.Write(addre, content, GetEncodingFromIndex(encod)); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + catch (Exception ex) + { + //LogNet.WriteError("string写入失败", ex.ToString()); + } + return ResultsBool; + } + + /// + /// hex进制写入 + /// + /// PLC连接对象 + /// 寄存器地址 + /// 写入内容 + /// 返回bool + private bool write_hex(IReadWriteNet readWriteNet, string addre, string content) + { + try + { + DateTime start = DateTime.Now; + OperateResult write = readWriteNet.Write(addre, content.ToHexBytes()); + ResultsBool = DemoUtils.WriteResultRender(write, addre); + } + catch (Exception ex) + { + //HslCommunication.LogNet.WriteError("hex写入失败", ex.ToString()); + } + return ResultsBool; + } + #endregion + + } +} diff --git a/src/LineTraceabilitySystem/Helpers/CommonHelper.cs b/src/LineTraceabilitySystem/Helpers/CommonHelper.cs new file mode 100644 index 0000000..bceb690 --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/CommonHelper.cs @@ -0,0 +1,195 @@ +using System.IO; +using System.Net.Http; +using System.Text.RegularExpressions; +using LineTraceabilitySystem.Constants; + +namespace LineTraceabilitySystem.Helpers +{ + /// + /// 通用辅助方法类 + /// + public static class CommonHelper + { + #region 字符串处理 + + /// + /// 验证字符串是否为有效的追溯码格式 + /// + /// 追溯码 + /// 是否有效 + public static bool IsValidTraceabilityCode(string code) + { + if (string.IsNullOrEmpty(code)) + { + return false; + } + + // 假设追溯码格式为:字母+数字,长度在6-20之间 + return Regex.IsMatch(code, "^[A-Za-z0-9]{6,20}$"); + } + + /// + /// 验证字符串是否为有效的摄像头码格式 + /// + /// 摄像头码 + /// 是否有效 + public static bool IsValidCameraCode(string code) + { + if (string.IsNullOrEmpty(code)) + { + return false; + } + + // 假设摄像头码格式为:CAM-数字,总长度在8-15之间 + return Regex.IsMatch(code, "^CAM-[0-9]{5,12}$"); + } + + /// + /// 清理扫码字符串中的无效字符 + /// + /// 输入字符串 + /// 清理后的字符串 + public static string CleanScanInput(string input) + { + if (string.IsNullOrEmpty(input)) + { + return string.Empty; + } + + // 去除首尾空格和不可见字符 + string cleaned = input.Trim(); + + // 移除常见的扫码前缀和后缀 + cleaned = Regex.Replace(cleaned, "^\\[\\[.*?\"*", string.Empty); + cleaned = Regex.Replace(cleaned, "\\]\\]\"*$", string.Empty); + + return cleaned; + } + + #endregion + + #region 日期时间处理 + + /// + /// 将日期时间转换为显示字符串 + /// + /// 日期时间 + /// 显示字符串 + public static string ToDisplayDateTime(DateTime dateTime) + { + return dateTime.ToString("yyyy-MM-dd HH:mm:ss"); + } + + /// + /// 将日期时间转换为简洁显示字符串 + /// + /// 日期时间 + /// 简洁显示字符串 + public static string ToShortDisplayDateTime(DateTime dateTime) + { + if (dateTime.Date == DateTime.Today) + { + return dateTime.ToString("HH:mm:ss"); + } + return dateTime.ToString("MM-dd HH:mm"); + } + + #endregion + + #region 错误处理 + + /// + /// 获取友好的错误信息 + /// + /// 异常 + /// 友好的错误信息 + public static string GetFriendlyErrorMessage(Exception exception) + { + if (exception == null) + { + return "操作失败,请重试。"; + } + + // 根据异常类型返回不同的友好错误信息 + if (exception is HttpRequestException) + { + return "网络连接异常,请检查网络设置后重试。"; + } + else if (exception is TimeoutException) + { + return "操作超时,请稍后重试。"; + } + else if (exception is UnauthorizedAccessException) + { + return "没有权限执行此操作,请联系管理员。"; + } + else if (exception is IOException) + { + return "文件操作失败,请检查文件权限。"; + } + else if (exception is FormatException || exception is ArgumentException) + { + return "数据格式错误,请检查输入数据。"; + } + else + { + // 对于其他异常,返回通用错误信息 + return "操作过程中出现错误,请重试。"; + } + } + + #endregion + + #region 文件路径处理 + + /// + /// 确保目录存在 + /// + /// 目录路径 + public static void EnsureDirectoryExists(string directoryPath) + { + if (!Directory.Exists(directoryPath)) + { + Directory.CreateDirectory(directoryPath); + } + } + + /// + /// 获取应用程序数据目录 + /// + /// 应用程序数据目录路径 + public static string GetApplicationDataDirectory() + { + string appDataDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); + string appDir = Path.Combine(appDataDir, Constants.AppConstants.AppName); + EnsureDirectoryExists(appDir); + return appDir; + } + + #endregion + + #region 其他辅助方法 + + /// + /// 生成唯一ID + /// + /// 唯一ID字符串 + public static string GenerateUniqueId() + { + return Guid.NewGuid().ToString("N"); + } + + /// + /// 延迟执行操作 + /// + /// 要执行的操作 + /// 延迟毫秒数 + public static async Task DelayExecution(Action action, int delayMilliseconds) + { + await Task.Delay(delayMilliseconds); + action.Invoke(); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Helpers/Converters/StatusToColorConverter.cs b/src/LineTraceabilitySystem/Helpers/Converters/StatusToColorConverter.cs new file mode 100644 index 0000000..a33e2fe --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/Converters/StatusToColorConverter.cs @@ -0,0 +1,38 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media; + +namespace LineTraceabilitySystem.Helpers.Converters +{ + public class StatusToColorConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string status) + { + switch (status.ToLower()) + { + case "正常": + case "已连接": + case "ok": + return Brushes.Green; + case "错误": + case "未连接": + case "failed": + return Brushes.Red; + case "警告": + return Brushes.Orange; + default: + return Brushes.Gray; + } + } + return Brushes.Gray; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Helpers/Converters/StatusToIconConverter.cs b/src/LineTraceabilitySystem/Helpers/Converters/StatusToIconConverter.cs new file mode 100644 index 0000000..88e95c2 --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/Converters/StatusToIconConverter.cs @@ -0,0 +1,38 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using Wpf.Ui.Controls; + +namespace LineTraceabilitySystem.Helpers.Converters +{ + public class StatusToIconConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string status) + { + switch (status.ToLower()) + { + case "正常": + case "已连接": + case "ok": + return "CheckCircle"; + case "错误": + case "未连接": + case "failed": + return "ErrorCircle"; + case "警告": + return "WarningCircle"; + default: + return "HelpCircle"; + } + } + return "HelpCircle"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Helpers/Converters/StatusToImageConverter.cs b/src/LineTraceabilitySystem/Helpers/Converters/StatusToImageConverter.cs new file mode 100644 index 0000000..e53d990 --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/Converters/StatusToImageConverter.cs @@ -0,0 +1,38 @@ +using System; +using System.Globalization; +using System.Windows.Data; +using System.Windows.Media.Imaging; + +namespace LineTraceabilitySystem.Helpers.Converters +{ + public class StatusToImageConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string status) + { + switch (status.ToLower()) + { + case "正常": + case "已连接": + case "ok": + return new BitmapImage(new Uri("/Icons/CheckCircle.png", UriKind.Relative)); + case "错误": + case "未连接": + case "failed": + return new BitmapImage(new Uri("/Icons/ErrorCircle.png", UriKind.Relative)); + case "警告": + return new BitmapImage(new Uri("/Icons/WarningCircle.png", UriKind.Relative)); + default: + return new BitmapImage(new Uri("/Icons/HelpCircle.png", UriKind.Relative)); + } + } + return new BitmapImage(new Uri("/Icons/HelpCircle.png", UriKind.Relative)); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Helpers/SqlSugarHelper.cs b/src/LineTraceabilitySystem/Helpers/SqlSugarHelper.cs new file mode 100644 index 0000000..c66843e --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/SqlSugarHelper.cs @@ -0,0 +1,55 @@ +using LineTraceabilitySystem.Constants; + +namespace LineTraceabilitySystem.Helpers +{ + /// + /// SqlSugar数据库帮助类 + /// + public class SqlSugarHelper + { + private static SqlSugarClient _db; + + /// + /// 数据库连接字符串 + /// + private static string ConnectionString => AppConstants.ConnectionString; + + /// + /// 获取数据库实例 + /// + /// SqlSugarClient实例 + public static SqlSugarClient GetInstance() + { + if (_db == null) + { + _db = new SqlSugarClient(new ConnectionConfig + { + ConnectionString = ConnectionString, + DbType = DbType.MySql, // 根据实际数据库类型调整 + IsAutoCloseConnection = true, + InitKeyType = InitKeyType.Attribute + }); + + // 启用日志 + _db.Aop.OnLogExecuting = (sql, parameters) => + { + System.Diagnostics.Debug.WriteLine($"SQL: {sql}"); + if (parameters != null && parameters.Count() > 0) + { + var paramStr = string.Join(", ", parameters.Select(p => $"{p.ParameterName}={p.Value}")); + System.Diagnostics.Debug.WriteLine($"参数: {paramStr}"); + } + }; + } + return _db; + } + + /// + /// 初始化数据库 + /// + public static void InitDatabase() + { + _db.DbMaintenance.CreateDatabase(); + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Helpers/StepToStringConverter.cs b/src/LineTraceabilitySystem/Helpers/StepToStringConverter.cs new file mode 100644 index 0000000..303d934 --- /dev/null +++ b/src/LineTraceabilitySystem/Helpers/StepToStringConverter.cs @@ -0,0 +1,36 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace LineTraceabilitySystem.Helpers +{ + /// + /// 将步骤数字转换为文本描述 + /// + public class StepToStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is int step) + { + switch (step) + { + case 0: + return "扫追溯码"; + case 10: + return "扫第一个子零件码"; + case 20: + return "扫第二个子零件码"; + default: + return "未知步骤"; + } + } + return "无效步骤"; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Icons/CheckCircle.svg b/src/LineTraceabilitySystem/Icons/CheckCircle.svg new file mode 100644 index 0000000..9181246 --- /dev/null +++ b/src/LineTraceabilitySystem/Icons/CheckCircle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Icons/ChevronDown.svg b/src/LineTraceabilitySystem/Icons/ChevronDown.svg new file mode 100644 index 0000000..7498e37 --- /dev/null +++ b/src/LineTraceabilitySystem/Icons/ChevronDown.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Icons/Cpu.svg b/src/LineTraceabilitySystem/Icons/Cpu.svg new file mode 100644 index 0000000..aaee26d --- /dev/null +++ b/src/LineTraceabilitySystem/Icons/Cpu.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Icons/ErrorCircle.svg b/src/LineTraceabilitySystem/Icons/ErrorCircle.svg new file mode 100644 index 0000000..6ff438a --- /dev/null +++ b/src/LineTraceabilitySystem/Icons/ErrorCircle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Icons/HelpCircle.svg b/src/LineTraceabilitySystem/Icons/HelpCircle.svg new file mode 100644 index 0000000..4d776d8 --- /dev/null +++ b/src/LineTraceabilitySystem/Icons/HelpCircle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Icons/WarningCircle.svg b/src/LineTraceabilitySystem/Icons/WarningCircle.svg new file mode 100644 index 0000000..4a6baeb --- /dev/null +++ b/src/LineTraceabilitySystem/Icons/WarningCircle.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/LineTraceabilitySystem/LineTraceabilitySystem.csproj b/src/LineTraceabilitySystem/LineTraceabilitySystem.csproj new file mode 100644 index 0000000..d44fce1 --- /dev/null +++ b/src/LineTraceabilitySystem/LineTraceabilitySystem.csproj @@ -0,0 +1,43 @@ + + + + WinExe + net8.0-windows + true + enable + LineTraceabilitySystem + 产线追溯系统 + 产线追溯系统 + enable + + + + + + + + + + + + + + + + + + + bin\Debug\net8.0-windows\HslCommunication.dll + + + bin\Debug\net8.0-windows\Register.dll + + + App.xaml + + + MainWindow.xaml + + + + \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/OperationLog.cs b/src/LineTraceabilitySystem/Models/OperationLog.cs new file mode 100644 index 0000000..6bf445d --- /dev/null +++ b/src/LineTraceabilitySystem/Models/OperationLog.cs @@ -0,0 +1,49 @@ +namespace LineTraceabilitySystem.Models +{ + /// + /// 操作日志模型 + /// + public class OperationLog + { + /// + /// 日志ID + /// + public int Id { get; set; } + + /// + /// 操作时间 + /// + public DateTime Timestamp { get; set; } + + /// + /// 操作类型 + /// + public string OperationType { get; set; } = string.Empty; + + /// + /// 操作详情 + /// + public string Details { get; set; } = string.Empty; + + /// + /// 操作结果 + /// + public bool Success { get; set; } + + /// + /// 错误信息(如果有) + /// + public string? ErrorMessage { get; set; } + + /// + /// 转换为显示字符串 + /// + /// + public override string ToString() + { + var resultText = Success ? "成功" : "失败"; + var errorText = !Success && !string.IsNullOrEmpty(ErrorMessage) ? $" - 错误: {ErrorMessage}" : ""; + return $"[{Timestamp:HH:mm:ss}] {OperationType}: {Details} - {resultText}{errorText}"; + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/Dto/TraceConfigDto.cs b/src/LineTraceabilitySystem/Models/trace/Dto/TraceConfigDto.cs new file mode 100644 index 0000000..fc525d6 --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/Dto/TraceConfigDto.cs @@ -0,0 +1,46 @@ +using System.ComponentModel.DataAnnotations; + +namespace LineTraceabilitySystem.Models.trace.Dto +{ + /// + /// 设备下载配置 + /// + public class DeviceDownLoadTraceConfigQueryDto + { + public string ProcessCode { get; set; } + } + + /// + /// 追溯配置,涵盖通用,项目,零件不同配置输入输出对象 + /// + public class TraceConfigDto + { + [Required(ErrorMessage = "主键不能为空")] + public int Id { get; set; } + + public string ConfigCode { get; set; } + + public string ConfigName { get; set; } + + public string ConfigValue { get; set; } + + public string ConfigType { get; set; } + + public string ConfigPartCode { get; set; } + + public string ConfigProjectCode { get; set; } + + public string FactoryCode { get; set; } + + public string WorkshopCode { get; set; } + + public string LineCode { get; set; } + + public string ProcessCode { get; set; } + + public string DeviceCode { get; set; } + + + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/Dto/TraceDictEolParameterDto.cs b/src/LineTraceabilitySystem/Models/trace/Dto/TraceDictEolParameterDto.cs new file mode 100644 index 0000000..3e6a58d --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/Dto/TraceDictEolParameterDto.cs @@ -0,0 +1,37 @@ +using System.ComponentModel.DataAnnotations; + +namespace LineTraceabilitySystem.Models.trace.Dto +{ + + /// + /// 终检台,追溯码绑定检测数据列描述字典输入输出对象 + /// + public class TraceDictEolParameterDto + { + [Required(ErrorMessage = "数据上传字典不能为空")] + public int Id { get; set; } + + public string ColCode { get; set; } + + public string Code { get; set; } + + public string Name { get; set; } + + public decimal UpperLimit { get; set; } + + public decimal LowerLimit { get; set; } + + public string Unit { get; set; } + + public int? Sort { get; set; } + + public int? Status { get; set; } + + public string CreateBy { get; set; } + + public DateTime? CreateTime { get; set; } + + + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/Dto/TraceDictZdParameterDto.cs b/src/LineTraceabilitySystem/Models/trace/Dto/TraceDictZdParameterDto.cs new file mode 100644 index 0000000..19d03ff --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/Dto/TraceDictZdParameterDto.cs @@ -0,0 +1,37 @@ +using System.ComponentModel.DataAnnotations; + +namespace LineTraceabilitySystem.Models.trace.Dto +{ + + /// + /// 折叠台,追溯码绑定检测数据列描述字典输入输出对象 + /// + public class TraceDictZdParameterDto + { + [Required(ErrorMessage = "数据上传字典不能为空")] + public int Id { get; set; } + + public string ColCode { get; set; } + + public string Code { get; set; } + + public string Name { get; set; } + + public decimal UpperLimit { get; set; } + + public decimal LowerLimit { get; set; } + + public string Unit { get; set; } + + public int? Sort { get; set; } + + public int? Status { get; set; } + + public string CreateBy { get; set; } + + public DateTime? CreateTime { get; set; } + + + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/Dto/TraceLogDto.cs b/src/LineTraceabilitySystem/Models/trace/Dto/TraceLogDto.cs new file mode 100644 index 0000000..4173301 --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/Dto/TraceLogDto.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; + +namespace LineTraceabilitySystem.Models.trace.Dto +{ + + /// + /// 追溯日志,错误日志,警告日志,调试日志输入输出对象 + /// + public class TraceLogDto + { + public long Id { get; set; } + + public string LogTitle { get; set; } + + public string LogType { get; set; } + + public string LogContent { get; set; } + + public string CreateBy { get; set; } + + public DateTime? CreateTime { get; set; } + + + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/Dto/TraceProjectDto.cs b/src/LineTraceabilitySystem/Models/trace/Dto/TraceProjectDto.cs new file mode 100644 index 0000000..b3d49d4 --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/Dto/TraceProjectDto.cs @@ -0,0 +1,31 @@ +using System.ComponentModel.DataAnnotations; + +namespace LineTraceabilitySystem.Models.trace.Dto +{ + /// + /// 追溯项目清单输入输出对象 + /// + public class TraceProjectDto + { + [Required(ErrorMessage = "项目id不能为空")] + public int Id { get; set; } + + public string ProjectName { get; set; } + + [Required(ErrorMessage = "项目编号不能为空")] + public string ProjectCode { get; set; } + + public string FactoryCode { get; set; } + + public string WorkshopCode { get; set; } + + public string LineCode { get; set; } + + public string CreateBy { get; set; } + + public DateTime? CreateTime { get; set; } + + + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/Dto/TraceProjectPartDto.cs b/src/LineTraceabilitySystem/Models/trace/Dto/TraceProjectPartDto.cs new file mode 100644 index 0000000..0cad748 --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/Dto/TraceProjectPartDto.cs @@ -0,0 +1,39 @@ +using System.ComponentModel.DataAnnotations; + +namespace LineTraceabilitySystem.Models.trace.Dto +{ + + /// + /// 追溯零件清单输入输出对象 + /// + public class TraceProjectPartDto + { + [Required(ErrorMessage = "零件id不能为空")] + public int Id { get; set; } + + [Required(ErrorMessage = "项目号不能为空")] + public string ProjectCode { get; set; } + + [Required(ErrorMessage = "总成号不能为空")] + public string ProductionCode { get; set; } + + public string ProductionName { get; set; } + + public string Specification { get; set; } + + public string Colour { get; set; } + + public string HandleType { get; set; } + + public string CustomCode { get; set; } + + public string Unit { get; set; } + + public string CreateBy { get; set; } + + public DateTime? CreateTime { get; set; } + + + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/Dto/TraceSnQcRecordDto.cs b/src/LineTraceabilitySystem/Models/trace/Dto/TraceSnQcRecordDto.cs new file mode 100644 index 0000000..95aa6c6 --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/Dto/TraceSnQcRecordDto.cs @@ -0,0 +1,118 @@ +using System.ComponentModel.DataAnnotations; + +namespace LineTraceabilitySystem.Models.trace.Dto +{ + + /// + /// 追溯扫码,扫子零件,绑定sn标签(模板,实际使用需要按时间分表)输入输出对象 + /// + public class TraceSnQcRecordDto + { + public long Id { get; set; } + + [Required(ErrorMessage = "项目编号不能为空")] + public string ProjectCode { get; set; } + + [Required(ErrorMessage = "产品编号不能为空")] + public string ProductionCode { get; set; } + + public string ProductionName { get; set; } + + public string Specification { get; set; } + + [Required(ErrorMessage = "追溯码SN不能为空")] + public string PartSn { get; set; } + + [Required(ErrorMessage = "检查类别目前有(eol,zd)不能为空")] + public string QcType { get; set; } + + public string Value01 { get; set; } + + public int? IsBack { get; set; } + + public string CreateBy { get; set; } + + public DateTime? CreateTime { get; set; } + + public string Value02 { get; set; } + + public string Value03 { get; set; } + + public string Value04 { get; set; } + + public string Value05 { get; set; } + + public string Value06 { get; set; } + + public string Value07 { get; set; } + + public string Value08 { get; set; } + + public string Value09 { get; set; } + + public string Value10 { get; set; } + + public string Value11 { get; set; } + + public string Value12 { get; set; } + + public string Value13 { get; set; } + + public string Value14 { get; set; } + + public string Value15 { get; set; } + + public string Value16 { get; set; } + + public string Value17 { get; set; } + + public string Value18 { get; set; } + + public string Value19 { get; set; } + + public string Value20 { get; set; } + + public string Value21 { get; set; } + + public string Value22 { get; set; } + + public string Value23 { get; set; } + + public string Value24 { get; set; } + + public string Value25 { get; set; } + + public string Value26 { get; set; } + + public string Value27 { get; set; } + + public string Value28 { get; set; } + + public string Value29 { get; set; } + + public string Value30 { get; set; } + + public string Value31 { get; set; } + + public string Value32 { get; set; } + + public string Value33 { get; set; } + + public string Value34 { get; set; } + + public string Value35 { get; set; } + + public string Value36 { get; set; } + + public string Value37 { get; set; } + + public string Value38 { get; set; } + + public string Value39 { get; set; } + + public string Value40 { get; set; } + + + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/Dto/TraceSnScanDto.cs b/src/LineTraceabilitySystem/Models/trace/Dto/TraceSnScanDto.cs new file mode 100644 index 0000000..5fa528b --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/Dto/TraceSnScanDto.cs @@ -0,0 +1,29 @@ +using System.ComponentModel.DataAnnotations; + +namespace LineTraceabilitySystem.Models.trace.Dto +{ + + /// + /// 追溯,扫sn追溯码记录(模板,实际使用需要按时间分表)输入输出对象 + /// + public class TraceSnScanDto + { + public long Id { get; set; } + + [Required(ErrorMessage = "项目编号不能为空")] + public string ProjectCode { get; set; } + + [Required(ErrorMessage = "零件编号不能为空")] + public string ProductionCode { get; set; } + + public string Specification { get; set; } + + [Required(ErrorMessage = "追溯码SN不能为空")] + public string PartSn { get; set; } + + public DateTime? CreateTime { get; set; } + + + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/Dto/TraceSnSubScanDto.cs b/src/LineTraceabilitySystem/Models/trace/Dto/TraceSnSubScanDto.cs new file mode 100644 index 0000000..7c8ff2e --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/Dto/TraceSnSubScanDto.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; + +namespace LineTraceabilitySystem.Models.trace.Dto +{ + + /// + /// 追溯扫码,扫子零件,绑定sn标签(模板,实际使用需要按时间分表)输入输出对象 + /// + public class TraceSnSubScanDto + { + public long Id { get; set; } + + public string SubCode { get; set; } + + public string SubName { get; set; } + + public string Specification { get; set; } + + [Required(ErrorMessage = "追溯码SN不能为空")] + public string PartSn { get; set; } + + [Required(ErrorMessage = "零件子件不能为空")] + public string SubPartScanCode { get; set; } + + public int? IsBack { get; set; } + + public DateTime? CreateTime { get; set; } + + + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/Dto/TraceSnTransitDto.cs b/src/LineTraceabilitySystem/Models/trace/Dto/TraceSnTransitDto.cs new file mode 100644 index 0000000..8c5e241 --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/Dto/TraceSnTransitDto.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; + +namespace LineTraceabilitySystem.Models.trace.Dto +{ + + /// + /// 追溯码过站记录输入输出对象 + /// + public class TraceSnTransitDto + { + public long Id { get; set; } + + [Required(ErrorMessage = "追溯码SN不能为空")] + public string PartSn { get; set; } + + [Required(ErrorMessage = "过站名称不能为空")] + public string TransitName { get; set; } + + public int? IsBack { get; set; } + + public DateTime? CreateTime { get; set; } + + + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/TraceConfig.cs b/src/LineTraceabilitySystem/Models/trace/TraceConfig.cs new file mode 100644 index 0000000..e9ec01c --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/TraceConfig.cs @@ -0,0 +1,78 @@ +global using SqlSugar; +namespace LineTraceabilitySystem.Models.trace +{ + /// + /// 追溯配置,涵盖通用,项目,零件不同配置 + /// + [SugarTable("trace_config")] + public class TraceConfig + { + /// + /// 主键 + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + + /// + /// 配置编号 + /// + [SugarColumn(ColumnName = "config_code")] + public string ConfigCode { get; set; } + + /// + /// 配置名称 + /// + [SugarColumn(ColumnName = "config_name")] + public string ConfigName { get; set; } + + /// + /// 配置内容 + /// + [SugarColumn(ColumnName = "config_value")] + public string ConfigValue { get; set; } + + /// + /// 配置类别 1-通用 2-项目专用 3-线专用 + /// + [SugarColumn(ColumnName = "config_type")] + public string ConfigType { get; set; } + + /// + /// 所属零件号 + /// + [SugarColumn(ColumnName = "config_part_code")] + public string ConfigPartCode { get; set; } + + /// + /// 所属项目号 + /// + [SugarColumn(ColumnName = "config_project_code")] + public string ConfigProjectCode { get; set; } + + /// + /// 工厂号 + /// + public string FactoryCode { get; set; } + + /// + /// 车间号 + /// + public string WorkshopCode { get; set; } + + /// + /// 线别 + /// + public string LineCode { get; set; } + + /// + /// 工序号 + /// + public string ProcessCode { get; set; } + + /// + /// 设备号 + /// + public string DeviceCode { get; set; } + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/TraceDictEolParameter.cs b/src/LineTraceabilitySystem/Models/trace/TraceDictEolParameter.cs new file mode 100644 index 0000000..9ab8dcf --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/TraceDictEolParameter.cs @@ -0,0 +1,72 @@ + +namespace LineTraceabilitySystem.Models.trace +{ + /// + /// 终检台,追溯码绑定检测数据列描述字典 + /// + [SugarTable("trace_dict_eol_parameter")] + public class TraceDictEolParameter + { + /// + /// 数据上传字典 + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + + /// + /// 列名称 + /// + [SugarColumn(ColumnName = "col_code")] + public string ColCode { get; set; } + + /// + /// 编号 + /// + public string Code { get; set; } + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 上限 + /// + [SugarColumn(ColumnName = "upper_limit")] + public decimal UpperLimit { get; set; } + + /// + /// 下限 + /// + [SugarColumn(ColumnName = "lower_limit")] + public decimal LowerLimit { get; set; } + + /// + /// 单位 + /// + public string Unit { get; set; } + + /// + /// 排序 + /// + public int? Sort { get; set; } + + /// + /// 是否启用 0-停用 1-启用 + /// + public int? Status { get; set; } + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "create_by")] + public string CreateBy { get; set; } + + /// + /// CreateTime + /// + [SugarColumn(ColumnName = "create_time")] + public DateTime? CreateTime { get; set; } + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/TraceDictZdParameter.cs b/src/LineTraceabilitySystem/Models/trace/TraceDictZdParameter.cs new file mode 100644 index 0000000..db4eabe --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/TraceDictZdParameter.cs @@ -0,0 +1,72 @@ + +namespace LineTraceabilitySystem.Models.trace +{ + /// + /// 折叠台,追溯码绑定检测数据列描述字典 + /// + [SugarTable("trace_dict_zd_parameter")] + public class TraceDictZdParameter + { + /// + /// 数据上传字典 + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + + /// + /// 列名称 + /// + [SugarColumn(ColumnName = "col_code")] + public string ColCode { get; set; } + + /// + /// 编号 + /// + public string Code { get; set; } + + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 上限 + /// + [SugarColumn(ColumnName = "upper_limit")] + public decimal UpperLimit { get; set; } + + /// + /// 下限 + /// + [SugarColumn(ColumnName = "lower_limit")] + public decimal LowerLimit { get; set; } + + /// + /// 单位 + /// + public string Unit { get; set; } + + /// + /// 排序 + /// + public int? Sort { get; set; } + + /// + /// 是否启用 0-停用 1-启用 + /// + public int? Status { get; set; } + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "create_by")] + public string CreateBy { get; set; } + + /// + /// CreateTime + /// + [SugarColumn(ColumnName = "create_time")] + public DateTime? CreateTime { get; set; } + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/TraceLog.cs b/src/LineTraceabilitySystem/Models/trace/TraceLog.cs new file mode 100644 index 0000000..dece81a --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/TraceLog.cs @@ -0,0 +1,47 @@ + +namespace LineTraceabilitySystem.Models.trace +{ + /// + /// 追溯日志,错误日志,警告日志,调试日志 + /// + [SugarTable("trace_log")] + public class TraceLog + { + /// + /// 日志id + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 日志标题 + /// + [SugarColumn(ColumnName = "log_title")] + public string LogTitle { get; set; } + + /// + /// 日志类别 + /// + [SugarColumn(ColumnName = "log_type")] + public string LogType { get; set; } + + /// + /// 日志内容 + /// + [SugarColumn(ColumnName = "log_content")] + public string LogContent { get; set; } + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "create_by")] + public string CreateBy { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "create_time")] + public DateTime? CreateTime { get; set; } + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/TraceProject.cs b/src/LineTraceabilitySystem/Models/trace/TraceProject.cs new file mode 100644 index 0000000..8290ed2 --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/TraceProject.cs @@ -0,0 +1,56 @@ + +namespace LineTraceabilitySystem.Models.trace +{ + /// + /// 追溯项目清单 + /// + [SugarTable("trace_project")] + public class TraceProject + { + /// + /// 项目id + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + + /// + /// 项目名称 + /// + [SugarColumn(ColumnName = "project_name")] + public string ProjectName { get; set; } + + /// + /// 项目编号 + /// + [SugarColumn(ColumnName = "project_code")] + public string ProjectCode { get; set; } + + /// + /// 工厂号 + /// + public string FactoryCode { get; set; } + + /// + /// 车间号 + /// + public string WorkshopCode { get; set; } + + /// + /// 产线号 + /// + public string LineCode { get; set; } + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "create_by")] + public string CreateBy { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "create_time")] + public DateTime? CreateTime { get; set; } + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/TraceProjectPart.cs b/src/LineTraceabilitySystem/Models/trace/TraceProjectPart.cs new file mode 100644 index 0000000..7c52028 --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/TraceProjectPart.cs @@ -0,0 +1,74 @@ + +namespace LineTraceabilitySystem.Models.trace +{ + /// + /// 追溯零件清单 + /// + [SugarTable("trace_project_part")] + public class TraceProjectPart + { + /// + /// 零件id + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public int Id { get; set; } + + /// + /// 项目号 + /// + [SugarColumn(ColumnName = "project_code")] + public string ProjectCode { get; set; } + + /// + /// 总成号 + /// + [SugarColumn(ColumnName = "production_code")] + public string ProductionCode { get; set; } + + /// + /// 总成名称 + /// + [SugarColumn(ColumnName = "production_name")] + public string ProductionName { get; set; } + + /// + /// 规格 + /// + public string Specification { get; set; } + + /// + /// 颜色 + /// + public string Colour { get; set; } + + /// + /// 左右脚 -L -R + /// + [SugarColumn(ColumnName = "handle_type")] + public string HandleType { get; set; } + + /// + /// 客户编码 + /// + [SugarColumn(ColumnName = "custom_code")] + public string CustomCode { get; set; } + + /// + /// 单位 + /// + public string Unit { get; set; } + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "create_by")] + public string CreateBy { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "create_time")] + public DateTime? CreateTime { get; set; } + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/TraceSnQcRecord.cs b/src/LineTraceabilitySystem/Models/trace/TraceSnQcRecord.cs new file mode 100644 index 0000000..ba0c685 --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/TraceSnQcRecord.cs @@ -0,0 +1,270 @@ + +namespace LineTraceabilitySystem.Models.trace +{ + /// + /// 追溯扫码,扫子零件,绑定sn标签(模板,实际使用需要按时间分表) + /// + [SugarTable("trace_sn_qc_record")] + public class TraceSnQcRecord + { + /// + /// 追溯扫码标签内容 + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 项目编号 + /// + [SugarColumn(ColumnName = "project_code")] + public string ProjectCode { get; set; } + + /// + /// 产品编号 + /// + [SugarColumn(ColumnName = "production_code")] + public string ProductionCode { get; set; } + + /// + /// 产品名称 + /// + [SugarColumn(ColumnName = "production_name")] + public string ProductionName { get; set; } + + /// + /// 产品规格 + /// + public string Specification { get; set; } + + /// + /// 追溯码SN + /// + [SugarColumn(ColumnName = "part_sn")] + public string PartSn { get; set; } + + /// + /// 检查类别目前有(eol,zd) + /// + [SugarColumn(ColumnName = "qc_type")] + public string QcType { get; set; } + + /// + /// 检测结果,检查名称与内容标准见dict表 + /// + public string Value01 { get; set; } + + /// + /// 是否返工 1-是 0-正常 + /// + [SugarColumn(ColumnName = "is_back")] + public int? IsBack { get; set; } + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "create_by")] + public string CreateBy { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "create_time")] + public DateTime? CreateTime { get; set; } + + /// + /// 参数 + /// + public string Value02 { get; set; } + + /// + /// 参数 + /// + public string Value03 { get; set; } + + /// + /// 参数 + /// + public string Value04 { get; set; } + + /// + /// 参数 + /// + public string Value05 { get; set; } + + /// + /// 参数 + /// + public string Value06 { get; set; } + + /// + /// 参数 + /// + public string Value07 { get; set; } + + /// + /// 参数 + /// + public string Value08 { get; set; } + + /// + /// 参数 + /// + public string Value09 { get; set; } + + /// + /// 参数 + /// + public string Value10 { get; set; } + + /// + /// 参数 + /// + public string Value11 { get; set; } + + /// + /// 参数 + /// + public string Value12 { get; set; } + + /// + /// 参数 + /// + public string Value13 { get; set; } + + /// + /// 参数 + /// + public string Value14 { get; set; } + + /// + /// 参数 + /// + public string Value15 { get; set; } + + /// + /// 参数 + /// + public string Value16 { get; set; } + + /// + /// 参数 + /// + public string Value17 { get; set; } + + /// + /// 参数 + /// + public string Value18 { get; set; } + + /// + /// 参数 + /// + public string Value19 { get; set; } + + /// + /// 参数 + /// + public string Value20 { get; set; } + + /// + /// 参数 + /// + public string Value21 { get; set; } + + /// + /// 参数 + /// + public string Value22 { get; set; } + + /// + /// 参数 + /// + public string Value23 { get; set; } + + /// + /// 参数 + /// + public string Value24 { get; set; } + + /// + /// 参数 + /// + public string Value25 { get; set; } + + /// + /// 参数 + /// + public string Value26 { get; set; } + + /// + /// 参数 + /// + public string Value27 { get; set; } + + /// + /// 参数 + /// + public string Value28 { get; set; } + + /// + /// 参数 + /// + public string Value29 { get; set; } + + /// + /// 参数 + /// + public string Value30 { get; set; } + + /// + /// 参数 + /// + public string Value31 { get; set; } + + /// + /// 参数 + /// + public string Value32 { get; set; } + + /// + /// 参数 + /// + public string Value33 { get; set; } + + /// + /// 参数 + /// + public string Value34 { get; set; } + + /// + /// 参数 + /// + public string Value35 { get; set; } + + /// + /// 参数 + /// + public string Value36 { get; set; } + + /// + /// 参数 + /// + public string Value37 { get; set; } + + /// + /// 参数 + /// + public string Value38 { get; set; } + + /// + /// 参数 + /// + public string Value39 { get; set; } + + /// + /// 参数 + /// + public string Value40 { get; set; } + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/TraceSnScan.cs b/src/LineTraceabilitySystem/Models/trace/TraceSnScan.cs new file mode 100644 index 0000000..3fee79f --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/TraceSnScan.cs @@ -0,0 +1,46 @@ + +namespace LineTraceabilitySystem.Models.trace +{ + /// + /// 追溯,扫sn追溯码记录(模板,实际使用需要按时间分表) + /// + [SugarTable("trace_sn_scan")] + public class TraceSnScan + { + /// + /// 追溯扫码标签内容 + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 项目编号 + /// + [SugarColumn(ColumnName = "project_code")] + public string ProjectCode { get; set; } + + /// + /// 零件编号 + /// + [SugarColumn(ColumnName = "production_code")] + public string ProductionCode { get; set; } + + /// + /// 规格 + /// + public string Specification { get; set; } + + /// + /// 追溯码SN + /// + [SugarColumn(ColumnName = "part_sn")] + public string PartSn { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "create_time")] + public DateTime? CreateTime { get; set; } + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/TraceSnSubScan.cs b/src/LineTraceabilitySystem/Models/trace/TraceSnSubScan.cs new file mode 100644 index 0000000..682c05e --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/TraceSnSubScan.cs @@ -0,0 +1,58 @@ + +namespace LineTraceabilitySystem.Models.trace +{ + /// + /// 追溯扫码,扫子零件,绑定sn标签(模板,实际使用需要按时间分表) + /// + [SugarTable("trace_sn_sub_scan")] + public class TraceSnSubScan + { + /// + /// 追溯扫码标签内容 + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 子件编号 + /// + [SugarColumn(ColumnName = "sub_code")] + public string SubCode { get; set; } + + /// + /// 子件名称 + /// + [SugarColumn(ColumnName = "sub_name")] + public string SubName { get; set; } + + /// + /// 规格 + /// + public string Specification { get; set; } + + /// + /// 追溯码SN + /// + [SugarColumn(ColumnName = "part_sn")] + public string PartSn { get; set; } + + /// + /// 零件子件 + /// + [SugarColumn(ColumnName = "sub_part_scan_code")] + public string SubPartScanCode { get; set; } + + /// + /// 是否返工 1-是 0-正常 + /// + [SugarColumn(ColumnName = "is_back")] + public int? IsBack { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "create_time")] + public DateTime? CreateTime { get; set; } + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Models/trace/TraceSnTransit.cs b/src/LineTraceabilitySystem/Models/trace/TraceSnTransit.cs new file mode 100644 index 0000000..d2f6cdc --- /dev/null +++ b/src/LineTraceabilitySystem/Models/trace/TraceSnTransit.cs @@ -0,0 +1,41 @@ + +namespace LineTraceabilitySystem.Models.trace +{ + /// + /// 追溯码过站记录 + /// + [SugarTable("trace_sn_transit")] + public class TraceSnTransit + { + /// + /// 追溯扫码标签内容 + /// + [SugarColumn(IsPrimaryKey = true, IsIdentity = true)] + public long Id { get; set; } + + /// + /// 追溯码SN + /// + [SugarColumn(ColumnName = "part_sn")] + public string PartSn { get; set; } + + /// + /// 过站名称 + /// + [SugarColumn(ColumnName = "transit_name")] + public string TransitName { get; set; } + + /// + /// 是否返工 1-返工 0-正常 + /// + [SugarColumn(ColumnName = "is_back")] + public int? IsBack { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "create_time")] + public DateTime? CreateTime { get; set; } + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Services/AlertService.cs b/src/LineTraceabilitySystem/Services/AlertService.cs new file mode 100644 index 0000000..2553481 --- /dev/null +++ b/src/LineTraceabilitySystem/Services/AlertService.cs @@ -0,0 +1,106 @@ +using System.Media; +using System.Windows; +using System.Reflection; +using System.IO; +using LineTraceabilitySystem.Constants; +using LineTraceabilitySystem.Views; + +namespace LineTraceabilitySystem.Services +{ + /// + /// 提醒服务的具体实现 + /// + public class AlertService : IAlertService + { + private readonly MainWindow _mainWindow; + + public AlertService() + { + // 获取主窗口实例 + _mainWindow = Application.Current.MainWindow as MainWindow; + } + + public void ShowInfo(string message, string title = "提示") + { + _mainWindow?.Dispatcher.Invoke(() => + { + System.Windows.MessageBox.Show(_mainWindow, message, title, MessageBoxButton.OK, MessageBoxImage.Information); + }); + } + + public void ShowWarning(string message, string title = "警告") + { + _mainWindow?.Dispatcher.Invoke(() => + { + System.Windows.MessageBox.Show(_mainWindow, message, title, MessageBoxButton.OK, MessageBoxImage.Warning); + PlaySound(SoundType.Warning); + }); + } + + public void ShowError(string message, string title = "错误") + { + _mainWindow?.Dispatcher.Invoke(() => + { + System.Windows.MessageBox.Show(_mainWindow, message, title, MessageBoxButton.OK, MessageBoxImage.Error); + PlaySound(SoundType.Error); + }); + } + + public bool ShowConfirmation(string message, string title = "确认") + { + bool result = false; + _mainWindow?.Dispatcher.Invoke(() => + { + var dialogResult = System.Windows.MessageBox.Show(_mainWindow, message, title, MessageBoxButton.YesNo, MessageBoxImage.Question); + result = dialogResult == MessageBoxResult.Yes; + }); + return result; + } + + public DialogResultType ShowQuestion(string message, string title = "问题") + { + DialogResultType result = DialogResultType.None; + _mainWindow?.Dispatcher.Invoke(() => + { + System.Windows.MessageBoxResult wpfResult = System.Windows.MessageBox.Show(_mainWindow, message, title, MessageBoxButton.YesNo, MessageBoxImage.Question); + result = wpfResult == System.Windows.MessageBoxResult.Yes ? DialogResultType.Yes : DialogResultType.No; + }); + return result; + } + + public void PlaySound(SoundType soundType = SoundType.Default) + { + try + { + // 根据声音类型播放不同的系统声音 + switch (soundType) + { + case SoundType.Success: + SystemSounds.Asterisk.Play(); + break; + case SoundType.Warning: + SystemSounds.Exclamation.Play(); + break; + case SoundType.Error: + SystemSounds.Hand.Play(); + break; + case SoundType.Info: + SystemSounds.Beep.Play(); + break; + default: + SystemSounds.Beep.Play(); + break; + } + } + catch { } + } + + public void ShowUnfinishedOperationAlert() + { + _mainWindow?.Dispatcher.Invoke(() => + { + ShowWarning("您有未完成的扫码操作,请继续完成!", "未完成操作提醒"); + }); + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Services/IAlertService.cs b/src/LineTraceabilitySystem/Services/IAlertService.cs new file mode 100644 index 0000000..e9aa32e --- /dev/null +++ b/src/LineTraceabilitySystem/Services/IAlertService.cs @@ -0,0 +1,70 @@ +using LineTraceabilitySystem.Constants; + +namespace LineTraceabilitySystem.Services +{ + /// + /// 提醒服务接口 + /// + public interface IAlertService + { + /// + /// 显示信息弹窗 + /// + /// 消息内容 + /// 标题 + void ShowInfo(string message, string title = "提示"); + + /// + /// 显示警告弹窗 + /// + /// 警告内容 + /// 标题 + void ShowWarning(string message, string title = "警告"); + + /// + /// 显示错误弹窗 + /// + /// 错误内容 + /// 标题 + void ShowError(string message, string title = "错误"); + + /// + /// 显示确认对话框 + /// + /// 确认内容 + /// 标题 + /// 用户是否确认 + bool ShowConfirmation(string message, string title = "确认"); + + /// + /// 显示问题对话框 + /// + /// 问题内容 + /// 标题 + /// 用户的选择结果 + DialogResultType ShowQuestion(string message, string title = "问题"); + + /// + /// 播放声音提醒 + /// + /// 声音类型 + void PlaySound(SoundType soundType = SoundType.Default); + + /// + /// 显示未完成操作的提醒 + /// + void ShowUnfinishedOperationAlert(); + } + + /// + /// 声音类型枚举 + /// + public enum SoundType + { + Default, + Success, + Warning, + Error, + Info + } +} diff --git a/src/LineTraceabilitySystem/Services/IS7PlcService.cs b/src/LineTraceabilitySystem/Services/IS7PlcService.cs new file mode 100644 index 0000000..79d9dad --- /dev/null +++ b/src/LineTraceabilitySystem/Services/IS7PlcService.cs @@ -0,0 +1,152 @@ +using System; +using System.Threading.Tasks; +using S7.Net; +using LineTraceabilitySystem.Events; + +namespace LineTraceabilitySystem.Services +{ + /// + /// 西门子PLC S7协议通信服务接口 + /// + public interface IS7PlcService + { + /// + /// 日志事件 + /// + event EventHandler LogGenerated; + + /// + /// 连接到PLC + /// + /// PLC的IP地址 + /// 机架号 + /// 槽号 + /// CPU类型 + /// 是否连接成功 + Task ConnectAsync(string ipAddress, int rack, int slot, CpuType cpuType); + + /// + /// 断开PLC连接 + /// + void Disconnect(); + + /// + /// 读取PLC中的位数据 + /// + /// 数据块号 + /// 起始字节地址 + /// 位地址 + /// 位值 + Task ReadBitAsync(int dbNumber, int startByteAdr, int bitAdr); + + /// + /// 写入位数据到PLC + /// + /// 数据块号 + /// 起始字节地址 + /// 位地址 + /// 要写入的值 + /// 是否写入成功 + Task WriteBitAsync(int dbNumber, int startByteAdr, int bitAdr, bool value); + + /// + /// 读取PLC中的字节数据 + /// + /// 数据块号 + /// 起始字节地址 + /// 字节值 + Task ReadByteAsync(int dbNumber, int startByteAdr); + + /// + /// 写入字节数据到PLC + /// + /// 数据块号 + /// 起始字节地址 + /// 要写入的值 + /// 是否写入成功 + Task WriteByteAsync(int dbNumber, int startByteAdr, byte value); + + /// + /// 读取PLC中的整数数据 + /// + /// 数据块号 + /// 起始字节地址 + /// 整数值 + Task ReadInt16Async(int dbNumber, int startByteAdr); + Task ReadInt16Async(string Address); + + /// + /// 写入整数数据到PLC + /// + /// 数据块号 + /// 起始字节地址 + /// 要写入的值 + /// 是否写入成功 + Task WriteInt16Async(int dbNumber, int startByteAdr, short value); + + /// + /// 读取PLC中的双整数数据 + /// + /// 数据块号 + /// 起始字节地址 + /// 双整数值 + Task ReadInt32Async(int dbNumber, int startByteAdr); + + /// + /// 写入双整数数据到PLC + /// + /// 数据块号 + /// 起始字节地址 + /// 要写入的值 + /// 是否写入成功 + Task WriteInt32Async(int dbNumber, int startByteAdr, int value); + + /// + /// 读取PLC中的浮点数数据 + /// + /// 数据块号 + /// 起始字节地址 + /// 浮点数值 + Task ReadFloatAsync(int dbNumber, int startByteAdr); + + /// + /// 写入浮点数数据到PLC + /// + /// 数据块号 + /// 起始字节地址 + /// 要写入的值 + /// 是否写入成功 + Task WriteFloatAsync(int dbNumber, int startByteAdr, float value); + + /// + /// 读取PLC中的字符串数据 + /// + /// 数据块号 + /// 起始字节地址 + /// 字符串值 + Task ReadStringAsync(int dbNumber, int startByteAdr); + + /// + /// 写入字符串数据到PLC + /// + /// 数据块号 + /// 起始字节地址 + /// 要写入的字符串 + /// 是否写入成功 + Task WriteStringAsync(int dbNumber, int startByteAdr, string value); + /// + /// 写入字符串数据到VD + /// + public Task WriteVaribleAsync(string varible, int value); + + /// + /// PLC连接状态 + /// + bool IsConnected { get; } + + /// + /// 连接状态改变事件 + /// + event EventHandler ConnectionStatusChanged; + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Services/IScanService.cs b/src/LineTraceabilitySystem/Services/IScanService.cs new file mode 100644 index 0000000..44f82e5 --- /dev/null +++ b/src/LineTraceabilitySystem/Services/IScanService.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; + +namespace LineTraceabilitySystem.Services +{ + /// + /// 扫描服务接口 + /// + public interface IScanService + { + /// + /// 验证追溯码 + /// + /// 追溯码 + /// 验证结果 + Task ValidateTraceabilityCodeAsync(string code); + /// + /// 同步验证追溯码 + /// + /// 追溯码 + /// 验证结果 + bool ValidateTraceabilityCode(string code); + + /// + /// 同步验证子零件码 + /// + /// 追溯码 + /// 验证结果 + bool ValidateSubPartCode(string code); + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Services/ISerialPortService.cs b/src/LineTraceabilitySystem/Services/ISerialPortService.cs new file mode 100644 index 0000000..f925620 --- /dev/null +++ b/src/LineTraceabilitySystem/Services/ISerialPortService.cs @@ -0,0 +1,114 @@ +using System.IO.Ports; + +namespace LineTraceabilitySystem.Services +{ + /// + /// 串口服务接口 + /// + public interface ISerialPortService + { + #region 属性 + + /// + /// 串口名称 + /// + string PortName { get; set; } + + /// + /// 波特率 + /// + int BaudRate { get; set; } + + /// + /// 数据位 + /// + int DataBits { get; set; } + + /// + /// 停止位 + /// + StopBits StopBits { get; set; } + + /// + /// 校验位 + /// + Parity Parity { get; set; } + + /// + /// 握手协议 + /// + System.IO.Ports.Handshake Handshake { get; set; } + + /// + /// 读取超时 + /// + int ReadTimeout { get; set; } + + /// + /// 写入超时 + /// + int WriteTimeout { get; set; } + + /// + /// 串口是否打开 + /// + bool IsOpen { get; } + + #endregion + + #region 方法 + + /// + /// 打开串口 + /// + /// 是否打开成功 + bool Open(); + + /// + /// 关闭串口 + /// + void Close(); + + /// + /// 读取串口数据 + /// + /// 读取的数据 + string Read(); + + /// + /// 写入串口数据 + /// + /// 要写入的数据 + /// 是否写入成功 + bool Write(string data); + + /// + /// 获取可用串口列表 + /// + /// 可用串口名称列表 + List GetAvailablePorts(); + + #endregion + + #region 事件 + + /// + /// 数据接收事件 + /// + event EventHandler DataReceived; + + /// + /// 错误事件 + /// + event EventHandler Error; + + /// + /// 状态变化事件 + /// + event EventHandler StatusChanged; + + #endregion + } + + +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Services/ITraceMainService.cs b/src/LineTraceabilitySystem/Services/ITraceMainService.cs new file mode 100644 index 0000000..449c6d9 --- /dev/null +++ b/src/LineTraceabilitySystem/Services/ITraceMainService.cs @@ -0,0 +1,71 @@ +using LineTraceabilitySystem.Models.trace; +using LineTraceabilitySystem.Models.trace.Dto; + +namespace LineTraceabilitySystem.Services +{ + /// + /// 追溯码主服务接口 + /// + public interface ITraceMainService + { + /// + /// 获取配置 + /// + /// 配置获取参数 + /// 配置结果 + Task GetConfigAsync(DeviceDownLoadTraceConfigQueryDto parm); + + /// + /// 检查追溯码是否存在 + /// + /// 追溯码 + /// 是否存在 + Task CheckTraceCodeExistsAsync(string traceCode); + + /// + /// 获取最后一个追溯码 + /// + /// 项目号 + /// 零件号 + /// 最后一个追溯码 + Task GetLastTraceCodeAsync(string projectCode, string productionCode); + + /// + /// 添加追溯码 + /// + /// 项目号 + /// 零件号 + /// 追溯码 + /// 添加结果 + Task AddTraceCodeAsync(string projectCode, string productionCode, string traceCode); + + /// + /// 上传追溯码与检测结果 + /// + /// 检测记录参数 + /// 上传结果 + Task UploadTraceCodeResultAsync(TraceSnQcRecord parm); + + /// + /// 添加追溯码过站信息 + /// + /// 过站信息参数 + /// 添加结果 + Task AddTraceSnTransitAsync(TraceSnTransit parm); + + /// + /// 检查追溯码是否过站 + /// + /// 追溯码 + /// 过站名称 + /// 是否过站 + Task CheckIsTransitAsync(string traceCode, string transitName); + + /// + /// 添加子零件追溯码与产品二维码绑定记录 + /// + /// 绑定记录参数 + /// 添加结果 + Task AddPartSnSubScanAsync(TraceSnSubScan scan); + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Services/S7PlcService.cs b/src/LineTraceabilitySystem/Services/S7PlcService.cs new file mode 100644 index 0000000..01173e4 --- /dev/null +++ b/src/LineTraceabilitySystem/Services/S7PlcService.cs @@ -0,0 +1,600 @@ +using System; +using System.Threading.Tasks; +using LineTraceabilitySystem.Events; +using S7.Net; + +namespace LineTraceabilitySystem.Services +{ + /// + /// 西门子PLC S7协议通信服务实现 + /// + public class S7PlcService : IS7PlcService, IDisposable + { + private Plc _plc; + + /// + /// 日志事件 + /// + public event EventHandler LogGenerated; + + /// + /// 触发日志事件 + /// + /// 日志消息 + /// 日志级别 + private void OnLogGenerated(string message, LogLevel level) + { + LogGenerated?.Invoke(this, new PlcLogEventArgs(message, level)); + } + + private bool _isConnected; + + /// + /// 构造函数 + /// + /// 日志记录器 + public S7PlcService() + { + _isConnected = false; + } + + /// + /// 连接到PLC + /// + /// PLC的IP地址 + /// 机架号 + /// 槽号 + /// CPU类型 + /// 是否连接成功 + public async Task ConnectAsync(string ipAddress, int rack, int slot, CpuType cpuType) + { + // 确保在UI线程外执行连接操作 + return await Task.Run(() => + { + try + { + if (_plc != null && _isConnected) + { + Disconnect(); + } + + _plc = new Plc(cpuType, ipAddress, (short)rack, (short)slot); + _plc.Open(); + + _isConnected = _plc.IsConnected; + // 触发状态变化事件 + OnConnectionStatusChanged(_isConnected); + + if (_isConnected) + { + OnLogGenerated( + $"成功连接到PLC: {ipAddress}, 机架: {rack}, 槽: {slot}", + LogLevel.Info + ); + } + else + { + OnLogGenerated( + $"连接PLC失败: {ipAddress}, 机架: {rack}, 槽: {slot}", + LogLevel.Warning + ); + } + + return _isConnected; + } + catch (PlcException ex) + { + // 专门处理PLC连接异常,仅记录为警告而不是错误 + OnLogGenerated($"PLC连接警告: {ex.Message}", LogLevel.Warning); + _isConnected = false; + OnConnectionStatusChanged(_isConnected); + return false; + } + catch (Exception ex) + { + // 处理其他异常 + OnLogGenerated($"连接PLC时发生异常: {ex.Message}", LogLevel.Exception); + _isConnected = false; + OnConnectionStatusChanged(_isConnected); + return false; + } + }); + } + + /// + /// 断开PLC连接 + /// + public void Disconnect() + { + try + { + if (_plc != null && _isConnected) + { + _plc.Close(); + _isConnected = false; + OnConnectionStatusChanged(_isConnected); + OnLogGenerated("已断开PLC连接", LogLevel.Info); + } + } + catch (Exception ex) + { + OnLogGenerated($"断开PLC连接时发生异常: {ex.Message}", LogLevel.Exception); + } + } + + /// + /// 读取PLC中的位数据 + /// + /// 数据块号 + /// 起始字节地址 + /// 位地址 + /// 位值 + public async Task ReadBitAsync(int dbNumber, int startByteAdr, int bitAdr) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("读取位数据失败: 未连接到PLC", LogLevel.Error); + return false; + } + + return await Task.Run(() => + { + return (bool)_plc.Read($"DB{dbNumber}.DBX{startByteAdr}.{bitAdr}"); + }); + } + catch (Exception ex) + { + OnLogGenerated($"读取位数据时发生异常: {ex.Message}", LogLevel.Exception); + return false; + } + } + + /// + /// 写入位数据到PLC + /// + /// 数据块号 + /// 起始字节地址 + /// 位地址 + /// 要写入的值 + /// 是否写入成功 + public async Task WriteBitAsync( + int dbNumber, + int startByteAdr, + int bitAdr, + bool value + ) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("写入位数据失败: 未连接到PLC", LogLevel.Error); + return false; + } + + await Task.Run(() => + { + _plc.Write($"DB{dbNumber}.DBX{startByteAdr}.{bitAdr}", value); + }); + + OnLogGenerated( + $"成功写入位数据到DB{dbNumber}.DBX{startByteAdr}.{bitAdr}: {value}", + LogLevel.Info + ); + return true; + } + catch (Exception ex) + { + OnLogGenerated($"写入位数据时发生异常: {ex.Message}", LogLevel.Exception); + return false; + } + } + + /// + /// 读取PLC中的字节数据 + /// + /// 数据块号 + /// 起始字节地址 + /// 字节值 + public async Task ReadByteAsync(int dbNumber, int startByteAdr) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("读取字节数据失败: 未连接到PLC", LogLevel.Error); + return 0; + } + + return await Task.Run(() => + { + return (byte)_plc.Read($"DB{dbNumber}.DBB{startByteAdr}"); + }); + } + catch (Exception ex) + { + OnLogGenerated($"读取字节数据时发生异常: {ex.Message}", LogLevel.Exception); + return 0; + } + } + + /// + /// 写入字节数据到PLC + /// + /// 数据块号 + /// 起始字节地址 + /// 要写入的值 + /// 是否写入成功 + public async Task WriteByteAsync(int dbNumber, int startByteAdr, byte value) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("写入字节数据失败: 未连接到PLC", LogLevel.Error); + return false; + } + + await Task.Run(() => + { + // 使用正确的地址格式写入字符串 + _plc.Write($"DB{dbNumber}.STRING{startByteAdr}", value); + }); + + OnLogGenerated($"成功写入字节数据到DB{dbNumber}.DBB{startByteAdr}: {value}", LogLevel.Info); + return true; + } + catch (Exception ex) + { + OnLogGenerated($"写入字节数据时发生异常: {ex.Message}", LogLevel.Exception); + return false; + } + } + + /// + /// 读取PLC中的整数数据 + /// + /// 数据块号 + /// 起始字节地址 + /// 整数值 + public async Task ReadInt16Async(int dbNumber, int startByteAdr) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("读取整数数据失败: 未连接到PLC", LogLevel.Error); + return 0; + } + + return await Task.Run(() => + { + return (short)_plc.Read($"DB{dbNumber}.DBW{startByteAdr}"); + }); + } + catch (Exception ex) + { + OnLogGenerated($"读取整数数据时发生异常: {ex.Message}", LogLevel.Exception); + return 0; + } + } + public async Task ReadInt16Async(string Address) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("读取整数数据失败: 未连接到PLC", LogLevel.Error); + return 0; + } + + return await Task.Run(() => + { + return (short)_plc.Read(Address); + }); + } + catch (Exception ex) + { + OnLogGenerated($"读取整数数据时发生异常: {ex.Message}", LogLevel.Exception); + return 0; + } + } + + /// + /// 写入整数数据到PLC + /// + /// 数据块号 + /// 起始字节地址 + /// 要写入的值 + /// 是否写入成功 + public async Task WriteInt16Async(int dbNumber, int startByteAdr, short value) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("写入整数数据失败: 未连接到PLC", LogLevel.Error); + return false; + } + + await Task.Run(() => + { + _plc.Write($"DB{dbNumber}.DBW{startByteAdr}", value); + }); + + OnLogGenerated($"成功写入整数数据到DB{dbNumber}.DBW{startByteAdr}: {value}", LogLevel.Info); + return true; + } + catch (Exception ex) + { + OnLogGenerated($"写入整数数据时发生异常: {ex.Message}", LogLevel.Exception); + return false; + } + } + + /// + /// 读取PLC中的双整数数据 + /// + /// 数据块号 + /// 起始字节地址 + /// 双整数值 + public async Task ReadInt32Async(int dbNumber, int startByteAdr) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("读取双整数数据失败: 未连接到PLC", LogLevel.Error); + return 0; + } + + return await Task.Run(() => + { + return (int)_plc.Read($"DB{dbNumber}.DBD{startByteAdr}"); + }); + } + catch (Exception ex) + { + OnLogGenerated($"读取双整数数据时发生异常: {ex.Message}", LogLevel.Exception); + return 0; + } + } + + /// + /// 写入双整数数据到PLC + /// + /// 数据块号 + /// 起始字节地址 + /// 要写入的值 + /// 是否写入成功 + public async Task WriteInt32Async(int dbNumber, int startByteAdr, int value) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("写入双整数数据失败: 未连接到PLC", LogLevel.Error); + return false; + } + + await Task.Run(() => + { + _plc.Write($"DB{dbNumber}.DBD{startByteAdr}", value); + }); + + OnLogGenerated($"成功写入双整数数据到DB{dbNumber}.DBD{startByteAdr}: {value}", LogLevel.Info); + return true; + } + catch (Exception ex) + { + OnLogGenerated($"写入双整数数据时发生异常: {ex.Message}", LogLevel.Exception); + return false; + } + } + + /// + /// 读取PLC中的浮点数数据 + /// + /// 数据块号 + /// 起始字节地址 + /// 浮点数值 + public async Task ReadFloatAsync(int dbNumber, int startByteAdr) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("读取浮点数数据失败: 未连接到PLC", LogLevel.Error); + return 0; + } + + return await Task.Run(() => + { + return (float)_plc.Read($"DB{dbNumber}.DBD{startByteAdr}"); + }); + } + catch (Exception ex) + { + OnLogGenerated($"读取浮点数数据时发生异常: {ex.Message}", LogLevel.Exception); + return 0; + } + } + + /// + /// 写入浮点数数据到PLC + /// + /// 数据块号 + /// 起始字节地址 + /// 要写入的值 + /// 是否写入成功 + public async Task WriteFloatAsync(int dbNumber, int startByteAdr, float value) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("写入浮点数数据失败: 未连接到PLC", LogLevel.Error); + return false; + } + + await Task.Run(() => + { + _plc.Write($"DB{dbNumber}.DBD{startByteAdr}", value); + }); + + OnLogGenerated($"成功写入浮点数数据到DB{dbNumber}.DBD{startByteAdr}: {value}", LogLevel.Info); + return true; + } + catch (Exception ex) + { + OnLogGenerated($"写入浮点数数据时发生异常: {ex.Message}", LogLevel.Exception); + return false; + } + } + + /// + /// 读取PLC中的字符串数据 + /// + /// 数据块号 + /// 起始字节地址 + /// 字符串值 + public async Task ReadStringAsync(int dbNumber, int startByteAdr) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("读取字符串数据失败: 未连接到PLC", LogLevel.Error); + return string.Empty; + } + + return await Task.Run(() => + { + // 使用正确的地址格式读取字符串 + return (string)_plc.Read($"DB{dbNumber}.STRING{startByteAdr}"); + }); + } + catch (Exception ex) + { + OnLogGenerated($"读取字符串数据时发生异常: {ex.Message}", LogLevel.Exception); + return string.Empty; + } + } + + /// + /// 写入字符串数据到PLC + /// + /// 数据块号 + /// 起始字节地址 + /// 要写入的字符串 + /// 是否写入成功 + public async Task WriteStringAsync(int dbNumber, int startByteAdr, string value) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("写入字符串数据失败: 未连接到PLC", LogLevel.Error); + return false; + } + + await Task.Run(() => + { + _plc.Write($"DB{dbNumber}.DBB{startByteAdr}", value); + }); + + OnLogGenerated($"成功写入字符串数据到DB{dbNumber}.DBB{startByteAdr}: {value}", LogLevel.Info); + return true; + } + catch (Exception ex) + { + OnLogGenerated($"写入字符串数据时发生异常: {ex.Message}", LogLevel.Exception); + return false; + } + } + + /// + /// 写入双整数数据到PLC的VD存储区 + /// + /// VD地址,例如880表示VD880 + /// 要写入的值 + /// 是否写入成功 + public async Task WriteVaribleAsync(string varible, int value) + { + try + { + if (!_isConnected || _plc == null) + { + OnLogGenerated("写入VD数据失败: 未连接到PLC", LogLevel.Error); + return false; + } + + await Task.Run(() => + { + _plc.Write($"VD{varible}", value); + }); + + OnLogGenerated($"成功写入双整数数据到{varible}: {value}", LogLevel.Info); + return true; + } + catch (Exception ex) + { + OnLogGenerated($"写入双整数数据时发生异常: {ex.Message}", LogLevel.Exception); + return false; + } + } + + /// + /// PLC连接状态 + /// + public bool IsConnected => _isConnected; + + /// + /// 连接状态改变事件 + /// + public event EventHandler ConnectionStatusChanged; + + /// + /// 触发连接状态改变事件 + /// + /// 当前连接状态 + protected virtual void OnConnectionStatusChanged(bool isConnected) + { + ConnectionStatusChanged?.Invoke(this, isConnected); + } + + /// + /// 释放资源 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 释放资源 + /// + /// 是否由用户代码调用 + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // 释放托管资源 + Disconnect(); + } + // 释放非托管资源 + _plc = null; + } + + /// + /// 析构函数 + /// + ~S7PlcService() + { + Dispose(false); + } + } +} diff --git a/src/LineTraceabilitySystem/Services/ScanService.cs b/src/LineTraceabilitySystem/Services/ScanService.cs new file mode 100644 index 0000000..1d9556b --- /dev/null +++ b/src/LineTraceabilitySystem/Services/ScanService.cs @@ -0,0 +1,78 @@ +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using System.Diagnostics; +using Newtonsoft.Json; +using LineTraceabilitySystem.Models; +using LineTraceabilitySystem.Helpers; +using LineTraceabilitySystem.Models.trace; + +namespace LineTraceabilitySystem.Services +{ + /// + /// 扫描服务的具体实现 + /// + public class ScanService : IScanService + { + private readonly HttpClient _httpClient; + private readonly string _baseUrl; + + public ScanService() + { + // 初始化HTTP客户端 + _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + _httpClient.Timeout = TimeSpan.FromSeconds(30); + + // 服务器基础URL + _baseUrl = "http://localhost:8080/api"; + } + + public bool ValidateSubPartCode(string code) + { + return true; + } + + public bool ValidateTraceabilityCode(string code) + { + if (code.StartsWith("A")) + { + return true; + /* var db = SqlSugarHelper.GetInstance(); + bool hasRecord = db.Queryable() + .Where(t => t.PartSn == code) + .Where(t => t.TransitName == "OP10") + .Any(); + return hasRecord;*/ + } + else + { + return false; + } + } + + public async Task ValidateTraceabilityCodeAsync(string code) + { + try + { + // 这里实现追溯码验证逻辑 + // 示例:调用服务器API验证追溯码 + + + + var response = await _httpClient.GetAsync($"{_baseUrl}/traceability/validate/{code}"); + if (response.IsSuccessStatusCode) + { + var result = await response.Content.ReadAsStringAsync(); + return bool.Parse(result); + } + return false; + } + catch (Exception ex) + { + Debug.WriteLine($"验证追溯码失败: {ex.Message}"); + return false; + } + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Services/SerialPortService.cs b/src/LineTraceabilitySystem/Services/SerialPortService.cs new file mode 100644 index 0000000..3838180 --- /dev/null +++ b/src/LineTraceabilitySystem/Services/SerialPortService.cs @@ -0,0 +1,303 @@ +using LineTraceabilitySystem.Constants; +using System.Diagnostics; +using System.IO.Ports; +using System.Text; + +namespace LineTraceabilitySystem.Services +{ + /// + /// 串口服务的具体实现 + /// + public class SerialPortService : ISerialPortService, IDisposable + { + private SerialPort? _serialPort; + private readonly object _lockObject = new object(); + private StringBuilder _receivedDataBuffer = new StringBuilder(); + private bool _disposed = false; + + #region 构造函数 + + public SerialPortService() + { + // 设置默认参数 + PortName = AppConstants.SerialPort.DefaultPortName; + BaudRate = AppConstants.SerialPort.DefaultBaudRate; + DataBits = AppConstants.SerialPort.DefaultDataBits; + StopBits = AppConstants.SerialPort.DefaultStopBits; + Parity = AppConstants.SerialPort.DefaultParity; + Handshake = AppConstants.SerialPort.DefaultHandshake ? System.IO.Ports.Handshake.RequestToSend : System.IO.Ports.Handshake.None; + ReadTimeout = AppConstants.SerialPort.DefaultReadTimeout; + WriteTimeout = AppConstants.SerialPort.DefaultWriteTimeout; + } + + #endregion + + #region ISerialPortService 实现 + + public string PortName { get; set; } + public int BaudRate { get; set; } + public int DataBits { get; set; } + public StopBits StopBits { get; set; } + public Parity Parity { get; set; } + public System.IO.Ports.Handshake Handshake { get; set; } + public int ReadTimeout { get; set; } + public int WriteTimeout { get; set; } + + public bool IsOpen + { + get { return _serialPort != null && _serialPort.IsOpen; } + } + + public bool Open() + { + try + { + Close(); // 先关闭已打开的串口 + + _serialPort = new SerialPort + { + PortName = PortName, + BaudRate = BaudRate, + DataBits = DataBits, + StopBits = StopBits, + Parity = Parity, + Handshake = Handshake, + ReadTimeout = ReadTimeout, + WriteTimeout = WriteTimeout, + NewLine = AppConstants.SerialPort.NewLine + }; + + _serialPort.DataReceived += HandleSerialPortDataReceived; + _serialPort.ErrorReceived += SerialPort_ErrorReceived; + _serialPort.Open(); // 触发状态变化事件 + OnStatusChanged(true); + return true; + } + catch (Exception ex) + { + Debug.WriteLine($"打开串口失败: {ex.Message}"); + OnError($"打开串口失败: {ex.Message}", ex); + return false; + } + } + + public void Close() + { + try + { + if (_serialPort != null && _serialPort.IsOpen) + { + _serialPort.DataReceived -= HandleSerialPortDataReceived; + _serialPort.ErrorReceived -= SerialPort_ErrorReceived; + _serialPort.Close(); + _serialPort = null; + + // 触发状态变化事件 + OnStatusChanged(false); + } + } + catch (Exception ex) + { + Debug.WriteLine($"关闭串口失败: {ex.Message}"); + OnError($"关闭串口失败: {ex.Message}", ex); + } + } + + public string Read() + { + lock (_lockObject) + { + if (!IsOpen) + { + return string.Empty; + } + + try + { + // 读取一行数据(直到换行符) + return _serialPort?.ReadLine() ?? string.Empty; + } + catch (TimeoutException) + { + return string.Empty; + } + catch (Exception ex) + { + Debug.WriteLine($"读取串口数据失败: {ex.Message}"); + OnError($"读取串口数据失败: {ex.Message}", ex); + return string.Empty; + } + } + } + + public bool Write(string data) + { + lock (_lockObject) + { + if (!IsOpen || string.IsNullOrEmpty(data)) + { + return false; + } + + try + { + _serialPort?.Write(data); + return true; + } + catch (Exception ex) + { + Debug.WriteLine($"写入串口数据失败: {ex.Message}"); + OnError($"写入串口数据失败: {ex.Message}", ex); + return false; + } + } + } + + public List GetAvailablePorts() + { + try + { + return new List(SerialPort.GetPortNames()); + } + catch (Exception ex) + { + Debug.WriteLine($"获取可用串口失败: {ex.Message}"); + OnError($"获取可用串口失败: {ex.Message}", ex); + return new List(); + } + } + + #endregion + + #region 事件处理 + + private void HandleSerialPortDataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e) + { + if (_serialPort == null || !_serialPort.IsOpen) + { + return; + } + + try + { + // 读取缓冲区中的所有数据 + string data = _serialPort.ReadExisting(); + + // 处理接收到的数据 + _receivedDataBuffer.Append(data); + + // 检查是否包含换行符(扫码枪数据结束标记) + string newLine = AppConstants.SerialPort.NewLine; + int newLineIndex = _receivedDataBuffer.ToString().IndexOf(newLine); + + if (newLineIndex >= 0) + { + // 提取完整的数据行 + string completeData = _receivedDataBuffer.ToString(0, newLineIndex); + _receivedDataBuffer.Remove(0, newLineIndex + newLine.Length); + + // 清理数据(去除首尾空白字符) + completeData = completeData.Trim(); + + // 触发数据接收事件 + OnDataReceived(completeData); + } + } + catch (Exception ex) + { + Debug.WriteLine($"处理串口数据失败: {ex.Message}"); + OnError($"处理串口数据失败: {ex.Message}", ex); + } + } + + private void SerialPort_ErrorReceived(object sender, System.IO.Ports.SerialErrorReceivedEventArgs e) + { + string errorMessage = "串口错误: " + e.EventType.ToString(); + Debug.WriteLine(errorMessage); + OnError(errorMessage); + } + + #endregion + + #region 事件触发 + + /// + /// 触发数据接收事件 + /// + /// 接收到的数据 + protected virtual void OnDataReceived(string data) + { + DataReceived?.Invoke(this, new LineTraceabilitySystem.Events.SerialDataReceivedEventArgs(data)); + } + + /// + /// 触发错误事件 + /// + /// 错误消息 + /// 异常对象 + protected virtual void OnError(string errorMessage, Exception? exception = null) + { + Error?.Invoke(this, new LineTraceabilitySystem.Events.SerialErrorEventArgs(errorMessage, exception)); + } + + /// + /// 触发状态变化事件 + /// + /// 是否打开 + protected virtual void OnStatusChanged(bool isOpen) + { + StatusChanged?.Invoke(this, new LineTraceabilitySystem.Events.SerialStatusChangedEventArgs(isOpen)); + } + + #endregion + + #region 事件 + + public event EventHandler? DataReceived; + public event EventHandler? Error; + public event EventHandler? StatusChanged; + + #endregion + + #region IDisposable 实现 + + /// + /// 释放资源 + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// 释放资源 + /// + /// 是否由用户代码调用 + protected virtual void Dispose(bool disposing) + { + if (!_disposed) + { + if (disposing) + { + // 释放托管资源 + Close(); // 关闭串口并取消事件订阅 + } + + // 释放非托管资源 + _serialPort = null; + _disposed = true; + } + } + + /// + /// 析构函数 + /// + ~SerialPortService() + { + Dispose(false); + } + + #endregion + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Services/TraceMainService.cs b/src/LineTraceabilitySystem/Services/TraceMainService.cs new file mode 100644 index 0000000..b0ed4e1 --- /dev/null +++ b/src/LineTraceabilitySystem/Services/TraceMainService.cs @@ -0,0 +1,192 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading.Tasks; +using System.Diagnostics; +using System.Text; +using Newtonsoft.Json; +using LineTraceabilitySystem.Constants; +using LineTraceabilitySystem.Models; +using LineTraceabilitySystem.Models.trace; +using LineTraceabilitySystem.Models.trace.Dto; + +namespace LineTraceabilitySystem.Services +{ + /// + /// 追溯码主服务的具体实现 + /// + public class TraceMainService : ITraceMainService + { + private readonly HttpClient _httpClient; + private readonly string _baseUrl; + + public TraceMainService() + { + // 初始化HTTP客户端 + _httpClient = new HttpClient(); + _httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + _httpClient.Timeout = TimeSpan.FromSeconds(30); + + // 服务器基础URL + _baseUrl = AppConstants.BaseUrl + "/mes/trace/traceMain/v1"; + } + + /// + /// 处理API响应 + /// + /// 返回数据类型 + /// HTTP响应 + /// 处理后的结果 + private async Task HandleApiResponse(HttpResponseMessage response) + { + if (response.IsSuccessStatusCode) + { + var result = await response.Content.ReadAsStringAsync(); + var apiResult = JsonConvert.DeserializeObject>(result); + + if (apiResult != null && apiResult.code == 200) + { + return apiResult.data; + } + else + { + Debug.WriteLine(string.Format("API返回错误: {0}", apiResult?.msg ?? "未知错误")); + return default; + } + } + else + { + Debug.WriteLine(string.Format("HTTP错误: {0}", response.StatusCode)); + return default; + } + } + + /// + /// API响应模型 + /// + private class ApiResponse + { + public int code { get; set; } + public T? data { get; set; } + public string? msg { get; set; } + } + + public async Task GetConfigAsync(DeviceDownLoadTraceConfigQueryDto parm) + { + try + { + var content = new StringContent(JsonConvert.SerializeObject(parm), Encoding.UTF8, "application/json"); + var response = await _httpClient.PostAsync($"{_baseUrl}/GetConfig", content); + return await HandleApiResponse(response); + } + catch (Exception ex) + { + Debug.WriteLine($"获取配置失败: {ex.Message}"); + return null; + } + } + + public async Task CheckTraceCodeExistsAsync(string traceCode) + { + try + { + var response = await _httpClient.GetAsync($"{_baseUrl}/CheckPartSnIsExist?partSn={traceCode}"); + return await HandleApiResponse(response); + } + catch (Exception ex) + { + Debug.WriteLine($"检查追溯码存在性失败: {ex.Message}"); + return false; + } + } + + public async Task GetLastTraceCodeAsync(string projectCode, string productionCode) + { + try + { + var response = await _httpClient.GetAsync($"{_baseUrl}/GetLastPartSn?projectCode={projectCode}&productionCode={productionCode}"); + return await HandleApiResponse(response) ?? string.Empty; + } + catch (Exception ex) + { + Debug.WriteLine($"获取最后一个追溯码失败: {ex.Message}"); + return string.Empty; + } + } + + public async Task AddTraceCodeAsync(string projectCode, string productionCode, string traceCode) + { + try + { + var response = await _httpClient.GetAsync($"{_baseUrl}/AddPartSn?projectCode={projectCode}&productionCode={productionCode}&partSn={traceCode}"); + return await HandleApiResponse(response); + } + catch (Exception ex) + { + Debug.WriteLine($"添加追溯码失败: {ex.Message}"); + return false; + } + } + + public async Task UploadTraceCodeResultAsync(TraceSnQcRecord parm) + { + try + { + var content = new StringContent(JsonConvert.SerializeObject(parm), Encoding.UTF8, "application/json"); + var response = await _httpClient.PostAsync($"{_baseUrl}/UploadPartSnAndZdDecord", content); + return await HandleApiResponse(response); + } + catch (Exception ex) + { + Debug.WriteLine($"上传追溯码结果失败: {ex.Message}"); + return false; + } + } + + public async Task AddTraceSnTransitAsync(TraceSnTransit parm) + { + try + { + var content = new StringContent(JsonConvert.SerializeObject(parm), Encoding.UTF8, "application/json"); + var response = await _httpClient.PostAsync($"{_baseUrl}/AddTraceSnTransit", content); + return await HandleApiResponse(response); + } + catch (Exception ex) + { + Debug.WriteLine($"添加追溯码过站信息失败: {ex.Message}"); + return false; + } + } + + public async Task CheckIsTransitAsync(string traceCode, string transitName) + { + try + { + var response = await _httpClient.GetAsync($"{_baseUrl}/CheckIsTransit?partSn={traceCode}&TransitName={transitName}"); + return await HandleApiResponse(response); + } + catch (Exception ex) + { + Debug.WriteLine($"检查追溯码是否过站失败: {ex.Message}"); + return false; + } + } + + public async Task AddPartSnSubScanAsync(TraceSnSubScan scan) + { + try + { + var content = new StringContent(JsonConvert.SerializeObject(scan), Encoding.UTF8, "application/json"); + var response = await _httpClient.PostAsync($"{_baseUrl}/AddPartSnSubScan", content); + return await HandleApiResponse(response); + } + catch (Exception ex) + { + Debug.WriteLine($"添加子零件追溯码与产品二维码绑定记录失败: {ex.Message}"); + return false; + } + } + + + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/ViewModels/HomeViewModel.cs b/src/LineTraceabilitySystem/ViewModels/HomeViewModel.cs new file mode 100644 index 0000000..7afc322 --- /dev/null +++ b/src/LineTraceabilitySystem/ViewModels/HomeViewModel.cs @@ -0,0 +1,265 @@ +using System.Collections.Generic; +using System.Collections.ObjectModel; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using LineTraceabilitySystem.Events; +using LineTraceabilitySystem.Models; +using LineTraceabilitySystem.Models.trace; +using LineTraceabilitySystem.Services; +using Prism.Events; + +namespace LineTraceabilitySystem.ViewModels +{ + public partial class HomeViewModel : ObservableObject + { + private readonly IScanService _scanService; + private readonly ITraceMainService _traceMainService; + private readonly IAlertService _alertService; + private readonly IEventAggregator _eventAggregator; + + [ObservableProperty] + private string title = "首页"; + + [ObservableProperty] + private string traceabilityCode = string.Empty; + + [ObservableProperty] + private string scanResult = string.Empty; + + [ObservableProperty] + private bool isScanOk = false; + + /// + /// 扫码列表 + /// + private List scanList = ["摄像头","扫码1","扫码2"]; + + /// + /// 当前步骤 0-扫追溯码 10-扫第一个码 20-扫第二个码 + /// + [ObservableProperty] + private int currentStep = 0; + + /// + /// 是否返工件 0-否 1-是 + /// + [ObservableProperty] + private int isBack = 0; + + /// + /// 前置校验工序 + /// + private string preStep = "OP10"; + + /// + /// 已扫描的子零件码 + /// + public ObservableCollection ScannedSubCodes { get; set; } = new(); + + public HomeViewModel(IScanService scanService, ITraceMainService traceMainService, + IAlertService alertService, IEventAggregator eventAggregator) + { + _scanService = scanService; + _traceMainService = traceMainService; + _alertService = alertService; + _eventAggregator = eventAggregator; + + // 订阅追溯码扫描事件 + //_eventAggregator.GetEvent().Subscribe(OnTraceabilityCodeScanned); + + // 初始化界面 + Initialize界面(); + } + + /// + /// 初始化界面 + /// + private void Initialize界面() + { + // 从缓存读取当前步骤情况,如果没有读到,默认是0 + // 这里简化处理,直接使用默认值 + CurrentStep = 0; + TraceabilityCode = string.Empty; + ScanResult = string.Empty; + IsScanOk = false; + ScannedSubCodes.Clear(); + } + + /// + /// 处理追溯码扫描事件 + /// + /// 扫描事件参数 + private async void OnTraceabilityCodeScanned(TraceabilityCodeScannedEvent eventArgs) + { + var code = eventArgs.Code; + + switch (CurrentStep) + { + case 0: // 扫描追溯码 + await HandleTraceabilityCodeScanAsync(code); + break; + case 10: // 扫描第一个子零件码 + case 20: // 扫描第二个子零件码 + await HandleSubCodeScanAsync(code); + break; + } + } + + /// + /// 处理追溯码扫描 + /// + /// 扫描的码 + private async Task HandleTraceabilityCodeScanAsync(string code) + { + // 校验是否是追溯码 + bool isValid = await _scanService.ValidateTraceabilityCodeAsync(code); + + if (!isValid) + { + ScanResult = "NG"; + IsScanOk = false; + _alertService.ShowWarning("不是有效的追溯码"); + return; + } + + // 检查追溯码是否存在 + bool exists = await _traceMainService.CheckTraceCodeExistsAsync(code); + + if (!exists) + { + ScanResult = "NG"; + IsScanOk = false; + _alertService.ShowWarning("追溯码不存在"); + return; + } + + // 确认是否是返工件 + bool isBackConfirm = _alertService.ShowConfirmation("该追溯码已存在,是否是返工件?", "确认"); + IsBack = isBackConfirm ? 1 : 0; + + if (!isBackConfirm) + { + // 取消,不赋值追溯码文本框,继续扫追溯码 + ScanResult = "NG"; + IsScanOk = false; + return; + } + + // 检查是否有前置工序的过站记录 + bool hasTransit = await _traceMainService.CheckIsTransitAsync(code, preStep); + + if (!hasTransit) + { + ScanResult = "NG"; + IsScanOk = false; + _alertService.ShowWarning($"追溯码无{preStep}过站记录"); + return; + } + + // 扫码成功 + TraceabilityCode = code; + ScanResult = "OK"; + IsScanOk = true; + CurrentStep = 10; // 进入下一步 + _alertService.ShowInfo("追溯码扫描成功"); + } + + /// + /// 处理子零件码扫描 + /// + /// 扫描的码 + private async Task HandleSubCodeScanAsync(string code) + { + // 简化处理,实际应根据项目需求进行验证 + if (string.IsNullOrWhiteSpace(code)) + { + ScanResult = "NG"; + IsScanOk = false; + _alertService.ShowWarning("扫描的子零件码无效"); + return; + } + + // 添加到已扫描列表 + ScannedSubCodes.Add(code); + ScanResult = "OK"; + IsScanOk = true; + + // 检查是否完成所有扫码 + if (CurrentStep == 20 && ScannedSubCodes.Count >= scanList.Count - 1) + { + // 完成所有扫码 + await CompleteAllScansAsync(); + } + else + { + // 进入下一步 + CurrentStep += 10; + _alertService.ShowInfo("子零件码扫描成功"); + } + } + + /// + /// 完成所有扫码 + /// + private async Task CompleteAllScansAsync() + { + // 添加子零件追溯码与产品二维码绑定记录 + var scanRecord = new TraceSnSubScan + { + + }; + + bool success = await _traceMainService.AddPartSnSubScanAsync(scanRecord); + + if (success) + { + _alertService.ShowInfo("所有码扫描完成,绑定记录添加成功"); + + // 添加追溯码过站信息 + var transitRecord = new TraceSnTransit + { + + TransitName = "当前工序", // 实际应填写当前工序名称 + // 其他必要字段赋值... + }; + + await _traceMainService.AddTraceSnTransitAsync(transitRecord); + } + else + { + _alertService.ShowError("绑定记录添加失败"); + } + + // 重置界面,继续从追溯码扫起 + Initialize界面(); + } + + /// + /// 补打按钮命令 + /// + [RelayCommand] + private void Reprint() + { + if (string.IsNullOrWhiteSpace(TraceabilityCode)) + { + _alertService.ShowWarning("没有可补打的追溯码"); + return; + } + + // 补打逻辑 + _alertService.ShowInfo("补打功能已触发"); + // 实际项目中应调用打印服务 + } + + /// + /// 手动扫码按钮命令 + /// + [RelayCommand] + private void ManualScan() + { + // 打开手动输入对话框 + // 简化处理,实际项目中应实现对话框 + _alertService.ShowInfo("手动扫码功能已触发"); + } + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/ViewModels/MainWindowViewModel.cs b/src/LineTraceabilitySystem/ViewModels/MainWindowViewModel.cs new file mode 100644 index 0000000..7a2ef7b --- /dev/null +++ b/src/LineTraceabilitySystem/ViewModels/MainWindowViewModel.cs @@ -0,0 +1,769 @@ +using LineTraceabilitySystem.Constants; +using LineTraceabilitySystem.Events; +using LineTraceabilitySystem.Helpers; +using LineTraceabilitySystem.Helpers.COMM.PLC; +using LineTraceabilitySystem.Models.trace; +using LineTraceabilitySystem.Services; +using Newtonsoft.Json.Linq; +using Prism.Commands; +using Prism.Events; +using Prism.Mvvm; +using Prism.Navigation; +using S7.Net; +using SqlSugar; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Diagnostics; +using System.IO.Ports; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Threading; + +namespace LineTraceabilitySystem.ViewModels +{ + /// + /// 主窗口视图模型的具体实现 + /// + public class MainWindowViewModel : BindableBase + { + private readonly IScanService _scanService; + private readonly IAlertService _alertService; + private readonly ISerialPortService _serialPortService; + private readonly ITraceMainService _traceMainService; + private readonly IS7PlcService _plcService; + //private readonly IRegionManager _regionManager; + private readonly IEventAggregator _eventAggregator; + private object _currentView; + private string _traceabilityCode = string.Empty; + private string _SubPartScanCode = string.Empty; + private bool _isTraceabilityCodeVerified = false; + private string _traceabilityCodeStatus = "-"; + private Brush _traceabilityCodeStatusColor = Brushes.Gray; + private string _SubPartScanCodeStatus = "-"; + private Brush _SubPartScanCodeStatusColor = Brushes.Gray; + + private string _currentStatus = "就绪"; + private int _productionCount = 0; + private int _currentStep = 0; + private string _scanResult = string.Empty; + private int _isBack = 0; + private bool _isScanOk = false; + private string _preStep = "OP10"; // 前置校验工序 + private string _thisStep = "OP20"; // 本站工序 + private List _scanList = ["追溯", "摄像头"]; // 扫码列表 + const string PLCIP = "192.168.0.30"; + + public DelegateCommand ComboSelectionChanged { get; private set; } + + + + /// + /// 导航命令 + /// + public DelegateCommand NavigateCommand { get; private set; } + + /// + /// 当前视图 + /// + public object CurrentView + { + get => _currentView; + set => SetProperty(ref _currentView, value); + } + + /// + /// 当前步骤 + /// + public int CurrentStep + { + get => _currentStep; + set => SetProperty(ref _currentStep, value); + } + + // 追溯码前缀已移至AppConstants.cs中 + private ObservableCollection _operationHistory = new(); + private string _subCode1 = string.Empty; + + private string _currentStepText = string.Empty; + + /// + /// 操作步骤 + /// + public string CurrentStepText + { + get => _currentStepText; + set => SetProperty(ref _currentStepText, value); + } + + /// + /// 操作历史 + /// + public ObservableCollection OperationHistory + { + get => _operationHistory; + set => SetProperty(ref _operationHistory, value); + } + + /// + /// 追溯码 + /// + public string TraceabilityCode + { + get => _traceabilityCode; + set => SetProperty(ref _traceabilityCode, value); + } + + /// + /// 摄像头码 + /// + public string SubPartScanCode + { + get => _SubPartScanCode; + set => SetProperty(ref _SubPartScanCode, value); + } + + /// + /// 子零件码1 + /// + public string SubCode1 + { + get => _subCode1; + set => SetProperty(ref _subCode1, value); + } + + /// + /// 扫码结果 + /// + public string ScanResult + { + get => _scanResult; + set => SetProperty(ref _scanResult, value); + } + + /// + /// 是否扫码成功 + /// + public bool IsScanOk + { + get => _isScanOk; + set => SetProperty(ref _isScanOk, value); + } + + /// + /// 追溯码状态 + /// + public string TraceabilityCodeStatus + { + get => _traceabilityCodeStatus; + set => SetProperty(ref _traceabilityCodeStatus, value); + } + + /// + /// 追溯码状态颜色 + /// + public Brush TraceabilityCodeStatusColor + { + get => _traceabilityCodeStatusColor; + set => SetProperty(ref _traceabilityCodeStatusColor, value); + } + + /// + /// 摄像头码状态 + /// + public string SubPartScanCodeStatus + { + get => _SubPartScanCodeStatus; + set => SetProperty(ref _SubPartScanCodeStatus, value); + } + + /// + /// 摄像头码状态颜色 + /// + public Brush SubPartScanCodeStatusColor + { + get => _SubPartScanCodeStatusColor; + set => SetProperty(ref _SubPartScanCodeStatusColor, value); + } + + /// + /// 添加操作历史 + /// + /// 操作描述 + public void AddToOperationHistory(string operation) + { + var timeStamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + OperationHistory.Add($"[{timeStamp}] {operation}"); + // 保持最新的100条记录 + if (OperationHistory.Count > 100) + { + OperationHistory.RemoveAt(0); + } + } + + /// + /// 已扫描的子零件码 + /// + public ObservableCollection ScannedSubCodes { get; set; } = new(); + + public MainWindowViewModel( + IScanService scanService, + IAlertService alertService, + ISerialPortService serialPortService, + ITraceMainService traceMainService, + IS7PlcService plcService, + + IEventAggregator eventAggregator + ) + { + //IRegionManager regionManager, + Debug.WriteLine("程序初始化"); + AddToOperationHistory($"程序初始化"); + // 初始化 + _scanService = scanService; + _alertService = alertService; + _serialPortService = serialPortService; + _traceMainService = traceMainService; + _plcService = plcService; + //_regionManager = regionManager; + _eventAggregator = eventAggregator; + + // 注意:以下事件类可能未正确定义或引用 + // 临时注释掉事件订阅代码,待确认事件类定义后再恢复 + // // 订阅串口数据接收事件 + _serialPortService.DataReceived += OnSerialDataReceived; + + // 尝试打开串口 + OpenSerialPort(); + + // 初始化扫码相关字段 + _scanResult = string.Empty; + _isScanOk = false; + + // 初始化PLC服务器 + InitializePlcService(); + // 初始化串口设置 + InitializeSerialPortSettings(); + + // 初始化导航命令 + NavigateCommand = new DelegateCommand(Navigate); + ComboSelectionChanged = new DelegateCommand(ComboxSelectionChanged); + + // 初始化状态 + Initialize(); + + timer = new DispatcherTimer(); + timer.Interval = TimeSpan.FromSeconds(1); + timer.Tick += Timer_Tick; + timer.Start(); + } + private void Timer_Tick(object sender, EventArgs e) + { + if (HostManager == null) return; + TagServer tagServer = HostManager.GetTagServerByIp(PLCIP); + if (tagServer != null && tagServer.IsConnected()) + { + //功能性状态监视---照明灯、蜂鸣器、产量清零、拍照启用、设备初始化 + Tag tag = tagServer.FindTagByName(ScrewCount); + if (tag != null && !string.IsNullOrEmpty(tag.TagNewValue)) + { + string[] ScrewCountStrArr = tag.TagNewValue.Trim(new char[2] { '[', ']' }).Split(new char[] { ',' }); + + } + + + + + //} + } + } + private void ComboxSelectionChanged(object mode) + { + Debug.Write(mode.ToString()); + if (mode is ComboBoxItem combo) + { + if (combo.Content.ToString() == "单机") + { + TagServer tagServer = HostManager.GetTagServerByIp(PLCIP); + if (CheckTagNameValid(tagServer, OnOrOffline)) + { + tagServer.WriteIntSingle(tagCommon.TagAddress, 1); + } + + } + if (combo.Content.ToString() == "联网") + { + TagServer tagServer = HostManager.GetTagServerByIp(PLCIP); + if (CheckTagNameValid(tagServer, OnOrOffline)) + { + tagServer.WriteIntSingle(tagCommon.TagAddress, 2); + } + } + } + } + + /// + /// 初始化状态 + /// + private void Initialize() + { + CurrentStep = 0; + CurrentStepText = $"请扫{_scanList[CurrentStep]}码"; + + SubPartScanCodeStatus = "--"; + TraceabilityCodeStatus = "--"; + TraceabilityCode = string.Empty; + SubPartScanCode = string.Empty; + ScanResult = string.Empty; + IsScanOk = false; + ScannedSubCodes.Clear(); + } + + /// + /// 导航方法 + /// + /// 视图名称 + private void Navigate(string viewName) + { + AddToOperationHistory($"导航至: {viewName}"); + //_regionManager.RequestNavigate("ContentRegion", viewName); + } + DispatcherTimer timer; + public Host HostManager; + const string ScrewCount = "ScrewCount"; + const string ScrewNum = "ScrewNum"; + const string OnOrOffline = "OnOrOffline"; + const string ScrewGunEnable = "ScrewGunEnable"; + /// + /// 初始化PLC设置 + /// + private async Task InitializePlcService() + { + //bool a = await _plcService.ConnectAsync("192.168.0.30", 0, 1, CpuType.S7200Smart); + //TODO PLC写入 VD880写入1 VD884写入1 + //TODO PLC读取 VD900: 当前产品螺丝个数 VD904:当前已完成螺丝个数 + //short aa = await _plcService.ReadInt16Async("VD900"); + //short bb = await _plcService.ReadInt16Async("VD904"); + + //bool b = await _plcService.WriteVaribleAsync("VD880", 1); + //bool c = await _plcService.WriteVaribleAsync("VD884", 1); + HostManager = new Host(); + + // 创建一个PLC + string targetPlc = PLCIP; + TagServer tagSimens = HostManager.CreateTagServer(targetPlc); + tagSimens.PLCStateChangeEvent += PLCStateChange; + tagSimens.PLCValueChangeEvent += Deal_From_PLC_Trigger; + + #region PLC部分 + // 添加地址信息,变量类型,读取数量,是否监视 DB1000.DBD480 + + // 手动操作命令字地址 + //tagSimens.AddTag(targetPlc, "ManualWord", "DB1000.DBW0", TagType.tagTypeShortInt, 1, TagAccessType.ReadWrite); + ////手自动模式切换地址 + //tagSimens.AddTag(targetPlc, "Cmd_Manual", "DB1000.DBW124", TagType.tagTypeShortInt, 1, TagAccessType.CycleRead); + ////产品型号字 + ////tagSimens.AddTag(targetPlc, "ProductType", "DB1000.DBW126", TagType.tagTypeShortInt, 1, TagAccessType.ReadWrite); + ////命令字 + tagSimens.AddTag(targetPlc, ScrewCount, "VD900", TagType.tagTypeInt, 2, TagAccessType.CycleRead); + tagSimens.AddTag(targetPlc, OnOrOffline, "VD880", TagType.tagTypeInt, 1, TagAccessType.ReadWrite); + tagSimens.AddTag(targetPlc, ScrewGunEnable, "VD884", TagType.tagTypeInt, 1, TagAccessType.ReadWrite); + + //tagSimens.AddTag(targetPlc, ScrewNum, "VD904", TagType.tagTypeShortInt, 1, TagAccessType.CycleRead); + + //////流程步 + //tagSimens.AddTag(targetPlc, "StepInfo", "DB1000.DBW130", TagType.tagTypeShortInt, 1, TagAccessType.CycleRead); + ////产品配方参数地址 + //tagSimens.AddTag(targetPlc, "RecipeParam", "VD600", TagType.tagTypeFloat, 4, TagAccessType.ReadWrite);//伺服位置 + //tagSimens.AddTag(targetPlc, "TestType", "VW500", TagType.tagTypeShortInt, 1, TagAccessType.ReadWrite);//通断通or断通断 + //tagSimens.AddTag(targetPlc, "VDropSource", "VW502", TagType.tagTypeShortInt, 1, TagAccessType.ReadWrite);//压降数据来源 + //tagSimens.AddTag(targetPlc, "LaserCompleteSignal", "VW504", TagType.tagTypeShortInt, 1, TagAccessType.ReadWrite);//压降数据来源 + #endregion + + // 连接PLC,开始工作 + tagSimens.ConnectPLC(PLCType.Smart200); + } + private void PLCStateChange(string sender, string msg) + { + + } + private void Deal_From_PLC_Trigger(string hostName, string tagName, Tag tag) + { + string info = string.Format("{0}/{1}/{2}", hostName, tagName, tag.TagNewValue); + //UpdateRunInfo(info); + + //switch (tagName) + //{ + // case "CmdWord": + // int Order = int.Parse(tag.TagNewValue); + // switch (Order) + // { + // case 2: + // Thread.Sleep(1000); + // globalMethod.WriteShortInt("CmdWordRe", 2);//102 + // break; + // case 3: + // Thread.Sleep(1000); + // globalMethod.WriteShortInt("CmdWordRe", 3);//103 + // break; + // case 4: + // Thread.Sleep(1000); + // globalMethod.WriteShortInt("CmdWordRe", 4);//104 + // break; + // case 5: + // Thread.Sleep(1000); + // globalMethod.WriteShortInt("CmdWordRe", 5);//105 + // break; + // case 6: + // Thread.Sleep(1000); + // globalMethod.WriteShortInt("CmdWordRe", 6);//106 + // break; + // case 10: + // Thread.Sleep(1000); + // globalMethod.WriteShortInt("CmdWordRe", 10);//106 + // break; + // case 11: + // Thread.Sleep(1000); + // globalMethod.WriteShortInt("CmdWordRe", 11);//106 + // break; + // default: + // break; + // } + // break; + // default: + // break; + //} + } + /// + /// 初始化串口设置 + /// + private void InitializeSerialPortSettings() + { + // 设置串口默认参数 + _serialPortService.PortName = "COM1"; // 默认串口名称 + _serialPortService.BaudRate = 9600; // 默认波特率 + _serialPortService.DataBits = 8; // 数据位 + _serialPortService.StopBits = StopBits.One; // 停止位 + _serialPortService.Parity = Parity.None; // 校验位 + _serialPortService.ReadTimeout = 500; // 读取超时 + _serialPortService.WriteTimeout = 500; // 写入超时 + _serialPortService.Handshake = Handshake.None; // 握手协议 + } + + /// + /// 打开串口 + /// + private void OpenSerialPort() + { + try + { + if (!_serialPortService.IsOpen) + { + bool isOpened = _serialPortService.Open(); + if (isOpened) + { + AddToOperationHistory($"串口已打开: {_serialPortService.PortName}"); + TraceabilityCodeStatus = "串口已打开"; + TraceabilityCodeStatusColor = Brushes.Blue; + } + else + { + AddToOperationHistory($"串口打开失败: {_serialPortService.PortName}"); + TraceabilityCodeStatus = "串口打开失败"; + TraceabilityCodeStatusColor = Brushes.Red; + } + } + } + catch (Exception ex) + { + AddToOperationHistory($"串口操作异常: {ex.Message}"); + TraceabilityCodeStatus = "串口异常"; + TraceabilityCodeStatusColor = Brushes.Red; + } + } + + /// + /// 处理串口数据接收事件 + /// + /// 发送者 + /// 事件参数 + private void OnSerialDataReceived( + object sender, + LineTraceabilitySystem.Events.SerialDataReceivedEventArgs e + ) + { + try + { + // 获取串口数据 + string data = e.Data; + if (!string.IsNullOrEmpty(data)) + { + // 处理数据(去掉末尾的换行符) + data = data.TrimEnd('\r', '\n'); + + // 使用Dispatcher确保UI更新在主线程执行 + Application.Current.Dispatcher.Invoke(() => + { + ScanResult = data; + Debug.WriteLine($"步骤:{CurrentStep}"); + // 根据当前步骤处理扫描数据 + switch (CurrentStep) + { + case 0: + OnTraceabilityCodeScanned(data); + break; + default: + OnSubPartCodeScannedAsync(data); + break; + } + }); + } + } + catch (Exception ex) + { + // 使用Dispatcher确保UI更新在主线程执行 + Application.Current.Dispatcher.Invoke(() => + { + AddToOperationHistory($"处理串口数据异常: {ex.Message}"); + }); + } + } + + /// + /// 处理追溯码扫描事件 + /// + /// 扫描到的追溯码 + private void OnTraceabilityCodeScanned(string code) + { + var db = SqlSugarHelper.GetInstance(); + TraceabilityCode = code; + // 验证追溯码逻辑 - 注意:ITraceMainService中可能没有VerifyTraceabilityCode方法 + // 以下代码需要根据实际ITraceMainService接口定义进行调整 + bool isTraceabilityCodeVerified = _scanService.ValidateTraceabilityCode(code); + if (!isTraceabilityCodeVerified) + { + ScanResult = "NG"; + IsScanOk = false; + _alertService.ShowWarning("不是有效的追溯码"); + return; + } + // 是否是未过站追溯码 + bool isTransit = db.Queryable() + .Where(it => it.PartSn == code) + .Where(it=>it.TransitName.ToLower() == _preStep.ToLower()) + .Any(); + if (!isTransit) + { + //发出弹窗 + //用户确认 isback赋值为1返工 + //用户取消,则结束 + MessageBoxResult messageBoxResult = MessageBox.Show($"追溯码未过站,前站:{_preStep.ToLower()}"); + return; + + } + + TraceabilityCodeStatus = isTraceabilityCodeVerified ? "OK" : "NG"; + TraceabilityCodeStatusColor = isTraceabilityCodeVerified ? Brushes.Green : Brushes.Red; + // 是否是返工件 + // 1.子零件已绑定 + bool isBack1 = db.Queryable() + .Where(it => it.PartSn == code) + .Where(it => it.IsBack == 0) + .Any(); + // 2.本站工序未过站 + bool isBack2 = db.Queryable() + .Where(it => it.PartSn == code) + .Where(it => it.TransitName.ToLower() == _thisStep.ToLower()) + .Any(); + if (isBack1 && isBack2) + { + //发出弹窗 + //用户确认 isback赋值为1返工 + //用户取消,则结束 + MessageBoxResult messageBoxResult = MessageBox.Show("请确认是否为返工件?","返工件确认",MessageBoxButton.OKCancel); + if (messageBoxResult == MessageBoxResult.OK) + { + _isBack = 1; + } + else + { + return; + } + + } + + + + AddToOperationHistory($"扫描追溯码: {code}, 状态: {TraceabilityCodeStatus}"); + // 切换到扫描摄像头码步骤 + CurrentStep += 1; + CurrentStepText = $"请扫{_scanList[CurrentStep]}码"; + } + + /// + /// 处理摄像头码扫描事件 + /// + /// 扫描到的摄像头码 + private async Task OnSubPartCodeScannedAsync(string code) + { + string partCodeScanName = _scanList[CurrentStep]; + SubPartScanCode = code; + // 验证摄像头码逻辑 - 注意:ITraceMainService中可能没有VerifySubPartScanCode方法 + // 以下代码需要根据实际ITraceMainService接口定义进行调整 + bool isSubPartScanCodeValid = code.Contains("2F1980546#")&& code.Length==31 && _scanService.ValidateSubPartCode(code); + SubPartScanCodeStatus = isSubPartScanCodeValid ? "OK" : "NG"; + SubPartScanCodeStatusColor = isSubPartScanCodeValid ? Brushes.Green : Brushes.Red; + if (!isSubPartScanCodeValid) + { + ScanResult = "NG"; + IsScanOk = false; + _alertService.ShowWarning("不是有效的码"); + return; + } + AddToOperationHistory($"扫描摄像头码: {code}, 状态: {SubPartScanCodeStatus}"); + CurrentStep += 1; + + if (CurrentStep == _scanList.Count) + { + Debug.WriteLine($"追溯数据上传服务器"); + AddToOperationHistory($"追溯数据上传服务器"); + TagServer tagServer = HostManager.GetTagServerByIp(PLCIP); + if (CheckTagNameValid(tagServer, ScrewGunEnable)) + { + tagServer.WriteIntSingle(tagCommon.TagAddress, 1); + } + CurrentStep = 1000; + } + if (CurrentStep == 1000) + { + await OnUploadScanned(SubPartScanCode, partCodeScanName); + Initialize(); + } + else + { + CurrentStepText = $"请扫{_scanList[CurrentStep]}码"; + } + } + + /// + /// 处理子零件码扫完码上传事件 + /// + /// 扫描到的子零件码 + /// 子零件名称 + /// + /// 处理子零件码扫完码上传事件 + /// + /// 扫描到的子零件码 + /// 子零件名称 + private async Task OnUploadScanned(string code, string subName) + { + try + { + Debug.WriteLine($"追溯数据上传服务器"); + AddToOperationHistory($"追溯数据上传服务器"); + + // 在后台线程中执行数据库操作 + await Task.Run(() => + { + // 使用SqlSugarHelper获取数据库实例 + var db = SqlSugarHelper.GetInstance(); + + var hasSubScan = db.Queryable() + .Where(it => it.PartSn == TraceabilityCode) + .Where(it => it.SubCode == "SXT") + .Where(it => it.IsBack == _isBack) + .Any(); + if (!hasSubScan || _isBack == 1) + { + // 创建追溯记录 + var traceRecord = new TraceSnSubScan + { + SubCode = "SXT", + SubName = subName, + Specification = "规格", + PartSn = TraceabilityCode, + SubPartScanCode = code, + IsBack = _isBack, + CreateTime = DateTime.Now + }; + // 插入数据 + db.Insertable(traceRecord).ExecuteCommand(); + } + var hasTransit = db.Queryable() + .Where(it => it.PartSn == TraceabilityCode) + .Where(it => it.TransitName == "OP20") + .Where(it => it.IsBack == _isBack) + .Any(); + if (!hasTransit || _isBack == 1) + { + // 创建过站记录 + var transitRecord = new TraceSnTransit + { + PartSn = TraceabilityCode, + TransitName = "OP20", + IsBack = _isBack, + CreateTime = DateTime.Now + }; + // 插入数据 + db.Insertable(transitRecord).ExecuteCommand(); + } + // 创建日志 + var logRecord = new TraceLog + { + LogTitle = "扫码追溯", + LogContent = $"扫码追溯成功,追溯码:{TraceabilityCode},子零件追溯码:{code}", + LogType = "success", + CreateTime = DateTime.Now, + CreateBy = "系统" + }; + // 插入数据 + db.Insertable(logRecord).ExecuteCommand(); + Debug.WriteLine($"追溯上传成功"); + + }); + + // 在UI线程中更新操作历史 + Application.Current.Dispatcher.Invoke(() => + { + AddToOperationHistory($"追溯上传成功"); + }); + } + catch (Exception ex) + { + Debug.WriteLine($"追溯上传失败: {ex.Message}"); + + // 在UI线程中更新操作历史和显示错误信息 + Application.Current.Dispatcher.Invoke(() => + { + AddToOperationHistory($"追溯上传失败: {ex.Message}"); + _alertService.ShowError($"追溯数据上传失败: {ex.Message}"); + }); + } + return true; + } + public static Tag tagCommon; + public static bool CheckTagNameValid(TagServer tagServer, string tagName) + { + bool ret = true; + if (tagServer != null) + { + tagCommon = tagServer.FindTagByName(tagName); + if (tagCommon == null) + { + ret = false; + } + + if (!tagServer.IsConnected()) + { + ret = false; + } + } + else + { + ret = false; + } + return ret; + } + } +} diff --git a/src/LineTraceabilitySystem/ViewModels/TraceabilityViewModel.cs b/src/LineTraceabilitySystem/ViewModels/TraceabilityViewModel.cs new file mode 100644 index 0000000..13bf684 --- /dev/null +++ b/src/LineTraceabilitySystem/ViewModels/TraceabilityViewModel.cs @@ -0,0 +1,77 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using System.Collections.ObjectModel; + +namespace LineTraceabilitySystem.ViewModels +{ + public partial class TraceabilityViewModel : ObservableObject + { + [ObservableProperty] + private string title = "追溯管理"; + + [ObservableProperty] + private string traceabilityCode = string.Empty; + + [ObservableProperty] + private string productModel = "[型号信息]"; + + [ObservableProperty] + private string productionBatch = "[批次信息]"; + + [ObservableProperty] + private string productionDate = "[日期信息]"; + + [ObservableProperty] + private string productionTeam = "[班组信息]"; + + [ObservableProperty] + private string status = "[状态信息]"; + + public ObservableCollection CameraBindings { get; set; } + + [RelayCommand] + private void ScanTraceabilityCode() + { + // 实现扫描追溯码逻辑 + // 模拟扫描成功 + ProductModel = "CameraModel-XY-2024"; + ProductionBatch = "Batch-20240601"; + ProductionDate = System.DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); + ProductionTeam = "Team-A"; + Status = "已验证"; + + // 加载摄像头绑定信息 + LoadCameraBindings(); + } + + [RelayCommand] + private void ManualInput() + { + // 实现手动输入逻辑 + } + + [RelayCommand] + private void PrintReport() + { + // 实现打印报告逻辑 + } + + private void LoadCameraBindings() + { + // 模拟数据 + CameraBindings = + [ + new CameraBindingInfo { CameraId = "CAM-1001", CameraModel = "HD-200", BindTime = "2024-06-01 10:15:30", Operator = "张三" }, + new CameraBindingInfo { CameraId = "CAM-1002", CameraModel = "HD-200", BindTime = "2024-06-01 10:20:15", Operator = "张三" } + ]; + } + } + + public class CameraBindingInfo + { + public string CameraId { get; set; } = string.Empty; + public string CameraModel { get; set; } = string.Empty; + public string BindTime { get; set; } = string.Empty; + public string Operator { get; set; } = string.Empty; + } +} \ No newline at end of file diff --git a/src/LineTraceabilitySystem/Views/HomeView.xaml b/src/LineTraceabilitySystem/Views/HomeView.xaml new file mode 100644 index 0000000..08279f2 --- /dev/null +++ b/src/LineTraceabilitySystem/Views/HomeView.xaml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + +