You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

356 lines
14 KiB
C#

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<RealTimeHub> _hubContext;
private readonly IDeviceRepository _deviceRepository;
private readonly IProductionRepository _productionRepository;
private readonly IAlarmRepository _alarmRepository;
private readonly ICollectionRepository _collectionRepository;
private readonly ILoggerService _logger;
private readonly Dictionary<string, HashSet<int>> _userDeviceGroups = new Dictionary<string, HashSet<int>>();
private readonly object _lock = new object();
public RealTimeService(
IHubContext<RealTimeHub> 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<DeviceCurrentStatus>();
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<int, List<ProductionRecord>>();
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<int>();
}
_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<DeviceCurrentStatus> 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();
}
}
}