From 545bb96b9d854c03c5a8b08a485329471a52fd25 Mon Sep 17 00:00:00 2001 From: atef Date: Fri, 27 Feb 2026 11:20:32 +0100 Subject: [PATCH] Update webscoket server failling --- csharp/App/GrowattCommunication/Program.cs | 65 ++- csharp/App/SinexcelCommunication/Program.cs | 408 +++++++++++++++--- .../Protocols/Modbus/Slaves/ModbusDevice.cs | 2 +- 3 files changed, 396 insertions(+), 79 deletions(-) diff --git a/csharp/App/GrowattCommunication/Program.cs b/csharp/App/GrowattCommunication/Program.cs index f643a613a..78461c1c9 100644 --- a/csharp/App/GrowattCommunication/Program.cs +++ b/csharp/App/GrowattCommunication/Program.cs @@ -170,8 +170,6 @@ public static class Program statusrecord.InverterRecord.WarningMainCode.WriteLine(" = WarningMainCode"); // 30408 this the duration statusrecord.InverterRecord.WarningSubCode.WriteLine(" = WarningSubCode"); // 30408 this the duration - - EssModeControl(statusrecord); statusrecord.ApplyDefaultSettings(); @@ -182,7 +180,6 @@ public static class Program statusrecord.Config.Save(); // save the config file growattDeviceT415K.Write(statusrecord.InverterRecord); Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff")); - } return statusrecord; } @@ -342,8 +339,7 @@ public static class Program var s3Bucket = Config.Load().S3?.Bucket; var subscribedNow = false; - //When the controller boots, it tries to subscribe to the queue - if (_subscribeToQueueForTheFirstTime == false) + if (_subscribeToQueueForTheFirstTime == false) { subscribedNow = true; _subscribeToQueueForTheFirstTime = true; @@ -356,12 +352,33 @@ public static class Program 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}"); - // Send message if: state changed, content changed, OR heartbeat needed - if (!subscribedNow && _subscribedToQueue && (stateChanged || contentChanged || needsHeartbeat)) + 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 (!subscribedNow && (stateChanged || contentChanged || needsHeartbeat)) { _prevSodiohomeAlarmState = currentSalimaxState.Status; - + // Set appropriate message type if (stateChanged || contentChanged) { @@ -374,13 +391,18 @@ public static class Program Console.WriteLine($"Sending Heartbeat message - {HeartbeatIntervalSeconds}s interval reached"); _lastHeartbeatTime = DateTime.Now; } - - if (s3Bucket != null) // why this is based on s3 bucket? + + try { 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 Configuration? config = SetConfigurationFile(); if (config != null) @@ -438,7 +460,28 @@ public static class Program } 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, System.Text.Json.JsonSerializerOptions?)")] private static async Task SaveModbusTcpFile(StatusRecord status) { diff --git a/csharp/App/SinexcelCommunication/Program.cs b/csharp/App/SinexcelCommunication/Program.cs index 26b210dd4..40e269f3e 100644 --- a/csharp/App/SinexcelCommunication/Program.cs +++ b/csharp/App/SinexcelCommunication/Program.cs @@ -25,6 +25,8 @@ using JsonSerializer = System.Text.Json.JsonSerializer; using static InnovEnergy.App.SinexcelCommunication.MiddlewareClasses.MiddlewareAgent; using System.Diagnostics.CodeAnalysis; 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. @@ -36,17 +38,28 @@ internal static class Program private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(5); private const UInt16 NbrOfFileToConcatenate = 15; // add this to config file 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 static Boolean _subscribedToQueue = false; private static Boolean _subscribeToQueueForTheFirstTime = false; private static SodistoreAlarmState _prevSodiohomeAlarmState = SodistoreAlarmState.Green; private static SodistoreAlarmState _sodiohomeAlarmState = SodistoreAlarmState.Green; + // Tracking for error/warning content changes + private static List _prevErrorCodes = new List(); + private static List _prevWarningCodes = new List(); + + // Heartbeat tracking + private static DateTime _lastHeartbeatTime = DateTime.MinValue; + private const Int32 HeartbeatIntervalSeconds = 60; + + // 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 Parity Parity = 0; //none private const Int32 StopBits = 1; @@ -55,7 +68,9 @@ internal static class Program 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(); while (true) { @@ -75,19 +90,22 @@ internal static class Program { Watchdog.NotifyReady(); - Console.WriteLine("Starting Sinexcel Communication"); + Console.WriteLine("Starting Sinexcel Communication, SW Version : " + SwVersionNumber); - var sinexcelDevice = new SinexcelDevice(_sinexcelChannel, SlaveId); - // var sinexcelDevice = new sinexcelDevices(new List { growattDeviceT415K }); + var sinexcelDevice1 = new SinexcelDevice(_sinexcelChannel1, SlaveId); + var sinexcelDevice2 = new SinexcelDevice(_sinexcelChannel2, SlaveId); StatusRecord? ReadStatus() { var config = Config.Load(); - var sinexcelRecord = sinexcelDevice.Read(); + var sinexcelRecord1 = sinexcelDevice1.Read(); + var sinexcelRecord2 = sinexcelDevice2.Read(); + return new StatusRecord { - InverterRecord = sinexcelRecord, + InverterRecord1 = sinexcelRecord1, + InverterRecord2 = sinexcelRecord2, Config = config // load from disk every iteration, so config can be changed while running }; } @@ -120,63 +138,176 @@ internal static class Program if (statusrecord == null) return null; - // Console.WriteLine( statusrecord.InverterRecord.TimedChargeAndDischargeOff + " TimedChargeAndDischargeOff "); - Console.WriteLine( statusrecord.InverterRecord.Battery1Power + " Battery1Power "); - 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.InverterRecord.DischargeEndTimePeriod1 + " DischargeEndTimePeriod1 "); + Console.WriteLine(" ************************************************ Inverter 1 ************************************************ "); + Console.WriteLine( statusrecord.InverterRecord1.SystemDateTime + " SystemDateTime "); + + Console.WriteLine( statusrecord.InverterRecord1.TotalPhotovoltaicPower + " TotalPhotovoltaicPower "); + Console.WriteLine( statusrecord.InverterRecord1.TotalBatteryPower + " TotalBatteryPower "); + Console.WriteLine( statusrecord.InverterRecord1.TotalLoadPower + " TotalLoadPower "); + Console.WriteLine( statusrecord.InverterRecord1.TotalGridPower + " TotalGridPower "); - 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); + + 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 ");*/ + + SendSalimaxStateAlarm(GetSodiHomeStateAlarm(statusrecord),statusrecord); + statusrecord.ControlConstants(); Console.WriteLine( " ************************************ We are writing ************************************"); var startWritingTime = DateTime.Now; Console.WriteLine(startWritingTime.ToString("HH:mm:ss.fff ") +"start Writing"); 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); - - var stopTime = DateTime.Now; - Console.WriteLine(stopTime.ToString("HH:mm:ss.fff ")+ "run iteration time finish"); + else + { + Console.WriteLine(" Nooooooo We cant' have the Right to Write"); + } + return statusrecord; } + catch (CrcException e) + { + Console.WriteLine(e); + throw; // restart only on CRC + } catch (Exception e) { - // Handle exception and print the error - Console.WriteLine(e ); - return null; + Console.WriteLine(e); + 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 StatusMessage GetSodiHomeStateAlarm(StatusRecord? record) + 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) { var s3Bucket = Config.Load().S3?.Bucket; @@ -231,6 +362,43 @@ internal static class Program return returnedStatus; } + /// + /// 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. + /// + 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(); + + // 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(); + + // 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) { var part = s3Bucket.Split('-').FirstOrDefault(); @@ -249,14 +417,63 @@ internal static class Program _subscribeToQueueForTheFirstTime = true; _prevSodiohomeAlarmState = currentSalimaxState.Status; _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 (!subscribedNow && _subscribedToQueue && currentSalimaxState.Status != _prevSodiohomeAlarmState) + if (!subscribedNow && (stateChanged || contentChanged || needsHeartbeat)) { _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); + 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 @@ -267,31 +484,89 @@ 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) { if (config == null) return; if (status == null) return; - status.Config.MinSoc = config.MinimumSoC; - status.Config.MaximumChargingCurrent = config.MaximumChargingCurrent; - status.Config.MaximumDischargingCurrent = config.MaximumDischargingCurrent; - status.Config.OperatingPriority = config.OperatingPriority; - status.Config.BatteriesCount = config.BatteriesCount; + status.Config.MinSoc = config.MinimumSoC; + status.Config.MaximumChargingCurrent = config.MaximumChargingCurrent; + status.Config.MaximumDischargingCurrent = config.MaximumDischargingCurrent; + status.Config.OperatingPriority = config.OperatingPriority; + 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 SaveModbusTcpFile(StatusRecord status) + private static async Task SaveModbusTcpFile(StatusRecord status) { var modbusData = new Dictionary(); // SYSTEM DATA 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); - - // Merge all results into one dictionary + 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 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) @@ -518,7 +793,7 @@ internal static class Program Console.WriteLine(error); await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension); - Heartbit(); + // Heartbit(); return false; } @@ -526,7 +801,7 @@ internal static class Program Console.WriteLine("✅ File uploaded to S3 successfully."); Console.WriteLine("---------------------------------------- Resending FailedUploadedFiles----------------------------------------"); - Heartbit(); + // Heartbit(); await ResendLocalFailedFilesAsync(s3Config); // retry any pending failed files } @@ -539,7 +814,7 @@ internal static class Program await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension); } - Heartbit(); + //Heartbit(); return false; } } @@ -548,11 +823,10 @@ internal static class Program return true; } - private static void Heartbit() + /* private static void Heartbit() { var s3Bucket = Config.Load().S3?.Bucket; var tryParse = int.TryParse(s3Bucket?.Split("-")[0], out var installationId); - if (tryParse) { var returnedStatus = new StatusMessage @@ -565,7 +839,7 @@ internal static class Program if (s3Bucket != null) RabbitMqManager.InformMiddleware(returnedStatus); } - } + }*/ private static async Task SaveToLocalCompressedFallback(Byte[] compressedData, String fileNameWithoutExtension) { diff --git a/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs b/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs index d8ba2cabc..63bcf8a58 100644 --- a/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs +++ b/csharp/Lib/Protocols/Modbus/Slaves/ModbusDevice.cs @@ -55,7 +55,7 @@ public class ModbusDevice<[DynamicallyAccessedMembers(All)] R> where R : notnull { _modbusClient.Channel.ClearBuffers(); batch.Write(record); - //Thread.Sleep(50); // this added mainly for Growatt reading + //Thread.Sleep(50); // this added mainly for Growatt writing } } } \ No newline at end of file