diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index 5e27a6256..e01f2481c 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -98,6 +98,8 @@ public class Controller : ControllerBase return Db.Errors .Where(error => error.InstallationId == id) + .OrderByDescending(error => error.Date) + .ThenByDescending(error => error.Time) .ToList(); } @@ -115,6 +117,8 @@ public class Controller : ControllerBase return Db.Warnings .Where(error => error.InstallationId == id) + .OrderByDescending(error => error.Date) + .ThenByDescending(error => error.Time) .ToList(); } diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index aef349777..4d27d66ce 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -34,6 +34,7 @@ public static class Program WebsocketManager.StartRabbitMqConsumer(); Console.WriteLine("Queue declared"); WebsocketManager.InformInstallationsToSubscribeToRabbitMq(); + WebsocketManager.MonitorInstallationTable(); builder.Services.AddControllers(); builder.Services.AddProblemDetails(setup => @@ -94,12 +95,4 @@ public static class Program Version = "v1" }; -} - - -// var x = new CorsPolicy -// { -// Headers = { "*" }, -// Origins = { "*" }, -// Methods = { "*" } -// }; +} \ No newline at end of file diff --git a/csharp/App/Backend/Websockets/AlarmOrWarning.cs b/csharp/App/Backend/Websockets/AlarmOrWarning.cs new file mode 100644 index 000000000..4c4d2c31a --- /dev/null +++ b/csharp/App/Backend/Websockets/AlarmOrWarning.cs @@ -0,0 +1,11 @@ + + +namespace InnovEnergy.App.Backend.Websockets; + +public class AlarmOrWarning +{ + public required String Date { get; set; } + public required String Time { get; set; } + public required String Description { get; set; } + public required String CreatedBy { get; set; } +} \ No newline at end of file diff --git a/csharp/App/Backend/Websockets/InstallationInfo.cs b/csharp/App/Backend/Websockets/InstallationInfo.cs index 770a6698c..0367dd8ae 100644 --- a/csharp/App/Backend/Websockets/InstallationInfo.cs +++ b/csharp/App/Backend/Websockets/InstallationInfo.cs @@ -1,7 +1,9 @@ using System.Net.WebSockets; +namespace InnovEnergy.App.Backend.Websockets; public class InstallationInfo { public int Status { get; set; } + public DateTime Timestamp { get; set; } public List Connections { get; } = new List(); } \ No newline at end of file diff --git a/csharp/App/Backend/Websockets/StatusMessage.cs b/csharp/App/Backend/Websockets/StatusMessage.cs index b64390c4e..0a4a707bd 100644 --- a/csharp/App/Backend/Websockets/StatusMessage.cs +++ b/csharp/App/Backend/Websockets/StatusMessage.cs @@ -1,10 +1,17 @@ +namespace InnovEnergy.App.Backend.Websockets; + public class StatusMessage { - public required int InstallationId { get; init; } - public required int Status { get; init; } - public String Date { get; set; } = null!; - public String Time { get; set; } = null!; - public String Description { get; init; } = null!; - public String CreatedBy { get; init; } = null!; -} \ No newline at end of file + public required Int32 InstallationId { get; set; } + public required int Status { get; set; } + public required MessageType Type { get; set; } + public List? Warnings { get; set; } + public List? Alarms { get; set; } +} + +public enum MessageType +{ + AlarmOrWarning, + Heartbit +} diff --git a/csharp/App/Backend/Websockets/WebsockerManager.cs b/csharp/App/Backend/Websockets/WebsockerManager.cs index bf83fd1f7..311410153 100644 --- a/csharp/App/Backend/Websockets/WebsockerManager.cs +++ b/csharp/App/Backend/Websockets/WebsockerManager.cs @@ -1,4 +1,3 @@ -using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Sockets; using System.Net.WebSockets; @@ -23,7 +22,6 @@ public static class WebsocketManager var installationIps = Db.Installations.Select(inst => inst.VpnIp).ToList(); Console.WriteLine("Count is "+installationIps.Count); var maxRetransmissions = 2; - UdpClient udpClient = new UdpClient(); udpClient.Client.ReceiveTimeout = 2000; @@ -55,14 +53,8 @@ public static class WebsocketManager } catch (SocketException ex) { - if (ex.SocketErrorCode == SocketError.TimedOut) - { - Console.WriteLine("Timed out waiting for a response. Retry..."); - } - else - { - Console.WriteLine("Error: " + ex.Message); - } + if (ex.SocketErrorCode == SocketError.TimedOut){Console.WriteLine("Timed out waiting for a response. Retry...");} + else{Console.WriteLine("Error: " + ex.Message);} } } } @@ -72,6 +64,23 @@ public static class WebsocketManager } + //Every 1 minute, check the timestamp of the latest received message for every installation. + //If the difference between the two timestamps is more than one minute, we consider this installation unavailable. + public static async Task MonitorInstallationTable() + { + while (true){ + lock (InstallationConnections){ + foreach (var installationConnection in InstallationConnections){ + if ((DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(1)){ + installationConnection.Value.Status = -1; + if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);} + } + } + } + await Task.Delay(TimeSpan.FromMinutes(1)); + } + } + public static async Task StartRabbitMqConsumer() { var consumer = new EventingBasicConsumer(Channel); @@ -90,90 +99,107 @@ public static class WebsocketManager Console.WriteLine("----------------------------------------------"); Console.WriteLine("Update installation connection table"); var installationId = receivedStatusMessage.InstallationId; - - //This is an error message - if (receivedStatusMessage.Status==2) - { - Console.WriteLine("-----------------------New error-----------------------"); - Error newError = new Error - { - InstallationId = receivedStatusMessage.InstallationId, - Description = receivedStatusMessage.Description, - Date = receivedStatusMessage.Date, - Time = receivedStatusMessage.Time, - DeviceCreatedTheMessage = receivedStatusMessage.CreatedBy, - Seen = false - }; - //Create a new error and add it to the database - Db.HandleError(newError,receivedStatusMessage.InstallationId); - } - else if (receivedStatusMessage.Status==1) + //This is a heartbit message, just update the timestamp for this installation. + //There is no need to notify the corresponding front-ends. + if (receivedStatusMessage.Type == MessageType.Heartbit) { - Console.WriteLine("-----------------------New warning-----------------------"); - - Warning newWarning = new Warning - { - InstallationId = receivedStatusMessage.InstallationId, - Description = receivedStatusMessage.Description, - Date = receivedStatusMessage.Date, - Time = receivedStatusMessage.Time, - DeviceCreatedTheMessage = receivedStatusMessage.CreatedBy, - Seen = false - }; - //Create a new error and add it to the database - Db.HandleWarning(newWarning,receivedStatusMessage.InstallationId); - } - - - if (!InstallationConnections.ContainsKey(installationId)) - { - Console.WriteLine("Create new empty list for installation: " + installationId); - InstallationConnections[installationId] = new InstallationInfo - { - Status = receivedStatusMessage.Status - }; + InstallationConnections[installationId].Timestamp = DateTime.Now; } else { - InstallationConnections[installationId].Status = receivedStatusMessage.Status; - } - - Console.WriteLine("----------------------------------------------"); - - foreach (var installationConnection in InstallationConnections) - { - if (installationConnection.Key == installationId && installationConnection.Value.Connections.Count > 0) + //Traverse the Warnings list, and store each of them to the database + if (receivedStatusMessage.Warnings != null) { - Console.WriteLine("Update all the connected websockets for installation " + installationId); - installationConnection.Value.Status = receivedStatusMessage.Status; - - var jsonObject = new + foreach (var warning in receivedStatusMessage.Warnings) { - id = installationId, - status = receivedStatusMessage.Status - }; - - string jsonString = JsonSerializer.Serialize(jsonObject); - byte[] dataToSend = Encoding.UTF8.GetBytes(jsonString); - - foreach (var connection in installationConnection.Value.Connections) - { - connection.SendAsync( - new ArraySegment(dataToSend, 0, dataToSend.Length), - WebSocketMessageType.Text, - true, // Indicates that this is the end of the message - CancellationToken.None - ); + Warning newWarning = new Warning + { + InstallationId = receivedStatusMessage.InstallationId, + Description = warning.Description, + Date = warning.Date, + Time = warning.Time, + DeviceCreatedTheMessage = warning.CreatedBy, + Seen = false + }; + //Create a new warning and add it to the database + Db.HandleWarning(newWarning, receivedStatusMessage.InstallationId); } } + + //Traverse the Alarm list, and store each of them to the database + if (receivedStatusMessage.Alarms != null) + { + foreach (var alarm in receivedStatusMessage.Alarms) + { + Error newError = new Error + { + InstallationId = receivedStatusMessage.InstallationId, + Description = alarm.Description, + Date = alarm.Date, + Time = alarm.Time, + DeviceCreatedTheMessage = alarm.CreatedBy, + Seen = false + }; + //Create a new error and add it to the database + Db.HandleError(newError, receivedStatusMessage.InstallationId); + } + } + + //This installation id does not exist in our data structure, add it. + if (!InstallationConnections.ContainsKey(installationId)) + { + Console.WriteLine("Create new empty list for installation: " + installationId); + InstallationConnections[installationId] = new InstallationInfo + { + Status = receivedStatusMessage.Status, + Timestamp = DateTime.Now + }; + } + else + { + InstallationConnections[installationId].Status = receivedStatusMessage.Status; + InstallationConnections[installationId].Timestamp = DateTime.Now; + } + + Console.WriteLine("----------------------------------------------"); + //Update all the connected front-ends regarding this installation + if(InstallationConnections[installationId].Connections.Count > 0) + { + InformWebsocketsForInstallation(installationId); + } } } } }; Channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer); } - + + //Inform all the connected websockets regarding installation "installationId" + public static void InformWebsocketsForInstallation(int installationId) + { + var installationConnection = InstallationConnections[installationId]; + Console.WriteLine("Update all the connected websockets for installation " + installationId); + + var jsonObject = new + { + id = installationId, + status = installationConnection.Status + }; + + string jsonString = JsonSerializer.Serialize(jsonObject); + byte[] dataToSend = Encoding.UTF8.GetBytes(jsonString); + + foreach (var connection in installationConnection.Connections) + { + connection.SendAsync( + new ArraySegment(dataToSend, 0, dataToSend.Length), + WebSocketMessageType.Text, + true, // Indicates that this is the end of the message + CancellationToken.None + ); + } + } public static async Task HandleWebSocketConnection(WebSocket currentWebSocket) { @@ -217,37 +243,36 @@ public static class WebsocketManager //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 - if (installationIds != null) - foreach (var installationId in installationIds) + foreach (var installationId in installationIds) + { + if (!InstallationConnections.ContainsKey(installationId)) { - if (!InstallationConnections.ContainsKey(installationId)) + Console.WriteLine("Create new empty list for installation id " + installationId); + InstallationConnections[installationId] = new InstallationInfo { - Console.WriteLine("Create new empty list for installation id " + installationId); - InstallationConnections[installationId] = new InstallationInfo - { - Status = -1 - }; - } - - InstallationConnections[installationId].Connections.Add(currentWebSocket); - - var jsonObject = new - { - id = installationId, - status = InstallationConnections[installationId].Status + Status = -1 }; - - var jsonString = JsonSerializer.Serialize(jsonObject); - var dataToSend = Encoding.UTF8.GetBytes(jsonString); - - - currentWebSocket.SendAsync(dataToSend, - WebSocketMessageType.Text, - true, // Indicates that this is the end of the message - CancellationToken.None - ); } + InstallationConnections[installationId].Connections.Add(currentWebSocket); + + var jsonObject = new + { + id = installationId, + status = InstallationConnections[installationId].Status + }; + + var jsonString = JsonSerializer.Serialize(jsonObject); + var dataToSend = Encoding.UTF8.GetBytes(jsonString); + + + currentWebSocket.SendAsync(dataToSend, + WebSocketMessageType.Text, + true, // Indicates that this is the end of the message + CancellationToken.None + ); + } + Console.WriteLine("Printing installation connection list"); Console.WriteLine("----------------------------------------------"); foreach (var installationConnection in InstallationConnections) diff --git a/csharp/App/SaliMax/HostList.txt b/csharp/App/SaliMax/HostList.txt index d2bd13d08..ae2be530d 100755 --- a/csharp/App/SaliMax/HostList.txt +++ b/csharp/App/SaliMax/HostList.txt @@ -1,5 +1,5 @@ -Prototype ie-entwicklung@10.2.3.115 Prototype +Prototype ie-entwicklung@10.2.3.115 Prototype Salimax0001 ie-entwicklung@10.2.3.104 Marti Technik (Bern) Salimax0002 ie-entwicklung@10.2.4.29 Weidmann Oberwil (ZG) Salimax0003 ie-entwicklung@10.2.4.33 Elektrotechnik Stefan GmbH diff --git a/csharp/App/SaliMax/src/MiddlewareClasses/StatusMessage.cs b/csharp/App/SaliMax/src/MiddlewareClasses/StatusMessage.cs index ca6d27f5b..f5b51fab6 100644 --- a/csharp/App/SaliMax/src/MiddlewareClasses/StatusMessage.cs +++ b/csharp/App/SaliMax/src/MiddlewareClasses/StatusMessage.cs @@ -1,10 +1,19 @@ +using InnovEnergy.App.SaliMax.Ess; + namespace InnovEnergy.App.SaliMax.MiddlewareClasses; public class StatusMessage { - public required Int32 InstallationId { get; set; } - public required Int32 Status { get; set; } - public List? Warnings { get; set; } - public List? Alarms { get; set; } + public required Int32 InstallationId { get; set; } + public required SalimaxAlarmState Status { get; set; } + public required MessageType Type { get; set; } + public List? Warnings { get; set; } + public List? Alarms { get; set; } +} + +public enum MessageType +{ + AlarmOrWarning, + Heartbit } \ No newline at end of file diff --git a/csharp/App/SaliMax/src/Program.cs b/csharp/App/SaliMax/src/Program.cs index 97a6c8908..253bfc1ab 100644 --- a/csharp/App/SaliMax/src/Program.cs +++ b/csharp/App/SaliMax/src/Program.cs @@ -59,7 +59,8 @@ internal static class Program private static IModel? _channel; private static Boolean _subscribedToQueue = false; private static Boolean _subscribeToQueueForTheFirstTime = false; - private static Int32 _prevSalimaxState = 0; // 0 is a green status + private static SalimaxAlarmState _prevSalimaxState = SalimaxAlarmState.Green; + private static Int32 _heartBitInterval = 0; static Program() { @@ -260,6 +261,7 @@ internal static class Program private static void SendSalimaxStateAlarm(StatusMessage currentSalimaxState) { var s3Bucket = Config.Load().S3?.Bucket; + _heartBitInterval++; //When the controller boots, it tries to subscribe to the queue if (_subscribeToQueueForTheFirstTime==false) @@ -279,6 +281,14 @@ internal static class Program if (s3Bucket != null) InformMiddleware(s3Bucket, currentSalimaxState); } + else if (_subscribedToQueue && _heartBitInterval==15) + { + _heartBitInterval = 0; + currentSalimaxState.Type = MessageType.Heartbit; + + if (s3Bucket != null) + InformMiddleware(s3Bucket, currentSalimaxState); + } //If there is an available message from the RabbitMQ Broker, subscribe to the queue if (_udpListener.Available > 0) @@ -417,7 +427,7 @@ internal static class Program } } - foreach (var warning in record.AcDc.Alarms) + foreach (var warning in record.AcDc.Warnings) { warningList.Add(new AlarmOrWarning { @@ -428,7 +438,7 @@ internal static class Program }); } - foreach (var warning in record.DcDc.Alarms) + foreach (var warning in record.DcDc.Warnings) { warningList.Add(new AlarmOrWarning { @@ -441,7 +451,7 @@ internal static class Program if (record.Battery != null) { - foreach (var warning in record.Battery.Alarms) + foreach (var warning in record.Battery.Warnings) { warningList.Add(new AlarmOrWarning { @@ -473,7 +483,8 @@ internal static class Program var returnedStatus = new StatusMessage { InstallationId = installationId, - Status = (Int16)salimaxAlarmsState, + Status = salimaxAlarmsState, + Type= MessageType.AlarmOrWarning, Alarms = alarmList, Warnings = warningList }; diff --git a/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcDevicesRecord.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcDevicesRecord.cs index 5df7597a0..c852a3895 100644 --- a/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcDevicesRecord.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/AcDcDevicesRecord.cs @@ -18,9 +18,9 @@ public class AcDcDevicesRecord public SystemControlRegisters? SystemControl { get; } public IReadOnlyList Devices { get; init; } - - //public IEnumerable Alarms => new []{AlarmMessage.BatteryOvervoltage}; //Devices.SelectMany(d => d.Status.Alarms).Distinct(); - //public IEnumerable Warnings => new []{WarningMessage.TempDerating}; //Devices.SelectMany(d => d.Status.Warnings).Distinct(); + + //public IEnumerable Alarms => new []{AlarmMessage.OvercurrentBalancer}; //Devices.SelectMany(d => d.Status.Alarms).Distinct(); + //public IEnumerable Warnings => new []{WarningMessage.DcOffset}; //Devices.SelectMany(d => d.Status.Warnings).Distinct(); public IEnumerable Alarms => Devices.SelectMany(d => d.Status.Alarms).Distinct(); public IEnumerable Warnings => Devices.SelectMany(d => d.Status.Warnings).Distinct();