FanucDeviceMonitor.cs 13.3 KB
using HslCommunication;
using HslCommunication.CNC.Fanuc;
using System.Security.Claims;
using System.Text;

namespace CNCFanucDataReading
{
    internal class FanucDeviceMonitor : IDisposable
    {
        private readonly CancellationTokenSource _cts = new();
        private readonly DaqEAlarmRecordService _daqEAlarmRecordService;
        private readonly EquipmentService _equipmentService;
        private readonly DaqClientConfigService _daqClientConfigService;
        private readonly EquipmentStatusService _equipmentStatusService;
        private readonly FanucDataService _fanucDataService;



        private bool _disposed = false;

        private List<BaseEquipment> _equipment = new();

        public FanucDeviceMonitor(string mysqlConnectionString = null)
        {

            // 初始化FreeSql服务
            if (!string.IsNullOrEmpty(mysqlConnectionString))
            {
                try
                {
                    _daqEAlarmRecordService = new DaqEAlarmRecordService(mysqlConnectionString);
                    _equipmentService = new EquipmentService(mysqlConnectionString);
                    _daqClientConfigService = new DaqClientConfigService(mysqlConnectionString);
                    _equipmentStatusService = new EquipmentStatusService(mysqlConnectionString);
                    _fanucDataService = new FanucDataService(mysqlConnectionString);

                    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 数据库连接初始化成功");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] 数据库连接初始化失败: {ex.Message}");
                }
            }
        }

        public async Task StartAsync()
        {
            _equipment = await _equipmentService.GetList();

            Console.WriteLine($"开始监控 {_equipment.Count} 台设备...");
            Console.WriteLine("\n按任意键停止监控...");

            var tasks = new List<Task>();
            //设备监听
            foreach (var equipment in _equipment)
            {
                tasks.Add(Task.Run(() => MonitorDevice(equipment, _cts.Token)));
            }

            // 启动心跳更新任务
            tasks.Add(Task.Run(() => _daqClientConfigService.UpdateHeartbeatAsync(_cts.Token)));

            // 等待退出
            Console.ReadKey();

            _cts.Cancel();

            // 等待所有任务完成
            await Task.WhenAll(tasks);

            Console.WriteLine("监控已停止");
        }

        #region BaseEquipment
        /// <summary>
        /// 监听设备
        /// </summary>
        /// <param name="equipment"></param>
        /// <param name="token"></param>
        /// <returns></returns>
        private async Task MonitorDevice(BaseEquipment equipment, CancellationToken token)
        {
            // 存储上一个循环的报警信息,用于检测报警状态变化
            var previousAlarms = new List<string>();

            while (!token.IsCancellationRequested)
            {
                try
                {
                    using var fanuc = new FanucSeries0i(equipment.Ip, equipment.Sequence.Value);
                    fanuc.ConnectTimeOut = AppConfig.FanucTimeout;
                    fanuc.TextEncoding = Encoding.GetEncoding("GBK");
                    OperateResult connect = await fanuc.ConnectServerAsync();

                    string equipmentStatus = string.Empty;

                    if (connect.IsSuccess)
                    {
                        // 读取设备数据
                        var data = await ReadDeviceDataAsync(fanuc, equipment);

                        switch (data.sysStatus)
                        {
                            case 3:
                                equipmentStatus = EquipmentStatus.Running;
                                break;
                            default:
                                equipmentStatus = EquipmentStatus.Free;
                                break;
                        }

                        //更新设备状态为Free or Running
                        await _equipmentStatusService.UpsertEquipmentStatusAsync(equipment, equipmentStatus);


                        if (data.fanucDataInfo != null)
                        {
                            // 保存到Iot数据库
                            await _fanucDataService.UpsertFanucDataAsync(equipment.EquipmentCode, equipment.EquipmentName, data.fanucDataInfo);

                            // 控制台输出
                            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {equipment.EquipmentName} 数据采集完成");
                        }

                        // 处理报警信息
                        var currentAlarms = await ProcessAlarmsAsync(fanuc, equipment, previousAlarms);
                        previousAlarms = currentAlarms;
                    }
                    else
                    {
                        //更新设备状态为离线
                        equipmentStatus = EquipmentStatus.Failure;
                        await _equipmentStatusService.UpsertEquipmentStatusAsync(equipment, equipmentStatus);

                        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {equipment.EquipmentName} 连接失败: {connect.Message}");
                    }

                    await Task.Delay(AppConfig.MonitorInterval, token);//读取间隔(毫秒)
                }
                catch (TaskCanceledException)
                {
                    break;
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {equipment.EquipmentName} 异常: {ex.Message}");
                    await Task.Delay(AppConfig.MonitorInterval, token);//读取间隔(毫秒)
                }
            }

            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {equipment.EquipmentName} 监控任务已停止");
        }

        /// <summary>
        /// 读取发那科机床数据
        /// </summary>
        /// <param name="fanuc"></param>
        /// <param name="equipment"></param>
        /// <returns></returns>
        private async Task<(FanucDataInfo fanucDataInfo, int sysStatus)> ReadDeviceDataAsync(FanucSeries0i fanuc, BaseEquipment equipment)
        {
            var data = new FanucDataInfo();
            // 读取系统状态
            var sysStatusResult = fanuc.ReadSysStatusInfo();
            int sysStatus = 0;
            if (sysStatusResult.IsSuccess)
            {
                data.SysStatusInfo = sysStatusResult.Content.ToJsonString();
                sysStatus = (int)sysStatusResult.Content.RunStatus;
            }

            // 读取坐标信息
            var coordsResult = fanuc.ReadSysAllCoors();
            if (coordsResult.IsSuccess)
            {
                data.SysAllCoors = coordsResult.Content.ToJsonString();
            }

            // 读取开机时间和运行时间
            data.BootTime = ReadTimeData(0, fanuc);
            data.RunTime = ReadTimeData(1, fanuc);

            // 读取已加工数量
            var produceCountResult = fanuc.ReadCurrentProduceCount();
            if (produceCountResult.IsSuccess)
            {
                data.ProduceCount = produceCountResult.Content;
            }

            // 读取当前的刀号
            var cutterNumberResult = fanuc.ReadCutterNumber();
            if (cutterNumberResult.IsSuccess)
            {
                data.CutterKnifeNumber = cutterNumberResult.Content;
            }

            return (data, sysStatus);
        }

        /// <summary>
        /// 处理报警信息
        /// </summary>
        /// <param name="fanuc"></param>
        /// <param name="equipment"></param>
        /// <param name="previousAlarms"></param>
        /// <returns></returns>
        private async Task<List<string>> ProcessAlarmsAsync(FanucSeries0i fanuc, BaseEquipment equipment, List<string> previousAlarms)
        {
            var currentAlarmMessages = new List<string>();
            var daqEAlarmRecords = new List<DaqEAlarmRecord>();

            // 读取报警信息
            var alarmResult = fanuc.ReadSystemAlarm();
            if (alarmResult.IsSuccess && alarmResult.Content != null && alarmResult.Content.Length > 0)
            {
                Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] {equipment.EquipmentName} 报警信息:");

                foreach (var alarm in alarmResult.Content)
                {

                    // 记录当前报警信息
                    var alarmMessage = alarm.Message?.Trim() ?? "";

                    currentAlarmMessages.Add(alarmMessage);

                    // 控制台输出
                    Console.WriteLine($"  报警号: {alarm.AlarmId}");
                    Console.WriteLine($"  报警信息: {alarmMessage}");

                    if (_daqEAlarmRecordService != null)
                    {
                        var daqEAlarmRecord = new DaqEAlarmRecord
                        {
                            Id = Guid.NewGuid(),
                            EquipmentCode = equipment.EquipmentCode,
                            EquipmentName = equipment.EquipmentName,
                            AlarmMessage = alarmMessage,
                            //ProjectCode = equipment.ProjectKeys,
                            FactoryCode = equipment.FactoryCode
                        };

                        daqEAlarmRecords.Add(daqEAlarmRecord);
                    }
                }
            }

            await SaveAndUpdateAlarms(equipment, previousAlarms, currentAlarmMessages, daqEAlarmRecords);

            return currentAlarmMessages;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="equipment">当前机床设备</param>
        /// <param name="previousAlarms">上一次采集的报警信息集合</param>
        /// <param name="currentAlarmMessages">本次采集的报警信息集合</param>
        /// <param name="daqEAlarmRecords">需要保存到Iot数据库的报警数据实体</param>
        /// <returns></returns>
        private async Task SaveAndUpdateAlarms(BaseEquipment equipment, List<string> previousAlarms, List<string> currentAlarmMessages, List<DaqEAlarmRecord> daqEAlarmRecords)
        {
            // 保存报警信息到数据库
            if (daqEAlarmRecords.Count > 0 && _daqEAlarmRecordService != null)
            {
                await _daqEAlarmRecordService.SaveBatchAlarms(daqEAlarmRecords);
            }

            // 检测报警状态变化:如果之前有报警,现在没有了,标记为已解除
            //这样会有一个问题,如果在报警信息存在的情况关闭了程序,那这个报警信息在数据库就不会解除了。
            //if (_daqEAlarmRecordService != null)
            //{
            //    foreach (var previousAlarmMessage in previousAlarms)
            //    {
            //        if (!currentAlarmMessages.Contains(previousAlarmMessage))
            //        {
            //            // 报警已解除
            //            await _daqEAlarmRecordService.UpdateAlarmIsEnd(equipment.EquipmentCode, previousAlarmMessage);
            //        }
            //    }
            //}

            // 关键修改:基于数据库状态解除报警,而不是基于内存中的previousAlarms
            if (_daqEAlarmRecordService != null)
            {
                // 获取数据库中该设备所有未解除的报警
                var dbUnresolvedAlarms = await _daqEAlarmRecordService
                    .GetUnresolvedAlarmsByEquipmentAsync(equipment.EquipmentCode);

                if (dbUnresolvedAlarms != null && dbUnresolvedAlarms.Any())
                {
                    // 找出数据库中已不存在的报警(需要解除)
                    var alarmsToResolve = dbUnresolvedAlarms
                        .Where(dbAlarm => !currentAlarmMessages.Contains(dbAlarm.AlarmMessage))
                        .Select(dbAlarm => dbAlarm.AlarmMessage)
                        .ToList();

                    if (alarmsToResolve.Any())
                    {
                        // 批量更新为已解除
                        await _daqEAlarmRecordService.BatchResolveAlarmsAsync(
                            equipment.EquipmentCode,
                            alarmsToResolve);
                    }
                }
            }
        }


        #endregion

        /// <summary>
        /// 获取机床时间信息
        /// </summary>
        /// <param name="type">0 开机时间,1 运行时间,2 切割时间,3 空闲时间</param>
        /// <param name="fanuc"></param>
        private string ReadTimeData(int type, FanucSeries0i fanuc)
        {
            OperateResult<long> read = fanuc.ReadTimeData(type);
            if (read.IsSuccess)
            {
                return $"{read.Content / 3600} H {read.Content % 3600 / 60} M {read.Content % 3600 % 60} S";
            }
            else
            {
                return "Read Failed:" + read.ToMessageShowString();
            }
        }


        public void Dispose()
        {
            if (!_disposed)
            {
                _cts?.Cancel();
                _cts?.Dispose();
                _equipmentService?.Dispose();
                _disposed = true;
            }
        }
    }
}