using JinYuan.DataConvertLib; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using PLCCommunication.MQTT; using System; using System.Collections.Generic; using System.Configuration; using System.Data; using System.Diagnostics; using System.IO; using System.Linq; using System.ServiceProcess; using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; namespace WindowsService1 { public partial class Service1 : ServiceBase { private readonly List _tasks = new List(); private readonly CancellationTokenSource _cts = new CancellationTokenSource(); private PLCWrapper _plcWrapper; private MqttClientWrapper mqttClient = null; private System.Threading.Timer _periodicTimer; private Dictionary _collectionGroups; private Dictionary> _plcAddresses; private AcquisitionType[] AcqTypes = null; private const int ReconnectionInterval = 5000; // 5 seconds private const int MaxReconnectAttempts = 3; public string PlcIpAddress = "127.0.0.1"; public int PlcPort = 9600; public string MQTTServerAddress = "127.0.0.1"; public int MQTTServerPort = 1883; public string ClientId = "601UPW01"; //PPM主题 public string Topic = "EVE/60J/EQP/NEW_PPM/601UPW01"; //PPM其他数据 public string Topic1 = "eve/60J/EQP/601UPW01"; //直角坐标机械臂 public string LRobotTopic = "EVE/60J/LRobot/601PWM01_LUR01"; //关节机械臂 public string JRobotTopic = "EVE/60J/JRobot/601PWM01_STR01"; public MqttCredential credentials = null; public string BU_id = "EVE2BU"; public string District_id = "QJ"; public string Factory_id = "60J"; public string Production_line_id = "MW-601"; public string Device_name = "601UPW01"; public int ThreadInterval = 50; public static bool SwitchLog = false; public Service1() { InitializeComponent(); ServiceName = "TestMqttService"; CanStop = true; // 服务可以被停止 CanPauseAndContinue = false; AutoLog = true; } protected override void OnStart(string[] args) { Logger.WriteInfo($"OnStart:服务启动-{DateTime.Now}"); InitializeConfig(); _plcWrapper = new PLCWrapper(PlcIpAddress, PlcPort); InitializeTask(); InitializePLC(); InitializeMQTT(); Task.Run(() => RunChangeDetectionTaskAsync(_cts.Token));// 启动变化检测任务 Logger.WriteInfo($"OnStart:服务启动完毕-{DateTime.Now}"); } protected override async void OnStop() { _cts.Cancel(); _periodicTimer?.Dispose(); // 停止 PLC 读取 if (_plcWrapper != null) { _plcWrapper.Disconnect(); Logger.WriteInfo("PLC 通讯停止."); } if (mqttClient != null) { await mqttClient.Disconnect(); Logger.WriteInfo("MQTT 连接已关闭."); } Logger.WriteInfo("OnStop"); } /// /// 系统配置文件读取 /// private void InitializeConfig() { ReadConfigFromXml(); PlcIpAddress = ConfigurationManager.AppSettings["PlcIpAddress"] ?? "127.0.0.1"; PlcPort = int.Parse(ConfigurationManager.AppSettings["PlcPort"] ?? "9600"); MQTTServerAddress = ConfigurationManager.AppSettings["MQTTServerAddress"] ?? "127.0.0.1"; MQTTServerPort = int.Parse(ConfigurationManager.AppSettings["MQTTServerPort"] ?? "1883"); ClientId = ConfigurationManager.AppSettings["ClientId"] ?? "601UPW01"; Topic = ConfigurationManager.AppSettings["Topic"] ?? "EVE/60J/EQP/NEW_PPM/601UPW01"; Topic1 = ConfigurationManager.AppSettings["Topic1"] ?? "eve/60J/EQP/601UPW01"; LRobotTopic = ConfigurationManager.AppSettings["LRobotTopic"] ?? "EVE/60J/LRobot/601PWM01_LUR01"; JRobotTopic = ConfigurationManager.AppSettings["JRobotTopic"] ?? "EVE/60J/JRobot/601PWM01_STR01"; BU_id = ConfigurationManager.AppSettings["BU_id"] ?? "601UPW01"; District_id = ConfigurationManager.AppSettings["District_id"] ?? "QJ"; Factory_id = ConfigurationManager.AppSettings["Factory_id"] ?? "60J"; Production_line_id = ConfigurationManager.AppSettings["Production_line_id"] ?? "MW-601"; Device_name = ConfigurationManager.AppSettings["Device_name"] ?? "601UPW01"; ThreadInterval = int.Parse(ConfigurationManager.AppSettings["ThreadInterval"] ?? "50"); SwitchLog = bool.Parse(ConfigurationManager.AppSettings["SwitchLog"] ?? "True"); MqttDataService.BU_id = BU_id; MqttDataService.District_id = District_id; MqttDataService.Factory_id = Factory_id; MqttDataService.Production_line_id = Production_line_id; MqttDataService.Device_name = Device_name; MqttDataService.Work_Center_id = "601FGE02"; Logger.WriteInfo("读取系统配置文件成功!"); } private void ReadConfigFromXml() { try { var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config.xml"); var doc = XDocument.Load(configPath); string deviceName = ""; _plcAddresses = new Dictionary>(); _collectionGroups = new Dictionary(); foreach (var deviceElement in doc.Descendants("Device")) { var isEnabled = bool.Parse(deviceElement.Attribute("isEnable")?.Value ?? "false"); if (isEnabled) { deviceName = deviceElement.Attribute("name")?.Value; foreach (var groupElement in deviceElement.Elements("CollectionGroup")) { var groupName = groupElement.Attribute("name").Value; Logger.WriteInfo($"添加寄存器组类型: {groupName}"); var acquisitionType = (AcquisitionType)Enum.Parse(typeof(AcquisitionType), groupName); var groupConfig = new CollectionGroupConfig { Name = groupName, StartAddress = groupElement.Attribute("StartAddress").Value, Length = int.Parse(groupElement.Attribute("length").Value), DefaultDataType = groupElement.Attribute("DataType")?.Value ?? "Short", Types = new Dictionary() }; var groupAddresses = new Dictionary(); foreach (var typeElement in groupElement.Elements("Type")) { var typeName = typeElement.Attribute("name").Value; var typeAddress = typeElement.Attribute("address")?.Value ?? typeElement.Value; var dataType = typeElement.Attribute("DataType")?.Value ?? groupConfig.DefaultDataType; // 创建TypeConfig对象 var typeConfig = new TypeConfig { Name = typeName, Address = typeAddress, DataType = dataType }; groupConfig.Types[typeName] = typeConfig; groupAddresses[typeName] = typeAddress; Logger.WriteInfo($"添加类型: {typeName}, 地址: {typeAddress}, 数据类型: {dataType}"); } _collectionGroups[acquisitionType] = groupConfig; _plcAddresses[acquisitionType] = groupAddresses; } break; // 只处理第一个启用的Device } } Logger.WriteInfo($"读取 【{deviceName}】地址文件成功!!!"); } catch (Exception ex) { Logger.WriteError($"读取配置文件错误: {ex.Message}"); } } private static PLCDataType DetermineDataType(string groupName, string dataTypeAttribute) { if (!string.IsNullOrEmpty(dataTypeAttribute)) { return (PLCDataType)Enum.Parse(typeof(PLCDataType), dataTypeAttribute); } if (!string.IsNullOrEmpty(groupName)) { AcquisitionType type = (AcquisitionType)Enum.Parse(typeof(AcquisitionType), groupName); // 如果 DataType 属性为空,根据组名推断类型 switch (type) { case AcquisitionType.PPM: return PLCDataType.Short; case AcquisitionType.Axis: case AcquisitionType.Cylinder: return PLCDataType.Float; default: throw new ArgumentException($"未知的组名: {groupName}"); } } else { throw new ArgumentException($"未知的组名: {groupName}"); } } #region 初始化Task线程,PLC,MQTT private void InitializeTask() { if (_collectionGroups != null && _collectionGroups.Count > 0) { List typesList = new List(); foreach (var kvp in _collectionGroups) { switch (kvp.Key) { case AcquisitionType.Axis: case AcquisitionType.Cylinder: StartPeriodicTask(); // 启动定时读取任务 break; case AcquisitionType.PPM: typesList.Add(kvp.Key); break; default: break; } } // 将 List 转换为数组 AcqTypes = typesList.ToArray(); // 如果需要,你可以在这里使用 types 数组 if (AcqTypes.Length > 0) { Logger.WriteInfo("添加类型: " + string.Join(", ", AcqTypes)); // 这里可以添加其他使用 types 数组的逻辑 } } } private async void InitializePLC() { try { bool connected = await _plcWrapper.ConnectAsync(); if (connected) { Logger.WriteInfo("PLC初始化并连接成功."); } else { Logger.WriteInfo("无法连接 PLC."); throw new Exception("PLC 连接失败"); } } catch (Exception ex) { Logger.WriteError($"初始化 PLC 错误: {ex.Message}"); } } private async void InitializeMQTT() { try { mqttClient = new MqttClientWrapper(MQTTServerAddress, MQTTServerPort, ClientId, credentials); //初始化 await mqttClient.InitializeAndConnectAsync();//连接MQTT SubscribeTopic(Topic);//订阅主题 SubscribeTopic(Topic1);//订阅主题1 SubscribeTopic(LRobotTopic);//订阅直角坐标机械臂 SubscribeTopic(JRobotTopic);//订阅关机机械臂 } catch (Exception ex) { Logger.WriteError($"连接 MQTT 代理时出错: {ex.Message}"); } } /// /// 订阅主题 /// /// private void SubscribeTopic(string topicToSubscribe) { mqttClient.OnMessageReceived += (timestamp, topic, message) => { //Console.WriteLine($"{timestamp} Topic[{topic}]: {message}"); //Logger.WriteMqtt($"{timestamp} Topic[{topic}]"); // 在这里更新你的 UI }; mqttClient.SubscribeMessage(topicToSubscribe, MqttClientWrapper.MessageFormat.Json, true); } /// /// 类发布消息 /// /// /// /// private async Task SendMqttAsync(string source, string publishTopic, string type, JObject messageObj) { try { if (publishTopic != null) { await mqttClient.SendMessageAsync(source, publishTopic, type, messageObj); } } catch (Exception ex) { Console.WriteLine($"发布信息: {ex.Message}"); } } /// /// 字符串发布消息 /// /// /// /// /// /// private async Task SendMqttAsync(string source, string publishTopic, string type, string messageObj) { try { if (publishTopic != null) { await mqttClient.SendMessageAsync(source, publishTopic, type, messageObj); } } catch (Exception ex) { Console.WriteLine($"发布信息: {ex.Message}"); } } #endregion #region 创建定时线程 /// /// 轴扭矩定时器采集线程 /// private void StartPeriodicTask() { Logger.WriteInfo("启动定期数据收集任务"); _periodicTimer = new System.Threading.Timer(async _ => { try { Logger.WriteInfo($"定期触发任务: {DateTime.Now}"); await CollectAndPublishData("AxisTask", new[] { AcquisitionType.Axis }); await CollectAndPublishData("AxisTask", new[] { AcquisitionType.Cylinder }); await CollectAndPublishData("LRobotTask", new[] { AcquisitionType.LRobot }); Logger.WriteInfo($"定期任务完成: {DateTime.Now}"); } catch (Exception ex) { Logger.WriteError($"周期性任务执行错误: {ex.Message}"); } }, null, TimeSpan.Zero, TimeSpan.FromSeconds(1)); // 每秒执行一次 } /// /// 有变化数据采集线程 /// /// /// /// private async Task RunChangeDetectionTaskAsync(CancellationToken ct) { while (!ct.IsCancellationRequested) { if (_plcWrapper._plcIsConnected) { try { await CollectAndPublishData("ChangeTask", AcqTypes); await Task.Delay(500, ct); } catch (OperationCanceledException) { // 正常取消,无需额外处理 } catch (Exception ex) { Logger.WriteError($"变更检测任务错误: {ex.Message}"); await Task.Delay(1000, ct); } } } } #endregion /// /// 读取数据 /// /// /// /// private async Task CollectAndPublishData(string source, AcquisitionType[] types) { const int maxRetries = 3; for (int retry = 0; retry < maxRetries; retry++) { try { var requests = new List(); foreach (var type in types) { var config = _collectionGroups[type]; requests.Add(new PLCDataReadRequest { StartAddress = config.StartAddress, Length = (ushort)config.Length, StorageName = config.Name }); } foreach (var request in requests) { var result = await _plcWrapper._dataReader.ReadDataAsync(request); if (result.IsSuccess) { await ProcessPLCData(source, request.StorageName, result.Data); } else { Logger.WriteInfo($"错误读取 {result.StorageName}: {result.ErrorMessage}"); throw new Exception($"读取 PLC 数据失败: {result.ErrorMessage}"); } } break; // 如果成功,退出重试循环 } catch (Exception ex) { Logger.WriteError($"Error {source}: {ex.Message}"); if (retry < maxRetries - 1) { Logger.WriteInfo($"尝试重新连接到 PLC,尝试 {retry + 1}"); bool reconnected = await AttemptReconnection(_cts.Token); if (!reconnected) { Logger.WriteError("无法重新连接 PLC,放弃进一步尝试"); break; } } else { Logger.WriteError($"最大重试次数 ({maxRetries}) 已到达,放弃操作"); } } } } /// /// 重连PLC /// /// /// private async Task AttemptReconnection(CancellationToken ct) { const int MaxReconnectAttempts = 3; const int ReconnectionInterval = 5000; // 5 seconds for (int attempt = 1; attempt <= MaxReconnectAttempts; attempt++) { if (ct.IsCancellationRequested) { return false; } Logger.WriteInfo($"尝试重新连接PLC,第 {attempt} 次尝试"); bool reconnected = await _plcWrapper.ReconnectAsync(); if (reconnected) { Logger.WriteInfo("PLC重新连接成功"); return true; } if (attempt < MaxReconnectAttempts) { await Task.Delay(ReconnectionInterval, ct); } } Logger.WriteError($"PLC重新连接失败,已尝试 {MaxReconnectAttempts} 次"); return false; } /// /// 读取PLC数据 /// /// /// /// /// private async Task ProcessPLCData(string source, string acquisitionTypeName, byte[] data) { try { AcquisitionType type = (AcquisitionType)Enum.Parse(typeof(AcquisitionType), acquisitionTypeName); var typeConfigs = GetTypeConfigs(type).ToList(); bool hasChanges = false; List processedValues = null; switch (type) { case AcquisitionType.PPM: var ppmResult = ProcessShortData(type, data, typeConfigs); hasChanges = ppmResult.hasChanges; processedValues = ppmResult.values; break; case AcquisitionType.Axis: case AcquisitionType.Cylinder: case AcquisitionType.LRobot: var floatResult = ProcessFloatData(type, data, typeConfigs); hasChanges = floatResult.hasChanges; processedValues = floatResult.values; break; default: Logger.WriteError($"未知的采集类型: {type}"); return; } if (hasChanges && processedValues != null) { // 上传到MQTT服务器 await UpMQTTServerDataNew(source, type, processedValues); } else { Logger.WriteInfo($"{type} 数据未改变,未上传"); } } catch (Exception ex) { Logger.WriteError($"处理 PLC 数据时出错(源: {source}, 类型: {acquisitionTypeName}): {ex.Message}"); } } // 相应地,ProcessFloatData方法也需要修改返回类型 private List _lastFloatStationData = new List(); private (bool hasChanges, List values) ProcessFloatData(AcquisitionType type, byte[] rawData, List typeConfigs) { bool hasChanges = false; var currentValues = new DataValue[typeConfigs.Count]; // 记录实际需要处理的数据点数量 Logger.WriteInfo($"开始处理数据: typeConfigs.Count={typeConfigs.Count}, rawData.Length={rawData.Length}"); var sw = new Stopwatch(); sw.Restart(); // 初始化数组为完整的大小 currentValues = new DataValue[typeConfigs.Count]; var stationDataList = new List(); try { // 第一步:处理所有Float类型数据(主数据) var floatConfigs = typeConfigs.Where(t => t.DataType.Equals("Float", StringComparison.OrdinalIgnoreCase)).ToList(); foreach (var config in floatConfigs) { // 从地址中提取偏移量 if (int.TryParse(config.Address.Substring(1), out int address)) { int startAddress = int.Parse(typeConfigs[0].Address.Substring(1)); int offset = (address - startAddress) * 2; if (offset >= 0 && offset + 4 <= rawData.Length) { byte[] valueBytes = new byte[4]; for (int j = 0; j < 4; j++) { valueBytes[j] = (byte)(Convert.ToInt32(rawData[offset + j]) & 0xFF); } object convertedValue = ConvertFromBytes(valueBytes, "Float"); int index = typeConfigs.IndexOf(config); currentValues[index] = new DataValue { Name = config.Name, Value = convertedValue, DataType = config.DataType }; } } } // 第二步:处理所有Short类型数据 var shortConfigs = typeConfigs.Where(t => t.DataType.Equals("Short", StringComparison.OrdinalIgnoreCase)).ToList(); foreach (var config in shortConfigs) { if (int.TryParse(config.Address.Substring(1), out int address)) { int startAddress = int.Parse(typeConfigs[0].Address.Substring(1)); int offset = (address - startAddress) * 2; if (offset >= 0 && offset + 2 <= rawData.Length) { byte[] valueBytes = new byte[2]; for (int j = 0; j < 2; j++) { valueBytes[j] = (byte)(Convert.ToInt32(rawData[offset + j]) & 0xFF); } object convertedValue = ConvertFromBytes(valueBytes, "Short"); int index = typeConfigs.IndexOf(config); currentValues[index] = new DataValue { Name = config.Name, Value = convertedValue, DataType = config.DataType }; } } } // 第三步:组织StationData列表 foreach (var floatConfig in floatConfigs) { string baseName = floatConfig.Name; // 例如 "S_MagLev" var mainValue = currentValues[typeConfigs.IndexOf(floatConfig)]?.Value; // 先获取MainValue,如果为0则跳过这个工位,暂时不过滤 //if (mainValue == null || Convert.ToDouble(mainValue) == 0) //{ // Logger.WriteInfo($"跳过工位 {baseName}: MainValue为0或null"); // continue; //} // 只检查MainValue是否与上一次的值相同,暂时不检查 //var lastData = _lastStationData.FirstOrDefault(x => x.Name == baseName); //if (lastData != null) //{ // bool valueEqual = Convert.ToDouble(lastData.MainValue) == Convert.ToDouble(mainValue); // if (valueEqual) // { // Logger.WriteInfo($"跳过工位 {baseName}: 值未变化 当前值:{mainValue}"); // continue; // } // Logger.WriteInfo($"工位 {baseName} 数值发生变化: {lastData.MainValue} -> {mainValue}"); //} // 创建StationData对象 var stationData = new StationData { Name = baseName, MainValue = mainValue, Timestamp = DateTime.Now, }; stationDataList.Add(stationData); Logger.WriteInfo($"添加工位数据: {stationData.Name}, 值: {stationData.MainValue}, 时间戳: {stationData.Timestamp:HH:mm:ss.fff}"); } // 检查是否有新数据需要更新 if (stationDataList.Any()) { hasChanges = true; // 如果有数据通过过滤,就表示有变化 // 创建深拷贝以避免引用问题 _lastFloatStationData = stationDataList.Select(s => new StationData { Name = s.Name, MainValue = s.MainValue, Timestamp = s.Timestamp }).ToList(); _lastFloatStationData = stationDataList;//把当前的新数据赋值到旧的集合中 //// 记录变化信息 //foreach (var newData in stationDataList) //{ // Logger.WriteInfo($"工位 {newData.Name} 更新: 时间戳: {newData.Timestamp:HH:mm:ss.fff}, 值: {newData.MainValue}"); //} } else { Logger.WriteInfo("没有发现时间戳变化的数据"); hasChanges = false; } sw.Stop(); Logger.WriteInfo($"处理结束耗时:{sw.ElapsedMilliseconds}ms"); return (hasChanges, stationDataList); } catch (Exception ex) { Logger.WriteError($"处理数据时出错: {ex.Message}"); return (false, stationDataList); } } /// /// 检测PPM数据是否有变化 /// /// /// private List _lastStationData = new List(); private readonly StationDataComparer2 _comparer = new StationDataComparer2(); private (bool hasChanges, List values) ProcessShortData(AcquisitionType type, byte[] rawData, List typeConfigs) { var sw = new Stopwatch(); sw.Start(); Logger.WriteInfo($"开始新一轮数据处理"); try { var currentStationData = new Dictionary(); bool hasNewData = false; // 1. 获取所有主值配置(Float类型) var mainValueConfigs = typeConfigs .Where(t => string.Equals(t.DataType, "Float", StringComparison.OrdinalIgnoreCase)) .OrderBy(t => int.Parse(t.Address.Substring(1))) .ToList(); Logger.WriteInfo($"找到 {mainValueConfigs.Count} 个Float类型配置"); foreach (var config in mainValueConfigs) { Logger.WriteInfo($"Float配置: {config.Name}, 地址: {config.Address}"); } // 2. 获取所有时间配置(Short类型) var timeConfigs = typeConfigs .Where(t => string.Equals(t.DataType, "Short", StringComparison.OrdinalIgnoreCase)) .ToList(); // 3. 首先读取所有工位的当前值和时间戳 foreach (var mainConfig in mainValueConfigs) { string stationName = mainConfig.Name; // 读取主值 double? mainValue = ReadFloatValue(mainConfig, rawData, typeConfigs[0].Address); if (mainValue == null || mainValue <= 0) { Logger.WriteInfo($"工位 {stationName} 主值无效: {mainValue}"); continue; } // 读取时间组件 DateTime timestamp = ReadTimestamp(stationName, timeConfigs, rawData, typeConfigs[0].Address); // 添加到当前数据集合 currentStationData[stationName] = new StationData { Name = stationName, MainValue = mainValue, Timestamp = timestamp }; Logger.WriteInfo($"读取工位数据: {stationName}, 值: {mainValue}, 时间: {timestamp:HH:mm:ss.fff}"); } // 4. 比较新旧数据,确定是否有变化 if (currentStationData.Any()) { if (_lastStationData == null || !_lastStationData.Any()) { hasNewData = true; Logger.WriteInfo("首次数据采集,记录所有数据"); } else { // 检查每个工位的数据是否有变化 foreach (var current in currentStationData.Values) { var lastData = _lastStationData.FirstOrDefault(x => x.Name == current.Name); if (lastData == null) { hasNewData = true; Logger.WriteInfo($"发现新工位数据: {current.Name}"); break; } // 检查值和时间戳是否都有变化 bool valueChanged = Math.Abs(Convert.ToDouble(lastData.MainValue) - Convert.ToDouble(current.MainValue)) > 0.001; bool timeChanged = Math.Abs((current.Timestamp - lastData.Timestamp).TotalMilliseconds) >= 500; if (valueChanged || timeChanged) { hasNewData = true; Logger.WriteInfo($"工位 {current.Name} 数据发生变化 - 值变化: {valueChanged}, 时间变化: {timeChanged}"); break; } } } } // 5. 如果有新数据,更新最后处理的数据 var resultList = currentStationData.Values.ToList(); if (hasNewData) { _lastStationData = new List(resultList); Logger.WriteInfo($"更新状态数据,数量: {resultList.Count}"); } sw.Stop(); Logger.WriteInfo($"处理完成,耗时: {sw.ElapsedMilliseconds}ms, 数据点数量: {resultList.Count}, 有变化: {hasNewData}"); return (hasNewData, resultList); } catch (Exception ex) { Logger.WriteError($"数据处理错误: {ex.Message}\n{ex.StackTrace}"); return (false, new List()); } } private double? ReadFloatValue(TypeConfig config, byte[] rawData, string baseAddress) { try { if (!int.TryParse(config.Address.Substring(1), out int address) || !int.TryParse(baseAddress.Substring(1), out int startAddress)) { return null; } int offset = (address - startAddress) * 2; if (offset < 0 || offset + 4 > rawData.Length) { return null; } byte[] valueBytes = new byte[4]; Array.Copy(rawData, offset, valueBytes, 0, 4); var value = ConvertFromBytes(valueBytes, "Float"); return value != null ? Convert.ToDouble(value) : null; } catch { return null; } } private DateTime ReadTimestamp(string stationName, List timeConfigs, byte[] rawData, string baseAddress) { // 获取时间组件配置 var hourConfig = timeConfigs.FirstOrDefault(t => t.Name == $"{stationName}_Hour"); var minConfig = timeConfigs.FirstOrDefault(t => t.Name == $"{stationName}_Min"); var secConfig = timeConfigs.FirstOrDefault(t => t.Name == $"{stationName}_Sec"); var msecConfig = timeConfigs.FirstOrDefault(t => t.Name == $"{stationName}_MSec"); // 读取时间值 int hour = ReadShortValue(hourConfig, rawData, baseAddress); int minute = ReadShortValue(minConfig, rawData, baseAddress); int second = ReadShortValue(secConfig, rawData, baseAddress); int millisecond = ReadShortValue(msecConfig, rawData, baseAddress); // 验证时间值 hour = Math.Min(Math.Max(hour, 0), 23); minute = Math.Min(Math.Max(minute, 0), 59); second = Math.Min(Math.Max(second, 0), 59); millisecond = Math.Min(Math.Max(millisecond, 0), 999); // 获取当前时间 var now = DateTime.Now; // 构建读取的时间 var readTime = new DateTime( now.Year, now.Month, now.Day, hour, minute, second, millisecond ); // 如果读取的时间比当前时间大,说明应该是前一天的数据 if (readTime > now) { readTime = readTime.AddDays(-1); Logger.WriteInfo($"检测到跨天数据,调整日期为前一天: {readTime:yyyy-MM-dd HH:mm:ss.fff}"); } return readTime; } private int ReadShortValue(TypeConfig config, byte[] rawData, string baseAddress) { try { if (config == null || !int.TryParse(config.Address.Substring(1), out int address) || !int.TryParse(baseAddress.Substring(1), out int startAddress)) { return 0; } int offset = (address - startAddress) * 2; if (offset < 0 || offset + 2 > rawData.Length) { return 0; } byte[] valueBytes = new byte[2]; Array.Copy(rawData, offset, valueBytes, 0, 2); var value = ConvertFromBytes(valueBytes, "Short"); return Convert.ToInt32(value); } catch { return 0; } } private object ConvertFromBytes(byte[] bytes, string dataType) { try { if (bytes == null || bytes.Length == 0) return null; switch (dataType.ToLower()) { case "bool": return BitConverter.ToInt16(bytes, 0) != 0; case "short": return ShortLib.GetShortFromByteArray(bytes, 0, DataFormat.CDAB); case "int": return IntLib.GetIntFromByteArray(bytes, 0, DataFormat.CDAB); case "float": return FloatLib.GetFloatFromByteArray(bytes, 0, DataFormat.CDAB); default: return ShortLib.GetShortFromByteArray(bytes, 0, DataFormat.CDAB); } } catch (Exception ex) { Logger.WriteError($"转换数据类型失败: {BitConverter.ToString(bytes)} to {dataType}, {ex.Message}"); return null; } } /// /// 上传MQTT /// /// /// /// /// public async Task UpMQTTServerDataNew(string source, AcquisitionType type, List processedValues) { try { if (processedValues == null || processedValues.Count == 0) { return; } //var addresses = _plcAddresses[type]; //var config = _collectionGroups[type]; var message = MqttDataService.CreateMqttMessage(source, type, processedValues, _plcAddresses, _collectionGroups); if (message != null) { var messageJson = JsonConvert.SerializeObject(message, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }); if (type.ToString() == "PPM") { await SendMqttAsync(source, Topic, type.ToString(), messageJson); } else if (type.ToString() == "Axis" || type.ToString() == "Cylinder") { await SendMqttAsync(source, Topic1, type.ToString(), messageJson); } } if (type.ToString() == "LRobot") { var message1 = MqttDataService.LRobotMessage(source, type, processedValues, _plcAddresses, _collectionGroups); if (message1 != null) { var messageJson1 = JsonConvert.SerializeObject(message1, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore, Formatting = Formatting.None }); if (type.ToString() == "LRobot") { await SendMqttAsync(source, LRobotTopic, type.ToString(), messageJson1); } } } } catch (Exception ex) { Logger.WriteError($"方法:上传MQTTServer数据,上传失败: {ex.Message}", ex); } } private List ConvertToShortList(object[] data) { return data.Select(d => Convert.ToInt16(d)).ToList(); } private List ConvertToFloatList(object[] data) { return data.Select(d => Convert.ToSingle(d)).ToList(); } // 获取特定类型的数据类型 public string GetDataType(AcquisitionType acquisitionType, string typeName) { if (_collectionGroups.TryGetValue(acquisitionType, out var groupConfig)) { if (groupConfig.Types.TryGetValue(typeName, out var typeConfig)) { return typeConfig.DataType; } } return "Short"; // 默认返回Short类型 } // 获取数据类型的字节大小 public int GetTypeSize(string dataType) { switch (dataType.ToLower()) { case "bool": return 2; // 在PLC中通常用2字节表示 case "short": return 2; // 2字节 case "int": return 4; // 4字节 case "float": return 4; // 4字节 default: return 2; // 默认2字节 } } // 获取组内所有类型配置 public IEnumerable GetTypeConfigs(AcquisitionType acquisitionType) { if (_collectionGroups.TryGetValue(acquisitionType, out var groupConfig)) { return groupConfig.Types.Values; } return Enumerable.Empty(); } } /// /// 时间转化 /// public static class DateTimeExtensions { public static long ToUnixTimeMilliseconds(this DateTime dateTime) { return (long)(dateTime.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds; } public static long GetUnixTimestamp() { return (DateTime.Now.ToUniversalTime().Ticks - 621355968000000000) / 10000000; } } // 辅助类 public class DataValue { public string Name { get; set; } public object Value { get; set; } public string DataType { get; set; } public override bool Equals(object obj) { if (obj is DataValue other) { if (Value == null && other.Value == null) return true; if (Value == null || other.Value == null) return false; switch (DataType.ToLower()) { case "float": if (Value is float f1 && other.Value is float f2) return Math.Abs(f1 - f2) < 0.000001f; break; case "bool": if (Value is bool b1 && other.Value is bool b2) return b1 == b2; break; } return Value.Equals(other.Value); } return false; } public override int GetHashCode() { return Value?.GetHashCode() ?? 0; } } }