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.

676 lines
25 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.SignalR;
using Haoliang.Core.Services;
using Haoliang.Models.Models.Device;
using Haoliang.Models.Models.Production;
using Haoliang.Models.Models.System;
using Haoliang.Models.Common;
namespace Haoliang.Core.Services
{
public interface IRealTimeService
{
/// <summary>
/// Connect a client to WebSocket hub
/// </summary>
Task ConnectClientAsync(string connectionId, string userId, string clientType);
/// <summary>
/// Disconnect a client
/// </summary>
Task DisconnectClientAsync(string connectionId);
/// <summary>
/// Join a device monitoring group
/// </summary>
Task JoinDeviceGroupAsync(string connectionId, int deviceId);
/// <summary>
/// Leave a device monitoring group
/// </summary>
Task LeaveDeviceGroupAsync(string connectionId, int deviceId);
/// <summary>
/// Join a dashboard group
/// </summary>
Task JoinDashboardGroupAsync(string connectionId, string dashboardId);
/// <summary>
/// Leave a dashboard group
/// </summary>
Task LeaveDashboardGroupAsync(string connectionId, string dashboardId);
/// <summary>
/// Broadcast device status update
/// </summary>
Task BroadcastDeviceStatusAsync(DeviceStatusUpdate statusUpdate);
/// <summary>
/// Broadcast production update
/// </summary>
Task BroadcastProductionUpdateAsync(ProductionUpdate productionUpdate);
/// <summary>
/// Broadcast alert update
/// </summary>
Task BroadcastAlertAsync(AlertUpdate alertUpdate);
/// <summary>
/// Send system notification
/// </summary>
Task SendSystemNotificationAsync(SystemNotification notification);
/// <summary>
/// Send real-time dashboard data
/// </summary>
Task SendDashboardUpdateAsync(DashboardUpdate dashboardUpdate);
/// <summary>
/// Send command to specific client
/// </summary>
Task SendCommandToClientAsync(string connectionId, RealTimeCommand command);
/// <summary>
/// Broadcast command to all clients
/// </summary>
Task BroadcastCommandAsync(RealTimeCommand command);
/// <summary>
/// Get connected clients count
/// </summary>
Task<int> GetConnectedClientsCountAsync();
/// <summary>
/// Get connected clients by type
/// </summary>
Task<List<ClientInfo>> GetConnectedClientsByTypeAsync(string clientType);
/// <summary>
/// Get device monitoring status
/// </summary>
Task<DeviceMonitoringStatus> GetDeviceMonitoringStatusAsync(int deviceId);
/// <summary>
/// Start data streaming for device
/// </summary>
Task StartDeviceStreamingAsync(int deviceId, int intervalMs = 1000);
/// <summary>
/// Stop data streaming for device
/// </summary>
Task StopDeviceStreamingAsync(int deviceId);
/// <summary>
/// Get active streaming devices
/// </summary>
Task<List<int>> GetActiveStreamingDevicesAsync();
}
public class RealTimeService : IRealTimeService
{
private readonly IHubContext<RealTimeHub> _hubContext;
private readonly IDeviceCollectionService _deviceCollectionService;
private readonly IProductionService _productionService;
private readonly IAlarmService _alarmService;
private readonly ICacheService _cacheService;
private readonly ConcurrentDictionary<string, ClientInfo> _connectedClients = new ConcurrentDictionary<string, ClientInfo>();
private readonly ConcurrentDictionary<int, DeviceStreamingInfo> _deviceStreaming = new ConcurrentDictionary<int, DeviceStreamingInfo>();
private readonly Timer _deviceStatusTimer;
private readonly Timer _productionTimer;
public RealTimeService(
IHubContext<RealTimeHub> hubContext,
IDeviceCollectionService deviceCollectionService,
IProductionService productionService,
IAlarmService alarmService,
ICacheService cacheService)
{
_hubContext = hubContext;
_deviceCollectionService = deviceCollectionService;
_productionService = productionService;
_alarmService = alarmService;
_cacheService = cacheService;
// Start timers for periodic updates
_deviceStatusTimer = new Timer(UpdateDeviceStatuses, null, TimeSpan.Zero, TimeSpan.FromSeconds(30));
_productionTimer = new Timer(UpdateProductionData, null, TimeSpan.Zero, TimeSpan.FromSeconds(60));
}
public async Task ConnectClientAsync(string connectionId, string userId, string clientType)
{
var clientInfo = new ClientInfo
{
ConnectionId = connectionId,
UserId = userId,
ClientType = clientType,
ConnectedAt = DateTime.UtcNow,
LastActivity = DateTime.UtcNow,
Groups = new HashSet<string>(),
DeviceGroups = new HashSet<int>()
};
_connectedClients.AddOrUpdate(connectionId, clientInfo, (key, existing) => clientInfo);
await _hubContext.Clients.Client(connectionId).SendAsync("ClientConnected", new
{
ClientId = connectionId,
UserId = userId,
ClientType = clientType,
Timestamp = DateTime.UtcNow
});
}
public async Task DisconnectClientAsync(string connectionId)
{
if (_connectedClients.TryRemove(connectionId, out var clientInfo))
{
// Remove from all groups
foreach (var group in clientInfo.Groups)
{
await _hubContext.Groups.RemoveFromGroupAsync(connectionId, group);
}
foreach (var deviceId in clientInfo.DeviceGroups)
{
await _hubContext.Groups.RemoveFromGroupAsync(connectionId, $"device_{deviceId}");
}
// Notify other clients
await _hubContext.Clients.AllExcept(connectionId).SendAsync("ClientDisconnected", new
{
ClientId = connectionId,
UserId = clientInfo.UserId,
Timestamp = DateTime.UtcNow
});
}
}
public async Task JoinDeviceGroupAsync(string connectionId, int deviceId)
{
if (_connectedClients.TryGetValue(connectionId, out var clientInfo))
{
clientInfo.DeviceGroups.Add(deviceId);
clientInfo.LastActivity = DateTime.UtcNow;
await _hubContext.Groups.AddToGroupAsync(connectionId, $"device_{deviceId}");
// Send current device status
var deviceStatus = await _deviceCollectionService.GetDeviceCurrentStatusAsync(deviceId);
await _hubContext.Clients.Client(connectionId).SendAsync("DeviceStatusUpdated", new
{
DeviceId = deviceId,
Status = deviceStatus.Status,
Timestamp = DateTime.UtcNow
});
}
}
public async Task LeaveDeviceGroupAsync(string connectionId, int deviceId)
{
if (_connectedClients.TryGetValue(connectionId, out var clientInfo))
{
clientInfo.DeviceGroups.Remove(deviceId);
await _hubContext.Groups.RemoveFromGroupAsync(connectionId, $"device_{deviceId}");
}
}
public async Task JoinDashboardGroupAsync(string connectionId, string dashboardId)
{
if (_connectedClients.TryGetValue(connectionId, out var clientInfo))
{
clientInfo.Groups.Add($"dashboard_{dashboardId}");
clientInfo.LastActivity = DateTime.UtcNow;
await _hubContext.Groups.AddToGroupAsync(connectionId, $"dashboard_{dashboardId}");
// Send current dashboard data
var dashboardUpdate = await GetDashboardUpdateAsync();
await _hubContext.Clients.Client(connectionId).SendAsync("DashboardUpdated", dashboardUpdate);
}
}
public async Task LeaveDashboardGroupAsync(string connectionId, string dashboardId)
{
if (_connectedClients.TryGetValue(connectionId, out var clientInfo))
{
clientInfo.Groups.Remove($"dashboard_{dashboardId}");
await _hubContext.Groups.RemoveFromGroupAsync(connectionId, $"dashboard_{dashboardId}");
}
}
public async Task BroadcastDeviceStatusAsync(DeviceStatusUpdate statusUpdate)
{
await _hubContext.Clients.Group($"device_{statusUpdate.DeviceId}").SendAsync("DeviceStatusUpdated", statusUpdate);
// Also broadcast to dashboard groups
await _hubContext.Clients.Group("dashboard").SendAsync("DeviceStatusUpdated", statusUpdate);
}
public async Task BroadcastProductionUpdateAsync(ProductionUpdate productionUpdate)
{
await _hubContext.Clients.Group($"device_{productionUpdate.DeviceId}").SendAsync("ProductionUpdated", productionUpdate);
// Also broadcast to dashboard groups
await _hubContext.Clients.Group("dashboard").SendAsync("ProductionUpdated", productionUpdate);
}
public async Task BroadcastAlertAsync(AlertUpdate alertUpdate)
{
await _hubContext.Clients.Group("dashboard").SendAsync("AlertUpdated", alertUpdate);
await _hubContext.Clients.Group("alerts").SendAsync("AlertUpdated", alertUpdate);
// Send to specific device groups if alert is device-specific
if (alertUpdate.DeviceId.HasValue)
{
await _hubContext.Clients.Group($"device_{alertUpdate.DeviceId.Value}").SendAsync("AlertUpdated", alertUpdate);
}
}
public async Task SendSystemNotificationAsync(SystemNotification notification)
{
await _hubContext.Clients.Group("notifications").SendAsync("SystemNotification", notification);
}
public async Task SendDashboardUpdateAsync(DashboardUpdate dashboardUpdate)
{
await _hubContext.Clients.Group("dashboard").SendAsync("DashboardUpdated", dashboardUpdate);
}
public async Task SendCommandToClientAsync(string connectionId, RealTimeCommand command)
{
await _hubContext.Clients.Client(connectionId).SendAsync("Command", command);
}
public async Task BroadcastCommandAsync(RealTimeCommand command)
{
await _hubContext.Clients.All.SendAsync("Command", command);
}
public async Task<int> GetConnectedClientsCountAsync()
{
// Clean up inactive clients
var cutoffTime = DateTime.UtcNow.AddMinutes(-5);
var inactiveClients = _connectedClients.Values.Where(c => c.LastActivity < cutoffTime).ToList();
foreach (var client in inactiveClients)
{
await DisconnectClientAsync(client.ConnectionId);
}
return _connectedClients.Count;
}
public async Task<List<ClientInfo>> GetConnectedClientsByTypeAsync(string clientType)
{
return _connectedClients.Values
.Where(c => c.ClientType.Equals(clientType, StringComparison.OrdinalIgnoreCase))
.ToList();
}
public async Task<DeviceMonitoringStatus> GetDeviceMonitoringStatusAsync(int deviceId)
{
var streamingInfo = _deviceStreaming.GetValueOrDefault(deviceId);
var monitoringClients = _connectedClients.Values
.Count(c => c.DeviceGroups.Contains(deviceId));
return new DeviceMonitoringStatus
{
DeviceId = deviceId,
IsStreaming = streamingInfo != null,
StreamingIntervalMs = streamingInfo?.IntervalMs ?? 0,
MonitoringClients = monitoringClients,
StreamingStartedAt = streamingInfo?.StartedAt,
LastStreamingUpdate = streamingInfo?.LastUpdate
};
}
public async Task StartDeviceStreamingAsync(int deviceId, int intervalMs = 1000)
{
if (!_deviceStreaming.ContainsKey(deviceId))
{
var streamingInfo = new DeviceStreamingInfo
{
DeviceId = deviceId,
IntervalMs = intervalMs,
StartedAt = DateTime.UtcNow,
LastUpdate = DateTime.UtcNow,
IsRunning = true
};
_deviceStreaming.AddOrUpdate(deviceId, streamingInfo, (key, existing) => streamingInfo);
// Start streaming task
Task.Run(() => StreamDeviceData(deviceId, intervalMs));
}
}
public async Task StopDeviceStreamingAsync(int deviceId)
{
if (_deviceStreaming.TryRemove(deviceId, out var streamingInfo))
{
streamingInfo.IsRunning = false;
}
}
public async Task<List<int>> GetActiveStreamingDevicesAsync()
{
return _deviceStreaming.Values
.Where(s => s.IsRunning)
.Select(s => s.DeviceId)
.ToList();
}
#region Private Methods
private void UpdateDeviceStatuses(object state)
{
Task.Run(async () =>
{
try
{
var activeDevices = await _deviceCollectionService.GetAllActiveDevicesAsync();
foreach (var device in activeDevices)
{
var status = await _deviceCollectionService.GetDeviceCurrentStatusAsync(device.Id);
var statusUpdate = new DeviceStatusUpdate
{
DeviceId = device.Id,
DeviceName = device.Name,
Status = status.Status,
CurrentProgram = status.CurrentProgram,
Runtime = status.Runtime,
Timestamp = DateTime.UtcNow
};
await BroadcastDeviceStatusAsync(statusUpdate);
}
}
catch (Exception ex)
{
// Log error
Console.WriteLine($"Error updating device statuses: {ex.Message}");
}
});
}
private void UpdateProductionData(object state)
{
Task.Run(async () =>
{
try
{
var date = DateTime.Today;
var devices = await _deviceCollectionService.GetAllActiveDevicesAsync();
foreach (var device in devices)
{
var production = await _productionService.GetDeviceProductionForDateAsync(device.Id, date);
var productionUpdate = new ProductionUpdate
{
DeviceId = device.Id,
DeviceName = device.Name,
Quantity = production,
Timestamp = DateTime.UtcNow
};
await BroadcastProductionUpdateAsync(productionUpdate);
}
}
catch (Exception ex)
{
// Log error
Console.WriteLine($"Error updating production data: {ex.Message}");
}
});
}
private async Task StreamDeviceData(int deviceId, int intervalMs)
{
var streamingInfo = _deviceStreaming.GetValueOrDefault(deviceId);
if (streamingInfo == null || !streamingInfo.IsRunning)
return;
try
{
while (streamingInfo.IsRunning)
{
try
{
// Get current device status
var status = await _deviceCollectionService.GetDeviceCurrentStatusAsync(deviceId);
// Get current production data
var production = await _productionService.GetDeviceProductionForDateAsync(deviceId, DateTime.Today);
// Create streaming message
var streamingMessage = new DeviceStreamingMessage
{
DeviceId = deviceId,
DeviceName = status.DeviceName,
Status = status.Status,
CurrentProgram = status.CurrentProgram,
Runtime = status.Runtime,
Quantity = production,
Timestamp = DateTime.UtcNow,
IntervalMs = intervalMs
};
// Send to device group
await _hubContext.Clients.Group($"device_{deviceId}").SendAsync("DeviceStreamingData", streamingMessage);
// Update last streaming time
streamingInfo.LastUpdate = DateTime.UtcNow;
}
catch (Exception ex)
{
// Log error but continue streaming
Console.WriteLine($"Error streaming device {deviceId} data: {ex.Message}");
}
await Task.Delay(intervalMs);
}
}
catch (Exception ex)
{
Console.WriteLine($"Device streaming task for device {deviceId} failed: {ex.Message}");
}
}
private async Task<DashboardUpdate> GetDashboardUpdateAsync()
{
// Get dashboard summary from cache or service
var dashboardSummary = await _cacheService.GetOrSetDashboardSummaryAsync(DateTime.Today,
() => _productionService.GetDashboardSummaryAsync(new DashboardFilter { Date = DateTime.Today }));
return new DashboardUpdate
{
Timestamp = DateTime.UtcNow,
TotalDevices = dashboardSummary.TotalDevices,
ActiveDevices = dashboardSummary.ActiveDevices,
OfflineDevices = dashboardSummary.OfflineDevices,
TotalProductionToday = dashboardSummary.TotalProductionToday,
TotalProductionThisWeek = dashboardSummary.TotalProductionThisWeek,
TotalProductionThisMonth = dashboardSummary.TotalProductionThisMonth,
OverallEfficiency = dashboardSummary.OverallEfficiency,
QualityRate = dashboardSummary.QualityRate,
DeviceSummaries = dashboardSummary.DeviceSummaries
};
}
#endregion
}
#region Supporting Models
public class RealTimeHub : Hub
{
private readonly IRealTimeService _realTimeService;
public RealTimeHub(IRealTimeService realTimeService)
{
_realTimeService = realTimeService;
}
public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
}
public override async Task OnDisconnectedAsync(Exception exception)
{
await base.OnDisconnectedAsync(exception);
}
public async Task JoinDeviceGroup(int deviceId)
{
await _realTimeService.JoinDeviceGroupAsync(Context.ConnectionId, deviceId);
await Clients.Caller.SendAsync("JoinedDeviceGroup", new { DeviceId = deviceId });
}
public async Task LeaveDeviceGroup(int deviceId)
{
await _realTimeService.LeaveDeviceGroupAsync(Context.ConnectionId, deviceId);
await Clients.Caller.SendAsync("LeftDeviceGroup", new { DeviceId = deviceId });
}
public async Task JoinDashboardGroup(string dashboardId)
{
await _realTimeService.JoinDashboardGroupAsync(Context.ConnectionId, dashboardId);
await Clients.Caller.SendAsync("JoinedDashboardGroup", new { DashboardId = dashboardId });
}
public async Task LeaveDashboardGroup(string dashboardId)
{
await _realTimeService.LeaveDashboardGroupAsync(Context.ConnectionId, dashboardId);
await Clients.Caller.SendAsync("LeftDashboardGroup", new { DashboardId = dashboardId });
}
public async Task RequestDeviceStreaming(int deviceId, int intervalMs = 1000)
{
await _realTimeService.StartDeviceStreamingAsync(deviceId, intervalMs);
await Clients.Caller.SendAsync("DeviceStreamingStarted", new { DeviceId = deviceId, IntervalMs = intervalMs });
}
public async Task StopDeviceStreaming(int deviceId)
{
await _realTimeService.StopDeviceStreamingAsync(deviceId);
await Clients.Caller.SendAsync("DeviceStreamingStopped", new { DeviceId = deviceId });
}
public async Task Ping()
{
await Clients.Caller.SendAsync("Pong", new { Timestamp = DateTime.UtcNow });
}
}
public class ClientInfo
{
public string ConnectionId { get; set; }
public string UserId { get; set; }
public string ClientType { get; set; }
public DateTime ConnectedAt { get; set; }
public DateTime LastActivity { get; set; }
public HashSet<string> Groups { get; set; }
public HashSet<int> DeviceGroups { get; set; }
}
public class DeviceStreamingInfo
{
public int DeviceId { get; set; }
public int IntervalMs { get; set; }
public DateTime StartedAt { get; set; }
public DateTime LastUpdate { get; set; }
public bool IsRunning { get; set; }
}
public class DeviceStatusUpdate
{
public int DeviceId { get; set; }
public string DeviceName { get; set; }
public DeviceStatus Status { get; set; }
public string CurrentProgram { get; set; }
public TimeSpan Runtime { get; set; }
public DateTime Timestamp { get; set; }
}
public class ProductionUpdate
{
public int DeviceId { get; set; }
public string DeviceName { get; set; }
public decimal Quantity { get; set; }
public DateTime Timestamp { get; set; }
}
public class AlertUpdate
{
public int? DeviceId { get; set; }
public string DeviceName { get; set; }
public string AlertType { get; set; }
public string Message { get; set; }
public DateTime Timestamp { get; set; }
public bool IsResolved { get; set; }
}
public class SystemNotification
{
public string NotificationType { get; set; }
public string Title { get; set; }
public string Message { get; set; }
public DateTime Timestamp { get; set; }
public Dictionary<string, object> Data { get; set; }
}
public class DashboardUpdate
{
public DateTime Timestamp { get; set; }
public int TotalDevices { get; set; }
public int ActiveDevices { get; set; }
public int OfflineDevices { get; set; }
public decimal TotalProductionToday { get; set; }
public decimal TotalProductionThisWeek { get; set; }
public decimal TotalProductionThisMonth { get; set; }
public decimal OverallEfficiency { get; set; }
public decimal QualityRate { get; set; }
public List<DeviceSummary> DeviceSummaries { get; set; }
}
public class RealTimeCommand
{
public string Command { get; set; }
public object Parameters { get; set; }
public DateTime Timestamp { get; set; }
public string CommandType { get; set; }
}
public class DeviceStreamingMessage
{
public int DeviceId { get; set; }
public string DeviceName { get; set; }
public DeviceStatus Status { get; set; }
public string CurrentProgram { get; set; }
public TimeSpan Runtime { get; set; }
public decimal Quantity { get; set; }
public DateTime Timestamp { get; set; }
public int IntervalMs { get; set; }
}
public class DeviceMonitoringStatus
{
public int DeviceId { get; set; }
public bool IsStreaming { get; set; }
public int StreamingIntervalMs { get; set; }
public int MonitoringClients { get; set; }
public DateTime? StreamingStartedAt { get; set; }
public DateTime? LastStreamingUpdate { get; set; }
}
#endregion
}