Included heart-bit functionality, replaced static variable assignments with enums, implemented monitoring functionality for the backend to check for disconnected installations

This commit is contained in:
Noe 2023-11-22 16:49:47 +01:00
parent 6301cf38a0
commit bdad83995d
10 changed files with 196 additions and 134 deletions

View File

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

View File

@ -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 =>
@ -95,11 +96,3 @@ public static class Program
};
}
// var x = new CorsPolicy
// {
// Headers = { "*" },
// Origins = { "*" },
// Methods = { "*" }
// };

View File

@ -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; }
}

View File

@ -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<WebSocket> Connections { get; } = new List<WebSocket>();
}

View File

@ -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!;
public required Int32 InstallationId { get; set; }
public required int Status { get; set; }
public required MessageType Type { get; set; }
public List<AlarmOrWarning>? Warnings { get; set; }
public List<AlarmOrWarning>? Alarms { get; set; }
}
public enum MessageType
{
AlarmOrWarning,
Heartbit
}

View File

@ -1,4 +1,3 @@
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Sockets;
using System.Net.WebSockets;
@ -24,7 +23,6 @@ public static class WebsocketManager
Console.WriteLine("Count is "+installationIps.Count);
var maxRetransmissions = 2;
UdpClient udpClient = new UdpClient();
udpClient.Client.ReceiveTimeout = 2000;
int port = 9000;
@ -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);
@ -91,73 +100,97 @@ public static class WebsocketManager
Console.WriteLine("Update installation connection table");
var installationId = receivedStatusMessage.InstallationId;
//This is an error message
if (receivedStatusMessage.Status==2)
//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 error-----------------------");
InstallationConnections[installationId].Timestamp = DateTime.Now;
}
else
{
//Traverse the Warnings list, and store each of them to the database
if (receivedStatusMessage.Warnings != null)
{
foreach (var warning in receivedStatusMessage.Warnings)
{
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 = receivedStatusMessage.Description,
Date = receivedStatusMessage.Date,
Time = receivedStatusMessage.Time,
DeviceCreatedTheMessage = receivedStatusMessage.CreatedBy,
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);
}
else if (receivedStatusMessage.Status==1)
{
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);
}
//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
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);
}
foreach (var installationConnection in InstallationConnections)
{
if (installationConnection.Key == installationId && installationConnection.Value.Connections.Count > 0)
//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);
installationConnection.Value.Status = receivedStatusMessage.Status;
var jsonObject = new
{
id = installationId,
status = receivedStatusMessage.Status
status = installationConnection.Status
};
string jsonString = JsonSerializer.Serialize(jsonObject);
byte[] dataToSend = Encoding.UTF8.GetBytes(jsonString);
foreach (var connection in installationConnection.Value.Connections)
foreach (var connection in installationConnection.Connections)
{
connection.SendAsync(
new ArraySegment<byte>(dataToSend, 0, dataToSend.Length),
@ -167,13 +200,6 @@ public static class WebsocketManager
);
}
}
}
}
}
};
Channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer);
}
public static async Task HandleWebSocketConnection(WebSocket currentWebSocket)
{
@ -217,7 +243,6 @@ 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)
{
if (!InstallationConnections.ContainsKey(installationId))

View File

@ -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 required SalimaxAlarmState Status { get; set; }
public required MessageType Type { get; set; }
public List<AlarmOrWarning>? Warnings { get; set; }
public List<AlarmOrWarning>? Alarms { get; set; }
}
public enum MessageType
{
AlarmOrWarning,
Heartbit
}

View File

@ -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
};

View File

@ -19,8 +19,8 @@ public class AcDcDevicesRecord
public SystemControlRegisters? SystemControl { get; }
public IReadOnlyList<AcDcRecord> Devices { get; init; }
//public IEnumerable<AlarmMessage> Alarms => new []{AlarmMessage.BatteryOvervoltage}; //Devices.SelectMany(d => d.Status.Alarms).Distinct();
//public IEnumerable<WarningMessage> Warnings => new []{WarningMessage.TempDerating}; //Devices.SelectMany(d => d.Status.Warnings).Distinct();
//public IEnumerable<AlarmMessage> Alarms => new []{AlarmMessage.OvercurrentBalancer}; //Devices.SelectMany(d => d.Status.Alarms).Distinct();
//public IEnumerable<WarningMessage> Warnings => new []{WarningMessage.DcOffset}; //Devices.SelectMany(d => d.Status.Warnings).Distinct();
public IEnumerable<AlarmMessage> Alarms => Devices.SelectMany(d => d.Status.Alarms).Distinct();
public IEnumerable<WarningMessage> Warnings => Devices.SelectMany(d => d.Status.Warnings).Distinct();