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#
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();
|
|
}
|
|
}
|
|
} |