using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.SignalR; using Haoliang.Models.Device; using Haoliang.Models.DataCollection; using Haoliang.Models.Production; using Haoliang.Models.System; using Haoliang.Data.Repositories; using Haoliang.Core.Services; namespace Haoliang.Core.Services { public interface IRealTimeService { Task BroadcastDeviceStatusAsync(); Task BroadcastProductionDataAsync(); Task BroadcastAlarmDataAsync(); Task SendDeviceUpdateAsync(int deviceId, DeviceCurrentStatus status); Task SendProductionUpdateAsync(int deviceId, ProductionRecord production); Task SendAlarmUpdateAsync(Alarm alarm); Task BroadcastSystemHealthAsync(); Task JoinDeviceGroupAsync(string connectionId, int deviceId); Task LeaveDeviceGroupAsync(string connectionId, int deviceId); Task JoinAllDevicesGroupAsync(string connectionId); Task LeaveAllDevicesGroupAsync(string connectionId); } public class RealTimeService : IRealTimeService { private readonly IHubContext _hubContext; private readonly IDeviceRepository _deviceRepository; private readonly IProductionRepository _productionRepository; private readonly IAlarmRepository _alarmRepository; private readonly ICollectionRepository _collectionRepository; private readonly ILoggerService _logger; private readonly Dictionary> _userDeviceGroups = new Dictionary>(); private readonly object _lock = new object(); public RealTimeService( IHubContext hubContext, IDeviceRepository deviceRepository, IProductionRepository productionRepository, IAlarmRepository alarmRepository, ICollectionRepository collectionRepository, ILoggerService logger) { _hubContext = hubContext; _deviceRepository = deviceRepository; _productionRepository = productionRepository; _alarmRepository = alarmRepository; _collectionRepository = collectionRepository; _logger = logger; } public async Task BroadcastDeviceStatusAsync() { try { var devices = await _deviceRepository.GetAllAsync(); var deviceStatuses = new List(); foreach (var device in devices) { var status = await GetDeviceCurrentStatusAsync(device.Id); deviceStatuses.Add(status); } await _hubContext.Clients.All.SendAsync("ReceiveDeviceStatusUpdate", deviceStatuses); _logger.LogDebug($"Broadcasted device status update for {deviceStatuses.Count} devices"); } catch (Exception ex) { _logger.LogError(ex, "Failed to broadcast device status update"); } } public async Task BroadcastProductionDataAsync() { try { var devices = await _deviceRepository.GetAllAsync(); var productionData = new Dictionary>(); foreach (var device in devices) { var todayProductions = await _productionRepository.GetByDeviceAndDateAsync(device.Id, DateTime.Today); productionData[device.Id] = todayProductions.ToList(); } await _hubContext.Clients.All.SendAsync("ReceiveProductionUpdate", productionData); _logger.LogDebug($"Broadcasted production data update for {devices.Count} devices"); } catch (Exception ex) { _logger.LogError(ex, "Failed to broadcast production data update"); } } public async Task BroadcastAlarmDataAsync() { try { var activeAlarms = await _alarmRepository.GetActiveAlarmsAsync(); var alarmData = activeAlarms.Select(a => new { a.AlarmId, a.DeviceCode, a.AlarmType, a.Severity, a.Title, a.Description, a.AlarmStatus, a.CreateTime }).ToList(); await _hubContext.Clients.All.SendAsync("ReceiveAlarmUpdate", alarmData); _logger.LogDebug($"Broadcasted alarm data update for {alarmData.Count} alarms"); } catch (Exception ex) { _logger.LogError(ex, "Failed to broadcast alarm data update"); } } public async Task SendDeviceUpdateAsync(int deviceId, DeviceCurrentStatus status) { try { await _hubContext.Clients.Group($"device_{deviceId}").SendAsync("ReceiveDeviceUpdate", status); _logger.LogDebug($"Sent device update for device {deviceId}"); } catch (Exception ex) { _logger.LogError(ex, $"Failed to send device update for device {deviceId}"); } } public async Task SendProductionUpdateAsync(int deviceId, ProductionRecord production) { try { await _hubContext.Clients.Group($"device_{deviceId}").SendAsync("ReceiveProductionUpdate", production); await _hubContext.Clients.All.SendAsync("ReceiveGlobalProductionUpdate", production); _logger.LogDebug($"Sent production update for device {deviceId}"); } catch (Exception ex) { _logger.LogError(ex, $"Failed to send production update for device {deviceId}"); } } public async Task SendAlarmUpdateAsync(Alarm alarm) { try { await _hubContext.Clients.Group($"device_{alarm.DeviceId}").SendAsync("ReceiveAlarmUpdate", alarm); await _hubContext.Clients.All.SendAsync("ReceiveGlobalAlarmUpdate", alarm); _logger.LogDebug($"Sent alarm update for alarm {alarm.AlarmId}"); } catch (Exception ex) { _logger.LogError(ex, $"Failed to send alarm update for alarm {alarm.AlarmId}"); } } public async Task BroadcastSystemHealthAsync() { try { var onlineDevices = await _deviceRepository.CountOnlineDevicesAsync(); var activeAlarms = await _alarmRepository.CountActiveAlarmsAsync(); var totalProductions = await _productionRepository.CountTodayProductionsAsync(); var healthData = new { Timestamp = DateTime.Now, OnlineDevices = onlineDevices, ActiveAlarms = activeAlarms, TotalProductions = totalProductions, SystemStatus = activeAlarms > 10 ? "Warning" : "Healthy" }; await _hubContext.Clients.All.SendAsync("ReceiveSystemHealth", healthData); _logger.LogDebug($"Broadcasted system health update"); } catch (Exception ex) { _logger.LogError(ex, "Failed to broadcast system health update"); } } public async Task JoinDeviceGroupAsync(string connectionId, int deviceId) { try { lock (_lock) { if (!_userDeviceGroups.ContainsKey(connectionId)) { _userDeviceGroups[connectionId] = new HashSet(); } _userDeviceGroups[connectionId].Add(deviceId); } await _hubContext.Groups.AddToGroupAsync(connectionId, $"device_{deviceId}"); _logger.LogDebug($"Connection {connectionId} joined device group {deviceId}"); } catch (Exception ex) { _logger.LogError(ex, $"Failed to add connection {connectionId} to device group {deviceId}"); } } public async Task LeaveDeviceGroupAsync(string connectionId, int deviceId) { try { lock (_lock) { if (_userDeviceGroups.ContainsKey(connectionId)) { _userDeviceGroups[connectionId].Remove(deviceId); if (_userDeviceGroups[connectionId].Count == 0) { _userDeviceGroups.Remove(connectionId); } } } await _hubContext.Groups.RemoveFromGroupAsync(connectionId, $"device_{deviceId}"); _logger.LogDebug($"Connection {connectionId} left device group {deviceId}"); } catch (Exception ex) { _logger.LogError(ex, $"Failed to remove connection {connectionId} from device group {deviceId}"); } } public async Task JoinAllDevicesGroupAsync(string connectionId) { try { await _hubContext.Groups.AddToGroupAsync(connectionId, "all_devices"); _logger.LogDebug($"Connection {connectionId} joined all devices group"); } catch (Exception ex) { _logger.LogError(ex, $"Failed to add connection {connectionId} to all devices group"); } } public async Task LeaveAllDevicesGroupAsync(string connectionId) { try { await _hubContext.Groups.RemoveFromGroupAsync(connectionId, "all_devices"); _logger.LogDebug($"Connection {connectionId} left all devices group"); } catch (Exception ex) { _logger.LogError(ex, $"Failed to remove connection {connectionId} from all devices group"); } } private async Task GetDeviceCurrentStatusAsync(int deviceId) { var device = await _deviceRepository.GetByIdAsync(deviceId); if (device == null) return new DeviceCurrentStatus(); var latestCollection = await _collectionRepository.GetLatestDeviceStatusAsync(deviceId); return new DeviceCurrentStatus { DeviceId = deviceId, DeviceCode = device.DeviceCode, DeviceName = device.DeviceName, Status = latestCollection?.Status ?? "Unknown", IsRunning = latestCollection?.IsRunning ?? false, NCProgram = latestCollection?.NCProgram ?? "", CumulativeCount = latestCollection?.CumulativeCount ?? 0, OperatingMode = latestCollection?.OperatingMode ?? "", RecordTime = latestCollection?.RecordTime ?? DateTime.Now }; } } // SignalR Hub for real-time communication public class RealTimeHub : Hub { private readonly IRealTimeService _realTimeService; private readonly ILoggerService _logger; public RealTimeHub(IRealTimeService realTimeService, ILoggerService logger) { _realTimeService = realTimeService; _logger = logger; } public override async Task OnConnectedAsync() { _logger.LogInformation($"Connection {Context.ConnectionId} connected to RealTimeHub"); // Automatically join all devices group for new connections await _realTimeService.JoinAllDevicesGroupAsync(Context.ConnectionId); await base.OnConnectedAsync(); } public override async Task OnDisconnectedAsync(Exception exception) { _logger.LogInformation($"Connection {Context.ConnectionId} disconnected from RealTimeHub"); // Clean up device group memberships // Note: This is a simplified cleanup - in production you'd track which groups each user was in await _realTimeService.LeaveAllDevicesGroupAsync(Context.ConnectionId); await base.OnDisconnectedAsync(exception); } // Client methods that can be called from frontend public async Task JoinDeviceGroup(int deviceId) { await _realTimeService.JoinDeviceGroupAsync(Context.ConnectionId, deviceId); await Clients.Caller.SendAsync("JoinedDeviceGroup", deviceId); } public async Task LeaveDeviceGroup(int deviceId) { await _realTimeService.LeaveDeviceGroupAsync(Context.ConnectionId, deviceId); await Clients.Caller.SendAsync("LeftDeviceGroup", deviceId); } public async Task RequestSystemHealth() { await _realTimeService.BroadcastSystemHealthAsync(); } public async Task RequestDeviceStatus() { await _realTimeService.BroadcastDeviceStatusAsync(); } public async Task RequestProductionData() { await _realTimeService.BroadcastProductionDataAsync(); } public async Task RequestAlarmData() { await _realTimeService.BroadcastAlarmDataAsync(); } } }