Update webscoket server failling

This commit is contained in:
atef 2026-02-27 11:20:32 +01:00
parent d81ef73bcf
commit 545bb96b9d
3 changed files with 396 additions and 79 deletions

View File

@ -170,8 +170,6 @@ public static class Program
statusrecord.InverterRecord.WarningMainCode.WriteLine(" = WarningMainCode"); // 30408 this the duration statusrecord.InverterRecord.WarningMainCode.WriteLine(" = WarningMainCode"); // 30408 this the duration
statusrecord.InverterRecord.WarningSubCode.WriteLine(" = WarningSubCode"); // 30408 this the duration statusrecord.InverterRecord.WarningSubCode.WriteLine(" = WarningSubCode"); // 30408 this the duration
EssModeControl(statusrecord); EssModeControl(statusrecord);
statusrecord.ApplyDefaultSettings(); statusrecord.ApplyDefaultSettings();
@ -182,7 +180,6 @@ public static class Program
statusrecord.Config.Save(); // save the config file statusrecord.Config.Save(); // save the config file
growattDeviceT415K.Write(statusrecord.InverterRecord); growattDeviceT415K.Write(statusrecord.InverterRecord);
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff")); Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff"));
} }
return statusrecord; return statusrecord;
} }
@ -342,7 +339,6 @@ public static class Program
var s3Bucket = Config.Load().S3?.Bucket; var s3Bucket = Config.Load().S3?.Bucket;
var subscribedNow = false; var subscribedNow = false;
//When the controller boots, it tries to subscribe to the queue
if (_subscribeToQueueForTheFirstTime == false) if (_subscribeToQueueForTheFirstTime == false)
{ {
subscribedNow = true; subscribedNow = true;
@ -356,9 +352,30 @@ public static class Program
var stateChanged = currentSalimaxState.Status != _prevSodiohomeAlarmState; var stateChanged = currentSalimaxState.Status != _prevSodiohomeAlarmState;
var contentChanged = HasErrorsOrWarningsChanged(currentSalimaxState); var contentChanged = HasErrorsOrWarningsChanged(currentSalimaxState);
var needsHeartbeat = (DateTime.Now - _lastHeartbeatTime).TotalSeconds >= HeartbeatIntervalSeconds; var needsHeartbeat = (DateTime.Now - _lastHeartbeatTime).TotalSeconds >= HeartbeatIntervalSeconds;
Console.WriteLine($"subscribedNow={subscribedNow}");
Console.WriteLine($"_subscribedToQueue={_subscribedToQueue}");
Console.WriteLine($"stateChanged={stateChanged}");
Console.WriteLine($"contentChanged={contentChanged}");
Console.WriteLine($"needsHeartbeat={needsHeartbeat}");
Console.WriteLine($"s3Bucket null? {s3Bucket == null}");
// Send message if: state changed, content changed, OR heartbeat needed if (s3Bucket == null)
if (!subscribedNow && _subscribedToQueue && (stateChanged || contentChanged || needsHeartbeat)) {
Console.WriteLine("⚠ S3 bucket not configured. Skipping middleware send.");
LogMiddlewareFailure(new Exception("S3 Bucket not configured"));
return;
}
// Ensure connection FIRST
if (!RabbitMqManager.EnsureConnected(currentSalimaxState, s3Bucket, VpnServerIp))
{
Console.WriteLine($"❌ RabbitMQ EnsureConnected FAILED at {DateTime.Now:HH:mm:ss.fff}");
LogMiddlewareFailure(new Exception("EnsureConnected returned false"));
return;
}
//If already subscribed to the queue and the status has been changed, update the queue
if (!subscribedNow && (stateChanged || contentChanged || needsHeartbeat))
{ {
_prevSodiohomeAlarmState = currentSalimaxState.Status; _prevSodiohomeAlarmState = currentSalimaxState.Status;
@ -375,12 +392,17 @@ public static class Program
_lastHeartbeatTime = DateTime.Now; _lastHeartbeatTime = DateTime.Now;
} }
if (s3Bucket != null) // why this is based on s3 bucket? try
{ {
RabbitMqManager.InformMiddleware(currentSalimaxState); RabbitMqManager.InformMiddleware(currentSalimaxState);
//LogMiddlewareFailure(new Exception($"✅ Middleware message sent at {DateTime.Now:HH:mm:ss.fff}"));
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to send middleware message: {ex.Message}");
LogMiddlewareFailure(ex);
} }
} }
//If there is an available message from the RabbitMQ Broker, apply the configuration file //If there is an available message from the RabbitMQ Broker, apply the configuration file
Configuration? config = SetConfigurationFile(); Configuration? config = SetConfigurationFile();
if (config != null) if (config != null)
@ -439,6 +461,27 @@ public static class Program
return registers; return registers;
} }
private static void LogMiddlewareFailure(Exception ex)
{
try
{
var logPath = "/home/inesco/SodiStoreHome/middleware_failures.log";
var logEntry =
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}]\n" +
$"Exception: {ex.GetType().FullName}\n" +
$"Message: {ex.Message}\n" +
$"StackTrace:\n{ex.StackTrace}\n" +
$"--------------------------------------------------\n";
File.AppendAllText(logPath, logEntry);
}
catch
{
// Never allow logging to crash the service
}
}
[RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Serialize<System.Collections.Generic.Dictionary<string, ushort>>(System.Collections.Generic.Dictionary<string, ushort>, System.Text.Json.JsonSerializerOptions?)")] [RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Serialize<System.Collections.Generic.Dictionary<string, ushort>>(System.Collections.Generic.Dictionary<string, ushort>, System.Text.Json.JsonSerializerOptions?)")]
private static async Task<Boolean> SaveModbusTcpFile(StatusRecord status) private static async Task<Boolean> SaveModbusTcpFile(StatusRecord status)
{ {

View File

@ -25,6 +25,8 @@ using JsonSerializer = System.Text.Json.JsonSerializer;
using static InnovEnergy.App.SinexcelCommunication.MiddlewareClasses.MiddlewareAgent; using static InnovEnergy.App.SinexcelCommunication.MiddlewareClasses.MiddlewareAgent;
using System.Diagnostics.CodeAnalysis; using System.Diagnostics.CodeAnalysis;
using InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType; using InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
using static InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType.WorkingMode;
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
@ -36,17 +38,28 @@ internal static class Program
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(5); private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(5);
private const UInt16 NbrOfFileToConcatenate = 15; // add this to config file private const UInt16 NbrOfFileToConcatenate = 15; // add this to config file
private static UInt16 _fileCounter = 0; private static UInt16 _fileCounter = 0;
private static Channel _sinexcelChannel; private static Channel _sinexcelChannel1;
private static Channel _sinexcelChannel2;
private const String SwVersionNumber =" V1.00.131025 beta"; private static readonly String SwVersionNumber = " V1.00." + DateTime.Today;
private const String VpnServerIp = "10.2.0.11"; private const String VpnServerIp = "10.2.0.11";
private static Boolean _subscribedToQueue = false; private static Boolean _subscribedToQueue = false;
private static Boolean _subscribeToQueueForTheFirstTime = false; private static Boolean _subscribeToQueueForTheFirstTime = false;
private static SodistoreAlarmState _prevSodiohomeAlarmState = SodistoreAlarmState.Green; private static SodistoreAlarmState _prevSodiohomeAlarmState = SodistoreAlarmState.Green;
private static SodistoreAlarmState _sodiohomeAlarmState = SodistoreAlarmState.Green; private static SodistoreAlarmState _sodiohomeAlarmState = SodistoreAlarmState.Green;
// Tracking for error/warning content changes
private static List<String> _prevErrorCodes = new List<String>();
private static List<String> _prevWarningCodes = new List<String>();
// Heartbeat tracking
private static DateTime _lastHeartbeatTime = DateTime.MinValue;
private const Int32 HeartbeatIntervalSeconds = 60;
// move all this to config file // move all this to config file
private const String Port = "/dev/ttyUSB0"; private const String Port1 = "/dev/ttyUSB0";
private const String Port2 = "/dev/ttyUSB1";
private const Byte SlaveId = 1; private const Byte SlaveId = 1;
private const Parity Parity = 0; //none private const Parity Parity = 0; //none
private const Int32 StopBits = 1; private const Int32 StopBits = 1;
@ -55,7 +68,9 @@ internal static class Program
public static async Task Main(String[] args) public static async Task Main(String[] args)
{ {
_sinexcelChannel = new SerialPortChannel(Port, BaudRate, Parity, DataBits, StopBits); _sinexcelChannel1 = new SerialPortChannel(Port1, BaudRate, Parity, DataBits, StopBits);
_sinexcelChannel2 = new SerialPortChannel(Port2, BaudRate, Parity, DataBits, StopBits);
InitializeCommunicationToMiddleware(); InitializeCommunicationToMiddleware();
while (true) while (true)
{ {
@ -75,19 +90,22 @@ internal static class Program
{ {
Watchdog.NotifyReady(); Watchdog.NotifyReady();
Console.WriteLine("Starting Sinexcel Communication"); Console.WriteLine("Starting Sinexcel Communication, SW Version : " + SwVersionNumber);
var sinexcelDevice = new SinexcelDevice(_sinexcelChannel, SlaveId); var sinexcelDevice1 = new SinexcelDevice(_sinexcelChannel1, SlaveId);
// var sinexcelDevice = new sinexcelDevices(new List<sinexcelDevice> { growattDeviceT415K }); var sinexcelDevice2 = new SinexcelDevice(_sinexcelChannel2, SlaveId);
StatusRecord? ReadStatus() StatusRecord? ReadStatus()
{ {
var config = Config.Load(); var config = Config.Load();
var sinexcelRecord = sinexcelDevice.Read(); var sinexcelRecord1 = sinexcelDevice1.Read();
var sinexcelRecord2 = sinexcelDevice2.Read();
return new StatusRecord return new StatusRecord
{ {
InverterRecord = sinexcelRecord, InverterRecord1 = sinexcelRecord1,
InverterRecord2 = sinexcelRecord2,
Config = config // load from disk every iteration, so config can be changed while running Config = config // load from disk every iteration, so config can be changed while running
}; };
} }
@ -120,61 +138,174 @@ internal static class Program
if (statusrecord == null) if (statusrecord == null)
return null; return null;
// Console.WriteLine( statusrecord.InverterRecord.TimedChargeAndDischargeOff + " TimedChargeAndDischargeOff "); Console.WriteLine(" ************************************************ Inverter 1 ************************************************ ");
Console.WriteLine( statusrecord.InverterRecord.Battery1Power + " Battery1Power "); Console.WriteLine( statusrecord.InverterRecord1.SystemDateTime + " SystemDateTime ");
Console.WriteLine( statusrecord.InverterRecord.Battery1Soc + " Battery1Soc ");
Console.WriteLine( statusrecord.InverterRecord.RepetitiveWeeks + " RepetitiveWeeks ");
Console.WriteLine( statusrecord.InverterRecord.EffectiveStartDate + " EffectiveStartDate ");
Console.WriteLine( statusrecord.InverterRecord.EffectiveEndDate + " EffectiveEndDate ");
Console.WriteLine( statusrecord.InverterRecord.ChargingPowerPeriod1 + " ChargingPowerPeriod1 ");
Console.WriteLine( statusrecord.InverterRecord.ChargeStartTimePeriod1 + " ChargeStartTimePeriod1 ");
Console.WriteLine( statusrecord.InverterRecord.ChargeEndTimePeriod1 + " ChargeEndTimePeriod1 ");
Console.WriteLine( statusrecord.InverterRecord.DischargeStartTimePeriod1 + " DischargeStartTimePeriod1 "); Console.WriteLine( statusrecord.InverterRecord1.TotalPhotovoltaicPower + " TotalPhotovoltaicPower ");
Console.WriteLine( statusrecord.InverterRecord.DischargeEndTimePeriod1 + " DischargeEndTimePeriod1 "); Console.WriteLine( statusrecord.InverterRecord1.TotalBatteryPower + " TotalBatteryPower ");
Console.WriteLine( statusrecord.InverterRecord1.TotalLoadPower + " TotalLoadPower ");
Console.WriteLine( statusrecord.InverterRecord1.TotalGridPower + " TotalGridPower ");
Console.WriteLine( statusrecord.InverterRecord1.Battery1Power + " Battery1Power ");
Console.WriteLine( statusrecord.InverterRecord1.Battery1Soc + " Battery1Soc ");
Console.WriteLine( statusrecord.InverterRecord1.Battery1BackupSoc + " Battery1BackupSoc ");
Console.WriteLine( statusrecord.InverterRecord1.Battery1MinSoc + " Battery1MinSoc ");
Console.WriteLine( statusrecord.InverterRecord1.Battery2Power + " Battery2Power ");
Console.WriteLine( statusrecord.InverterRecord1.Battery2Soc + " Battery2Soc ");
Console.WriteLine( statusrecord.InverterRecord1.Battery2BackupSoc + " Battery2BackupSoc ");
Console.WriteLine( statusrecord.InverterRecord1.Battery2MinSoc + " Battery2MinSoc ");
Console.WriteLine( statusrecord.InverterRecord1.EnableGridExport + " EnableGridExport ");
Console.WriteLine( statusrecord.InverterRecord1.PowerGridExportLimit + " PowerGridExportLimit ");
Console.WriteLine( statusrecord.InverterRecord1.PowerOn + " PowerOn ");
Console.WriteLine( statusrecord.InverterRecord1.PowerOff + " PowerOff ");
Console.WriteLine( statusrecord.InverterRecord1.WorkingMode + " WorkingMode ");
Console.WriteLine( statusrecord.InverterRecord1.GridSwitchMethod + " GridSwitchMethod ");
Console.WriteLine( statusrecord.InverterRecord1.ThreePhaseWireSystem + " ThreePhaseWireSystem ");
Console.WriteLine(" ************************************************ Inverter 2 ************************************************ ");
Console.WriteLine( statusrecord.InverterRecord2.SystemDateTime + " SystemDateTime ");
Console.WriteLine( statusrecord.InverterRecord2.TotalPhotovoltaicPower + " TotalPhotovoltaicPower ");
Console.WriteLine( statusrecord.InverterRecord2.TotalBatteryPower + " TotalBatteryPower ");
Console.WriteLine( statusrecord.InverterRecord2.TotalLoadPower + " TotalLoadPower ");
Console.WriteLine( statusrecord.InverterRecord2.TotalGridPower + " TotalGridPower ");
Console.WriteLine( statusrecord.InverterRecord2.Battery1Power + " Battery1Power ");
Console.WriteLine( statusrecord.InverterRecord2.Battery1Soc + " Battery1Soc ");
Console.WriteLine( statusrecord.InverterRecord2.Battery1BackupSoc + " Battery1BackupSoc ");
Console.WriteLine( statusrecord.InverterRecord2.Battery1MinSoc + " Battery1MinSoc ");
Console.WriteLine( statusrecord.InverterRecord2.Battery2Power + " Battery2Power ");
Console.WriteLine( statusrecord.InverterRecord2.Battery2Soc + " Battery2Soc ");
Console.WriteLine( statusrecord.InverterRecord2.Battery2BackupSoc + " Battery2BackupSoc ");
Console.WriteLine( statusrecord.InverterRecord2.Battery2MinSoc + " Battery2MinSoc ");
Console.WriteLine( statusrecord.InverterRecord2.EnableGridExport + " EnableGridExport ");
Console.WriteLine( statusrecord.InverterRecord2.PowerGridExportLimit + " PowerGridExportLimit ");
Console.WriteLine( statusrecord.InverterRecord2.PowerOn + " PowerOn ");
Console.WriteLine( statusrecord.InverterRecord2.PowerOff + " PowerOff ");
Console.WriteLine( statusrecord.InverterRecord2.WorkingMode + " WorkingMode ");
Console.WriteLine( statusrecord.InverterRecord2.GridSwitchMethod + " GridSwitchMethod ");
Console.WriteLine( statusrecord.InverterRecord2.ThreePhaseWireSystem + " ThreePhaseWireSystem ");
/*
Console.WriteLine( statusrecord.InverterRecord1.RepetitiveWeeks + " RepetitiveWeeks ");
Console.WriteLine( statusrecord.InverterRecord1.EffectiveStartDate + " EffectiveStartDate ");
Console.WriteLine( statusrecord.InverterRecord1.EffectiveEndDate + " EffectiveEndDate ");
Console.WriteLine( statusrecord.InverterRecord1.ChargingPowerPeriod1 + " ChargingPowerPeriod1 ");
Console.WriteLine( statusrecord.InverterRecord1.DishargingPowerPeriod1 + " dischargingPowerPeriod1 ");
Console.WriteLine( statusrecord.InverterRecord1.ChargeStartTimePeriod1 + " ChargeStartTimePeriod1 ");
Console.WriteLine( statusrecord.InverterRecord1.ChargeEndTimePeriod1 + " ChargeEndTimePeriod1 ");
Console.WriteLine( statusrecord.InverterRecord1.DischargeStartTimePeriod1 + " DischargeStartTimePeriod1 ");
Console.WriteLine( statusrecord.InverterRecord1.DischargeEndTimePeriod1 + " DischargeEndTimePeriod1 ");*/
Console.WriteLine( statusrecord.InverterRecord.PowerOn + " PowerOn ");
Console.WriteLine( statusrecord.InverterRecord.PowerOff + " PowerOff ");
Console.WriteLine( statusrecord.InverterRecord.WorkingMode + " WorkingMode ");
// Console.WriteLine( statusrecord.InverterRecord.FaultClearing + " FaultClearing ");
SendSalimaxStateAlarm(GetSodiHomeStateAlarm(statusrecord),statusrecord); SendSalimaxStateAlarm(GetSodiHomeStateAlarm(statusrecord),statusrecord);
statusrecord.ControlConstants();
Console.WriteLine( " ************************************ We are writing ************************************"); Console.WriteLine( " ************************************ We are writing ************************************");
var startWritingTime = DateTime.Now; var startWritingTime = DateTime.Now;
Console.WriteLine(startWritingTime.ToString("HH:mm:ss.fff ") +"start Writing"); Console.WriteLine(startWritingTime.ToString("HH:mm:ss.fff ") +"start Writing");
statusrecord?.Config.Save(); // save the config file statusrecord?.Config.Save(); // save the config file
if (statusrecord != null) if (statusrecord is { Config.ControlPermission: true })
{ {
statusrecord.InverterRecord.WorkingMode = WorkingMode.TimeChargeDischarge; Console.WriteLine(" We have the Right to Write");
sinexcelDevice1.Write(statusrecord.InverterRecord1);
sinexcelDevice2.Write(statusrecord.InverterRecord2);
//statusrecord.InverterRecord.TimedChargeAndDischargeOff = 0;
statusrecord.InverterRecord.RepetitiveWeeks = SinexcelWeekDays.All;
statusrecord.InverterRecord.EffectiveStartDate = DateTime.Today.AddDays(-1);
statusrecord.InverterRecord.EffectiveEndDate = DateTime.Today.AddDays(2);
statusrecord.InverterRecord.ChargingPowerPeriod1 = 3;
statusrecord.InverterRecord.DishargingPowerPeriod1 = 3;
statusrecord.InverterRecord.DischargeStartTimePeriod1 = new TimeSpan(16, 00, 0);
statusrecord.InverterRecord.DischargeEndTimePeriod1 = new TimeSpan(17, 03, 0);
statusrecord.InverterRecord.PowerOn = 1;
statusrecord.InverterRecord.PowerOff = 0;
//statusrecord.InverterRecord.FaultClearing = 1;
} }
if (statusrecord?.InverterRecord != null) sinexcelDevice.Write(statusrecord.InverterRecord); else
{
Console.WriteLine(" Nooooooo We cant' have the Right to Write");
}
var stopTime = DateTime.Now;
Console.WriteLine(stopTime.ToString("HH:mm:ss.fff ")+ "run iteration time finish");
return statusrecord; return statusrecord;
} }
catch (CrcException e)
{
Console.WriteLine(e);
throw; // restart only on CRC
}
catch (Exception e) catch (Exception e)
{ {
// Handle exception and print the error
Console.WriteLine(e); Console.WriteLine(e);
return null; return null; // keep running for non-critical errors
}
}
}
private static void ControlConstants(this StatusRecord? statusrecord)
{
if (statusrecord == null) return;
statusrecord.InverterRecord1.Battery1BackupSoc = (Single)statusrecord.Config.MinSoc ;
statusrecord.InverterRecord1.Battery2BackupSoc = (Single)statusrecord.Config.MinSoc ;
statusrecord.InverterRecord1.RepetitiveWeeks = SinexcelWeekDays.All;
var isChargePeriod = IsNowInsideDateAndTime(statusrecord.Config.StartTimeChargeandDischargeDayandTime, statusrecord.Config.StopTimeChargeandDischargeDayandTime);
Console.WriteLine("Are we inside the charge/Discharge time " + isChargePeriod);
if (statusrecord.Config.OperatingPriority != TimeChargeDischarge)
{
statusrecord.InverterRecord1.WorkingMode = statusrecord.Config.OperatingPriority;
}
else if (statusrecord.Config.OperatingPriority == TimeChargeDischarge && isChargePeriod)
{
statusrecord.InverterRecord1.WorkingMode = statusrecord.Config.OperatingPriority;
if (statusrecord.Config.TimeChargeandDischargePower > 0)
{
statusrecord.InverterRecord1.EffectiveStartDate = statusrecord.Config.StartTimeChargeandDischargeDayandTime.Date;
statusrecord.InverterRecord1.EffectiveEndDate = statusrecord.Config.StopTimeChargeandDischargeDayandTime.Date;
statusrecord.InverterRecord1.ChargingPowerPeriod1 = Math.Abs(statusrecord.Config.TimeChargeandDischargePower);
statusrecord.InverterRecord1.ChargeStartTimePeriod1 = statusrecord.Config.StartTimeChargeandDischargeDayandTime.TimeOfDay;
statusrecord.InverterRecord1.ChargeEndTimePeriod1 = statusrecord.Config.StopTimeChargeandDischargeDayandTime.TimeOfDay;
statusrecord.InverterRecord1.DischargeStartTimePeriod1 = TimeSpan.Zero;
statusrecord.InverterRecord1.DischargeEndTimePeriod1 = TimeSpan.Zero;
}
else
{
statusrecord.InverterRecord1.EffectiveStartDate = statusrecord.Config.StartTimeChargeandDischargeDayandTime.Date;
statusrecord.InverterRecord1.EffectiveEndDate = statusrecord.Config.StopTimeChargeandDischargeDayandTime.Date;
statusrecord.InverterRecord1.DishargingPowerPeriod1 = Math.Abs(statusrecord.Config.TimeChargeandDischargePower);
statusrecord.InverterRecord1.DischargeStartTimePeriod1 = statusrecord.Config.StartTimeChargeandDischargeDayandTime.TimeOfDay;
statusrecord.InverterRecord1.DischargeEndTimePeriod1 = statusrecord.Config.StopTimeChargeandDischargeDayandTime.TimeOfDay;
statusrecord.InverterRecord1.ChargeStartTimePeriod1 = TimeSpan.Zero;
statusrecord.InverterRecord1.ChargeEndTimePeriod1 = TimeSpan.Zero;
} }
} }
else
{
statusrecord.InverterRecord1.WorkingMode = SpontaneousSelfUse;
} }
statusrecord.InverterRecord1.PowerOn = 1;
statusrecord.InverterRecord1.PowerOff = 0;
//statusrecord.InverterRecord.FaultClearing = 1;
}
private static bool IsNowInsideDateAndTime(DateTime effectiveStart, DateTime effectiveEnd)
{
DateTime now = DateTime.Now;
// Date check
if (now < effectiveStart || now > effectiveEnd)
return false;
return true;
}
private static StatusMessage GetSodiHomeStateAlarm(StatusRecord? record) private static StatusMessage GetSodiHomeStateAlarm(StatusRecord? record)
{ {
@ -231,6 +362,43 @@ internal static class Program
return returnedStatus; return returnedStatus;
} }
/// <summary>
/// Checks if the error or warning content has changed compared to the previous state.
/// This allows detection of new/cleared errors even when the overall alarm state (Red/Orange/Green) remains the same.
/// </summary>
private static Boolean HasErrorsOrWarningsChanged(StatusMessage currentState)
{
// Get current error codes (descriptions)
var currentErrors = currentState.Alarms?
.Select(a => a.Description ?? String.Empty)
.OrderBy(d => d) // Sort for consistent comparison
.ToList() ?? new List<String>();
// Get current warning codes (descriptions)
var currentWarnings = currentState.Warnings?
.Select(w => w.Description ?? String.Empty)
.OrderBy(d => d) // Sort for consistent comparison
.ToList() ?? new List<String>();
// Check if lists have changed (new items added or existing items removed)
var errorsChanged = !currentErrors.SequenceEqual(_prevErrorCodes);
var warningsChanged = !currentWarnings.SequenceEqual(_prevWarningCodes);
// Update tracking if changes detected
if (errorsChanged || warningsChanged)
{
Console.WriteLine($"Error/Warning content changed:");
Console.WriteLine($" Errors: {String.Join(", ", currentErrors)} (was: {String.Join(", ", _prevErrorCodes)})");
Console.WriteLine($" Warnings: {String.Join(", ", currentWarnings)} (was: {String.Join(", ", _prevWarningCodes)})");
_prevErrorCodes = currentErrors;
_prevWarningCodes = currentWarnings;
return true;
}
return false;
}
private static Int32 GetInstallationId(String s3Bucket) private static Int32 GetInstallationId(String s3Bucket)
{ {
var part = s3Bucket.Split('-').FirstOrDefault(); var part = s3Bucket.Split('-').FirstOrDefault();
@ -249,14 +417,63 @@ internal static class Program
_subscribeToQueueForTheFirstTime = true; _subscribeToQueueForTheFirstTime = true;
_prevSodiohomeAlarmState = currentSalimaxState.Status; _prevSodiohomeAlarmState = currentSalimaxState.Status;
_subscribedToQueue = RabbitMqManager.SubscribeToQueue(currentSalimaxState, s3Bucket, VpnServerIp); _subscribedToQueue = RabbitMqManager.SubscribeToQueue(currentSalimaxState, s3Bucket, VpnServerIp);
_lastHeartbeatTime = DateTime.Now; // Initialize heartbeat timer
}
// Check if we should send a message
var stateChanged = currentSalimaxState.Status != _prevSodiohomeAlarmState;
var contentChanged = HasErrorsOrWarningsChanged(currentSalimaxState);
var needsHeartbeat = (DateTime.Now - _lastHeartbeatTime).TotalSeconds >= HeartbeatIntervalSeconds;
Console.WriteLine($"subscribedNow={subscribedNow}");
Console.WriteLine($"_subscribedToQueue={_subscribedToQueue}");
Console.WriteLine($"stateChanged={stateChanged}");
Console.WriteLine($"contentChanged={contentChanged}");
Console.WriteLine($"needsHeartbeat={needsHeartbeat}");
Console.WriteLine($"s3Bucket null? {s3Bucket == null}");
if (s3Bucket == null)
{
Console.WriteLine("⚠ S3 bucket not configured. Skipping middleware send.");
LogMiddlewareFailure(new Exception("S3 Bucket not configured"));
return;
}
// Ensure connection FIRST
if (!RabbitMqManager.EnsureConnected(currentSalimaxState, s3Bucket, VpnServerIp))
{
Console.WriteLine($"❌ RabbitMQ EnsureConnected FAILED at {DateTime.Now:HH:mm:ss.fff}");
LogMiddlewareFailure(new Exception("EnsureConnected returned false"));
return;
} }
//If already subscribed to the queue and the status has been changed, update the queue //If already subscribed to the queue and the status has been changed, update the queue
if (!subscribedNow && _subscribedToQueue && currentSalimaxState.Status != _prevSodiohomeAlarmState) if (!subscribedNow && (stateChanged || contentChanged || needsHeartbeat))
{ {
_prevSodiohomeAlarmState = currentSalimaxState.Status; _prevSodiohomeAlarmState = currentSalimaxState.Status;
if (s3Bucket != null)
// Set appropriate message type
if (stateChanged || contentChanged)
{
currentSalimaxState.Type = MessageType.AlarmOrWarning;
Console.WriteLine($"Sending AlarmOrWarning message - StateChanged: {stateChanged}, ContentChanged: {contentChanged}");
}
else if (needsHeartbeat)
{
currentSalimaxState.Type = MessageType.Heartbit;
Console.WriteLine($"Sending Heartbeat message - {HeartbeatIntervalSeconds}s interval reached");
_lastHeartbeatTime = DateTime.Now;
}
try
{
RabbitMqManager.InformMiddleware(currentSalimaxState); RabbitMqManager.InformMiddleware(currentSalimaxState);
LogMiddlewareFailure(new Exception($"✅ Middleware message sent at {DateTime.Now:HH:mm:ss.fff}"));
}
catch (Exception ex)
{
Console.WriteLine($"❌ Failed to send middleware message: {ex.Message}");
LogMiddlewareFailure(ex);
}
} }
//If there is an available message from the RabbitMQ Broker, apply the configuration file //If there is an available message from the RabbitMQ Broker, apply the configuration file
@ -267,6 +484,28 @@ internal static class Program
} }
} }
private static void LogMiddlewareFailure(Exception ex)
{
try
{
var logPath = "/home/inesco/SodiStoreHome/middleware_failures.log";
var logEntry =
$"[{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff}]\n" +
$"Exception: {ex.GetType().FullName}\n" +
$"Message: {ex.Message}\n" +
$"StackTrace:\n{ex.StackTrace}\n" +
$"--------------------------------------------------\n";
File.AppendAllText(logPath, logEntry);
}
catch
{
// Never allow logging to crash the service
}
}
private static void ApplyConfigFile(this StatusRecord? status, Configuration? config) private static void ApplyConfigFile(this StatusRecord? status, Configuration? config)
{ {
if (config == null) return; if (config == null) return;
@ -277,6 +516,14 @@ internal static class Program
status.Config.MaximumDischargingCurrent = config.MaximumDischargingCurrent; status.Config.MaximumDischargingCurrent = config.MaximumDischargingCurrent;
status.Config.OperatingPriority = config.OperatingPriority; status.Config.OperatingPriority = config.OperatingPriority;
status.Config.BatteriesCount = config.BatteriesCount; status.Config.BatteriesCount = config.BatteriesCount;
status.Config.ClusterNumber = config.ClusterNumber;
status.Config.StartTimeChargeandDischargeDayandTime = config.StartTimeChargeandDischargeDayandTime;
status.Config.StopTimeChargeandDischargeDayandTime = config.StopTimeChargeandDischargeDayandTime;
status.Config.TimeChargeandDischargePower = config.TimeChargeandDischargePower;
status.Config.PvNumber = config.PvNumber;
status.Config.ControlPermission = config.ControlPermission;
} }
private static async Task<Boolean> SaveModbusTcpFile(StatusRecord status) private static async Task<Boolean> SaveModbusTcpFile(StatusRecord status)
@ -285,13 +532,41 @@ internal static class Program
// SYSTEM DATA // SYSTEM DATA
var result1 = ConvertToModbusRegisters((status.Config.ModbusProtcolNumber * 10), "UInt16", 30001); // this to be updated to modbusTCP version var result1 = ConvertToModbusRegisters((status.Config.ModbusProtcolNumber * 10), "UInt16", 30001); // this to be updated to modbusTCP version
var result2 = ConvertToModbusRegisters(status.InverterRecord.SystemDateTime.ToUnixTime(), "UInt32", 30002); var result2 = ConvertToModbusRegisters(status.InverterRecord1.SystemDateTime.ToUnixTime(), "UInt32", 30002);
// SYSTEM DATA
var result3 = ConvertToModbusRegisters(status.InverterRecord1.WorkingMode, "UInt16", 30004);
// BATTERY SUMMARY (assuming single battery [0]) // this to be improved
var result4 = ConvertToModbusRegisters((status.Config.BatteriesCount), "UInt16", 31000);
var result8 = ConvertToModbusRegisters((status.InverterRecord1.Battery1Voltage.Value * 10), "UInt16", 31001);
var result12 = ConvertToModbusRegisters((status.InverterRecord1.Battery2Voltage.Value * 10), "Int16", 31002);
var result13 = ConvertToModbusRegisters((status.InverterRecord1.Battery1Current.Value * 10), "Int32", 31003);
var result16 = ConvertToModbusRegisters((status.InverterRecord1.Battery2Current.Value * 10), "Int32", 31005);
var result9 = ConvertToModbusRegisters((status.InverterRecord1.Battery1Soc.Value * 100), "UInt16", 31007);
var result14 = ConvertToModbusRegisters((status.InverterRecord1.Battery2Soc.Value * 100), "UInt16", 31008);
var result5 = ConvertToModbusRegisters((status.InverterRecord1.TotalBatteryPower.Value * 10), "Int32", 31009);
var result7 = ConvertToModbusRegisters((status.InverterRecord1.Battery1BackupSoc * 100), "UInt16", 31011);
var result20 = ConvertToModbusRegisters((status.InverterRecord1.Battery2BackupSoc * 100), "UInt16", 31012);
var result15 = ConvertToModbusRegisters((status.InverterRecord1.Battery1Soh.Value * 100), "UInt16", 31013);
var result26 = ConvertToModbusRegisters((status.InverterRecord1.Battery2Soh.Value * 100), "UInt16", 31014);
var result21 = ConvertToModbusRegisters((status.InverterRecord1.Battery1MaxChargingCurrent * 10), "UInt16", 31016);
var result22 = ConvertToModbusRegisters((status.InverterRecord1.Battery1MaxDischargingCurrent * 10), "UInt16", 31017);
var result18 = ConvertToModbusRegisters((status.InverterRecord1.PvTotalPower * 10), "UInt32", 32000);
var result19 = ConvertToModbusRegisters((status.InverterRecord1.GridPower * 10), "Int32", 33000);
var result23 = ConvertToModbusRegisters((status.InverterRecord1.GridVoltageFrequency * 10), "UInt16", 33002);
var result24 = ConvertToModbusRegisters((status.InverterRecord1.WorkingMode), "UInt16", 34000);
var result25 = ConvertToModbusRegisters((status.InverterRecord1.InverterActivePower * 10), "Int32", 34001);
var result29 = ConvertToModbusRegisters((status.InverterRecord1.EnableGridExport ), "UInt16", 34003);
var result27 = ConvertToModbusRegisters((status.InverterRecord1.PowerGridExportLimit ), "Int16", 34004);
// Merge all results into one dictionary // Merge all results into one dictionary
var allResults = new[] var allResults = new[]
{ {
result1,result2 result1, result2, result3, result4, result5, result23, result24, result25, result29, result27, result26, result7, result8, result9, result16, result20, result12, result13, result14, result15, result18, result19, result21, result22
}; };
foreach (var result in allResults) foreach (var result in allResults)
@ -518,7 +793,7 @@ internal static class Program
Console.WriteLine(error); Console.WriteLine(error);
await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension); await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension);
Heartbit(); // Heartbit();
return false; return false;
} }
@ -526,7 +801,7 @@ internal static class Program
Console.WriteLine("✅ File uploaded to S3 successfully."); Console.WriteLine("✅ File uploaded to S3 successfully.");
Console.WriteLine("---------------------------------------- Resending FailedUploadedFiles----------------------------------------"); Console.WriteLine("---------------------------------------- Resending FailedUploadedFiles----------------------------------------");
Heartbit(); // Heartbit();
await ResendLocalFailedFilesAsync(s3Config); // retry any pending failed files await ResendLocalFailedFilesAsync(s3Config); // retry any pending failed files
} }
@ -539,7 +814,7 @@ internal static class Program
await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension); await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension);
} }
Heartbit(); //Heartbit();
return false; return false;
} }
} }
@ -548,11 +823,10 @@ internal static class Program
return true; return true;
} }
private static void Heartbit() /* private static void Heartbit()
{ {
var s3Bucket = Config.Load().S3?.Bucket; var s3Bucket = Config.Load().S3?.Bucket;
var tryParse = int.TryParse(s3Bucket?.Split("-")[0], out var installationId); var tryParse = int.TryParse(s3Bucket?.Split("-")[0], out var installationId);
if (tryParse) if (tryParse)
{ {
var returnedStatus = new StatusMessage var returnedStatus = new StatusMessage
@ -565,7 +839,7 @@ internal static class Program
if (s3Bucket != null) if (s3Bucket != null)
RabbitMqManager.InformMiddleware(returnedStatus); RabbitMqManager.InformMiddleware(returnedStatus);
} }
} }*/
private static async Task SaveToLocalCompressedFallback(Byte[] compressedData, String fileNameWithoutExtension) private static async Task SaveToLocalCompressedFallback(Byte[] compressedData, String fileNameWithoutExtension)
{ {

View File

@ -55,7 +55,7 @@ public class ModbusDevice<[DynamicallyAccessedMembers(All)] R> where R : notnull
{ {
_modbusClient.Channel.ClearBuffers(); _modbusClient.Channel.ClearBuffers();
batch.Write(record); batch.Write(record);
//Thread.Sleep(50); // this added mainly for Growatt reading //Thread.Sleep(50); // this added mainly for Growatt writing
} }
} }
} }