2023-11-29 20:28:11 +00:00
using System.Net ;
using System.Net.Sockets ;
2023-11-08 10:59:15 +00:00
using System.Net.WebSockets ;
using System.Text ;
using System.Text.Json ;
2023-11-29 20:28:11 +00:00
using InnovEnergy.App.Backend.Database ;
2024-08-12 07:48:16 +00:00
using InnovEnergy.App.Backend.DataTypes ;
2024-11-28 13:43:47 +00:00
using InnovEnergy.Lib.Utils ;
2023-11-08 10:59:15 +00:00
namespace InnovEnergy.App.Backend.Websockets ;
public static class WebsocketManager
{
2024-07-23 12:52:10 +00:00
public static Dictionary < Int64 , InstallationInfo > InstallationConnections = new Dictionary < Int64 , InstallationInfo > ( ) ;
2023-11-08 10:59:15 +00:00
2025-01-14 12:56:12 +00:00
//Every 1 minute, check the timestamp of the latest received message for every installation.
//If the difference between the two timestamps is more than two minutes, we consider this Salimax installation unavailable.
2024-08-12 07:48:16 +00:00
public static async Task MonitorSalimaxInstallationTable ( )
2023-11-22 15:49:47 +00:00
{
while ( true ) {
lock ( InstallationConnections ) {
2024-12-16 14:03:27 +00:00
Console . WriteLine ( "MONITOR SALIMAX INSTALLATIONS\n" ) ;
2023-11-22 15:49:47 +00:00
foreach ( var installationConnection in InstallationConnections ) {
2024-12-16 14:03:27 +00:00
2025-01-14 12:56:12 +00:00
if ( installationConnection . Value . Product = = ( int ) ProductType . Salimax & & ( DateTime . Now - installationConnection . Value . Timestamp ) > TimeSpan . FromMinutes ( 2 ) ) {
2024-12-16 14:03:27 +00:00
2025-01-14 12:56:12 +00:00
// Console.WriteLine("Installation ID is "+installationConnection.Key);
// Console.WriteLine("installationConnection.Value.Timestamp is "+installationConnection.Value.Timestamp);
// Console.WriteLine("diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
2024-12-16 14:03:27 +00:00
2025-01-14 12:56:12 +00:00
installationConnection . Value . Status = ( int ) StatusType . Offline ;
Installation installation = Db . Installations . FirstOrDefault ( f = > f . Product = = ( int ) ProductType . Salimax & & f . Id = = installationConnection . Key ) ;
installation . Status = ( int ) StatusType . Offline ;
2024-11-28 13:43:47 +00:00
installation . Apply ( Db . Update ) ;
2023-11-22 15:49:47 +00:00
if ( installationConnection . Value . Connections . Count > 0 ) { InformWebsocketsForInstallation ( installationConnection . Key ) ; }
}
}
2024-12-16 14:03:27 +00:00
Console . WriteLine ( "FINISHED MONITORING SALIMAX INSTALLATIONS\n" ) ;
2023-11-22 15:49:47 +00:00
}
2024-12-16 14:03:27 +00:00
await Task . Delay ( TimeSpan . FromMinutes ( 1 ) ) ;
2023-11-22 15:49:47 +00:00
}
}
2024-08-12 07:48:16 +00:00
2025-01-14 12:56:12 +00:00
//Every 1 minute, check the timestamp of the latest received message for every installation.
//If the difference between the two timestamps is more than 1 hour, we consider this Salidomo installation unavailable.
2024-08-12 07:48:16 +00:00
public static async Task MonitorSalidomoInstallationTable ( )
{
while ( true ) {
2024-12-16 14:03:27 +00:00
Console . WriteLine ( "TRY TO LOCK FOR MONITOR SALIDOMO INSTALLATIONS\n" ) ;
2024-08-12 07:48:16 +00:00
lock ( InstallationConnections ) {
2024-12-16 14:03:27 +00:00
Console . WriteLine ( "MONITOR SALIDOMO INSTALLATIONS\n" ) ;
2024-08-12 07:48:16 +00:00
foreach ( var installationConnection in InstallationConnections ) {
2024-12-16 14:03:27 +00:00
Console . WriteLine ( "Installation ID is " + installationConnection . Key ) ;
2025-01-14 12:56:12 +00:00
if ( installationConnection . Value . Product = = ( int ) ProductType . Salidomo & & ( DateTime . Now - installationConnection . Value . Timestamp ) > TimeSpan . FromMinutes ( 60 ) )
2024-08-16 07:40:19 +00:00
{
2024-08-30 12:33:47 +00:00
// Console.WriteLine("Installation ID is "+installationConnection.Key);
// Console.WriteLine("installationConnection.Value.Timestamp is "+installationConnection.Value.Timestamp);
// Console.WriteLine("diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
2024-08-16 07:40:19 +00:00
2025-01-14 12:56:12 +00:00
Installation installation = Db . Installations . FirstOrDefault ( f = > f . Product = = ( int ) ProductType . Salidomo & & f . Id = = installationConnection . Key ) ;
installation . Status = ( int ) StatusType . Offline ;
2024-11-28 13:43:47 +00:00
installation . Apply ( Db . Update ) ;
2025-01-14 12:56:12 +00:00
installationConnection . Value . Status = ( int ) StatusType . Offline ;
2024-08-12 07:48:16 +00:00
if ( installationConnection . Value . Connections . Count > 0 ) { InformWebsocketsForInstallation ( installationConnection . Key ) ; }
}
}
2024-12-16 14:03:27 +00:00
Console . WriteLine ( "FINISHED WITH UPDATING\n" ) ;
2024-08-12 07:48:16 +00:00
}
2024-12-16 14:03:27 +00:00
await Task . Delay ( TimeSpan . FromMinutes ( 1 ) ) ;
2024-08-12 07:48:16 +00:00
}
}
2023-11-22 15:49:47 +00:00
//Inform all the connected websockets regarding installation "installationId"
2024-07-23 12:52:10 +00:00
public static void InformWebsocketsForInstallation ( Int64 installationId )
2023-11-22 15:49:47 +00:00
{
2024-07-23 12:52:10 +00:00
var installation = Db . GetInstallationById ( installationId ) ;
2023-11-22 15:49:47 +00:00
var installationConnection = InstallationConnections [ installationId ] ;
Console . WriteLine ( "Update all the connected websockets for installation " + installationId ) ;
2024-07-23 12:52:10 +00:00
2023-11-22 15:49:47 +00:00
var jsonObject = new
{
id = installationId ,
2024-07-23 12:52:10 +00:00
status = installationConnection . Status ,
testingMode = installation . TestingMode
2023-11-22 15:49:47 +00:00
} ;
string jsonString = JsonSerializer . Serialize ( jsonObject ) ;
byte [ ] dataToSend = Encoding . UTF8 . GetBytes ( jsonString ) ;
foreach ( var connection in installationConnection . Connections )
{
connection . SendAsync (
new ArraySegment < byte > ( dataToSend , 0 , dataToSend . Length ) ,
WebSocketMessageType . Text ,
true , // Indicates that this is the end of the message
CancellationToken . None
) ;
}
}
2023-11-08 10:59:15 +00:00
public static async Task HandleWebSocketConnection ( WebSocket currentWebSocket )
{
var buffer = new byte [ 4096 ] ;
try
{
while ( currentWebSocket . State = = WebSocketState . Open )
{
//Listen for incoming messages on this WebSocket
var result = await currentWebSocket . ReceiveAsync ( buffer , CancellationToken . None ) ;
2023-11-13 16:51:36 +00:00
2023-11-08 10:59:15 +00:00
if ( result . MessageType ! = WebSocketMessageType . Text )
continue ;
var message = Encoding . UTF8 . GetString ( buffer , 0 , result . Count ) ;
var installationIds = JsonSerializer . Deserialize < int [ ] > ( message ) ;
2023-11-13 16:51:36 +00:00
//This is a ping message to keep the connection alive, reply with a pong
if ( installationIds [ 0 ] = = - 1 )
{
var jsonObject = new
{
id = - 1 ,
status = - 1
} ;
2024-04-02 12:36:43 +00:00
var jsonString = JsonSerializer . Serialize ( jsonObject ) ;
2023-11-13 16:51:36 +00:00
var dataToSend = Encoding . UTF8 . GetBytes ( jsonString ) ;
currentWebSocket . SendAsync ( dataToSend ,
WebSocketMessageType . Text ,
true ,
CancellationToken . None
) ;
continue ;
}
2025-01-14 12:56:12 +00:00
//Received a new message from this websocket.
//We have a HandleWebSocketConnection per connected frontend
2023-11-08 10:59:15 +00:00
lock ( InstallationConnections )
{
2024-08-12 07:48:16 +00:00
List < WebsocketMessage > dataToSend = new List < WebsocketMessage > ( ) ;
2023-11-08 10:59:15 +00:00
//Each front-end will send the list of the installations it wants to access
//If this is a new key (installation id), initialize the list for this key and then add the websocket object for this client
//Then, report the status of each requested installation to the front-end that created the websocket connection
2023-11-22 15:49:47 +00:00
foreach ( var installationId in installationIds )
{
2024-07-23 12:52:10 +00:00
var installation = Db . GetInstallationById ( installationId ) ;
2023-11-22 15:49:47 +00:00
if ( ! InstallationConnections . ContainsKey ( installationId ) )
2023-11-08 10:59:15 +00:00
{
2025-01-14 12:56:12 +00:00
//Since we keep all the changes to the database, in case that the backend reboots, we need to update the in-memory data structure.
//Thus, if the status is -1, we put an old timestamp, otherwise, we put the most recent timestamp.
//We store everything to the database, because when the backend reboots, we do not want to wait until all the installations send the heartbit messages.
//We want the in memory data structure to be up to date immediately.
2023-11-22 15:49:47 +00:00
InstallationConnections [ installationId ] = new InstallationInfo
2023-11-08 10:59:15 +00:00
{
2024-12-16 14:03:27 +00:00
Status = installation . Status ,
2025-01-14 12:56:12 +00:00
Timestamp = installation . Status = = ( int ) StatusType . Offline ? DateTime . Now . AddDays ( - 1 ) : DateTime . Now ,
2024-08-12 07:48:16 +00:00
Product = installation . Product
2023-11-22 15:49:47 +00:00
} ;
}
2023-11-08 10:59:15 +00:00
2023-11-22 15:49:47 +00:00
InstallationConnections [ installationId ] . Connections . Add ( currentWebSocket ) ;
2023-11-08 10:59:15 +00:00
2024-08-12 07:48:16 +00:00
var jsonObject = new WebsocketMessage
2023-11-22 15:49:47 +00:00
{
id = installationId ,
2024-07-23 12:52:10 +00:00
status = InstallationConnections [ installationId ] . Status ,
testingMode = installation . TestingMode
2023-11-22 15:49:47 +00:00
} ;
2024-08-12 07:48:16 +00:00
dataToSend . Add ( jsonObject ) ;
2024-12-16 14:03:27 +00:00
2023-11-22 15:49:47 +00:00
}
2024-08-12 07:48:16 +00:00
var jsonString = JsonSerializer . Serialize ( dataToSend ) ;
var encodedDataToSend = Encoding . UTF8 . GetBytes ( jsonString ) ;
currentWebSocket . SendAsync ( encodedDataToSend ,
WebSocketMessageType . Text ,
true , // Indicates that this is the end of the message
CancellationToken . None
) ;
2023-11-08 10:59:15 +00:00
Console . WriteLine ( "Printing installation connection list" ) ;
Console . WriteLine ( "----------------------------------------------" ) ;
foreach ( var installationConnection in InstallationConnections )
{
Console . WriteLine ( "Installation ID: " + installationConnection . Key + " Number of Connections: " + installationConnection . Value . Connections . Count ) ;
}
Console . WriteLine ( "----------------------------------------------" ) ;
}
}
lock ( InstallationConnections )
{
//When the front-end terminates the connection, the following code will be executed
Console . WriteLine ( "The connection has been terminated" ) ;
foreach ( var installationConnection in InstallationConnections )
{
if ( installationConnection . Value . Connections . Contains ( currentWebSocket ) )
{
installationConnection . Value . Connections . Remove ( currentWebSocket ) ;
}
}
}
await currentWebSocket . CloseAsync ( WebSocketCloseStatus . NormalClosure , "Connection closed by server" , CancellationToken . None ) ;
lock ( InstallationConnections )
{
//Print the installationConnections dictionary after deleting a websocket
Console . WriteLine ( "Print the installation connections list after deleting a websocket" ) ;
Console . WriteLine ( "----------------------------------------------" ) ;
foreach ( var installationConnection in InstallationConnections )
{
Console . WriteLine ( "Installation ID: " + installationConnection . Key + " Number of Connections: " + installationConnection . Value . Connections . Count ) ;
}
Console . WriteLine ( "----------------------------------------------" ) ;
}
}
catch ( Exception ex )
{
Console . WriteLine ( "WebSocket error: " + ex . Message ) ;
}
}
}