Compare commits

...

28 Commits

Author SHA1 Message Date
Yinyin Liu c076d55407 AI diagnosis UX improvements: status-aware, time-filtered, simpler explanations
- Only show AI diagnosis when installation status is red/orange (not green/offline)
- Filter alarms to last 24 hours to avoid showing outdated issues
- Show alarm name first with "Last seen" timestamp instead of "AI Diagnosis" label
- Update Mistral prompt for shorter, non-technical bullet-point explanations
- Fix Mistral JSON parsing when response wrapped in markdown code fences
- Add TestDiagnoseError endpoint for testing full AI flow without auth

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 12:16:35 +01:00
Yinyin Liu 9a723c0a6f Merge branch 'main' into integrated_AI 2026-02-16 15:18:54 +01:00
Yinyin Liu e35ad1d9d7 Merge branch 'main' of 91.92.155.224:Innovenergy/Innovenergy_trunk 2026-02-16 15:10:04 +01:00
Yinyin Liu ba20f35735 added Installation SN in SodistoreHome list page on monitor 2026-02-16 15:09:50 +01:00
atef 0b0d91f4dd add new function to ensure connection for sinexcel 2026-02-13 10:04:38 +01:00
atef d324de335e Update the midleeware message for configuration in Sinexcel project 2026-02-13 10:04:12 +01:00
atef 848d821c6c add the oldK2 for Tschireen 2026-02-13 10:03:48 +01:00
atef f897d49106 add release flag for deploy sinexcel 2026-02-13 10:03:05 +01:00
atef f821c7c645 Update main function for deligreen 2026-02-13 10:02:48 +01:00
atef 27d671d2f4 Update main function for growatt 2026-02-13 10:02:15 +01:00
atef 15ef53903f Update version number 2026-02-13 10:01:17 +01:00
atef f696424a71 Update the version number for SodiStoremax 2026-02-13 09:45:28 +01:00
atef fc79441c10 add the tschireen relay exception 2026-02-13 09:41:17 +01:00
atef 105a4e59f5 Add clear buffer for serialPort 2026-02-13 09:40:57 +01:00
atef bc130ea99c Add alarm and error sinexcel 2026-02-13 09:40:37 +01:00
atef 3b32b8a6dc update the config file sinexcel 2026-02-13 09:40:21 +01:00
atef 6c0271e70c update Modbus Library to achieve clear buffers regularly 2026-02-13 09:36:49 +01:00
atef 4c94238188 add mapping of the error and warning growatt 2026-02-13 09:36:12 +01:00
atef 07ede85347 Add Valerio Meter library 2026-02-13 09:35:17 +01:00
atef 5fd533419b add control permession for growatt project 2026-02-13 09:34:42 +01:00
atef 75eb7f3055 add deploy script for Kaco 2026-02-13 09:33:54 +01:00
atef 95270bb5d1 Create A sync my release file to have CI/CD sinexcel deployement 2026-02-13 09:33:41 +01:00
atef 2cf5ed4b95 Create Kaco project 2026-02-13 09:33:07 +01:00
atef bd572b4ed1 Update Amax register and put coils as writable as default.
new function to clear buffers.
Add old K2 variable as Tschireen have problem with Relay 23. Now its relay 22
2026-02-13 09:31:39 +01:00
atef 8c2a360ba3 Merge remote-tracking branch 'origin/main' 2026-02-13 09:29:19 +01:00
atef 08fcd7496f Add Alarm and Warning to sinexcel product.
Add time charge and discharge and control permession to config file
2026-02-13 09:29:08 +01:00
Yinyin Liu 4d6a2fdd4d Add .env to gitignore to prevent committing secrets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 11:45:06 +01:00
Yinyin Liu 02d2ef054b fix Mode reading on OverView Page from Inverter reading 2026-02-12 11:39:39 +01:00
73 changed files with 3679 additions and 486 deletions

View File

@ -821,6 +821,47 @@ public class Controller : ControllerBase
});
}
/// <summary>
/// Test endpoint for the full AI diagnostic flow (knowledge base + Mistral API).
/// No auth required. Remove before production.
/// Usage: GET /api/TestDiagnoseError?errorDescription=SomeAlarm
/// </summary>
[HttpGet(nameof(TestDiagnoseError))]
public async Task<ActionResult> TestDiagnoseError(string errorDescription = "AbnormalGridVoltage")
{
// 1. Try knowledge base first
var kbResult = AlarmKnowledgeBase.TryGetDiagnosis(errorDescription);
if (kbResult is not null)
{
return Ok(new
{
Source = "KnowledgeBase",
Alarm = errorDescription,
MistralEnabled = DiagnosticService.IsEnabled,
kbResult.Explanation,
kbResult.Causes,
kbResult.NextSteps
});
}
// 2. If not in KB, try Mistral directly with a test prompt
if (!DiagnosticService.IsEnabled)
return Ok(new { Source = "None", Alarm = errorDescription, Message = "Not in knowledge base and Mistral API key not configured." });
var aiResult = await DiagnosticService.TestCallMistralAsync(errorDescription);
if (aiResult is null)
return Ok(new { Source = "MistralFailed", Alarm = errorDescription, Message = "Mistral API call failed or returned empty." });
return Ok(new
{
Source = "MistralAI",
Alarm = errorDescription,
aiResult.Explanation,
aiResult.Causes,
aiResult.NextSteps
});
}
[HttpPut(nameof(UpdateFolder))]
public ActionResult<Folder> UpdateFolder([FromBody] Folder folder, Token authToken)
{

View File

@ -88,6 +88,28 @@ public static class DiagnosticService
return response;
}
// ── test helper (no DB dependency) ─────────────────────────────
/// <summary>
/// Calls Mistral directly with a generic prompt. For testing only - no DB lookup.
/// </summary>
public static async Task<DiagnosticResponse?> TestCallMistralAsync(string errorDescription)
{
if (!IsEnabled) return null;
// Check cache first
if (Cache.TryGetValue(errorDescription, out var cached))
return cached;
var prompt = BuildPrompt(errorDescription, "SodioHome", new List<string>());
var response = await CallMistralAsync(prompt);
if (response is not null)
Cache.TryAdd(errorDescription, response);
return response;
}
// ── prompt ──────────────────────────────────────────────────────
private static string BuildPrompt(string errorDescription, string productName, List<string> recentErrors)
@ -102,9 +124,12 @@ These are lithium-ion BESS units with a BMS, PV inverter, and grid inverter.
Error: {errorDescription}
Other recent errors: {recentList}
Explain in plain English for a homeowner. List likely causes and next steps.
Explain for a non-technical homeowner. Keep it very short and simple:
- explanation: 1 short sentence, no jargon
- causes: 2-3 bullet points, plain language
- nextSteps: 2-3 simple action items a homeowner can understand
Reply with ONLY valid JSON, no markdown:
{{""explanation"":""2-3 sentences"",""causes"":[""...""],""nextSteps"":[""...""]}}
{{""explanation"":""1 short sentence"",""causes"":[""...""],""nextSteps"":[""...""]}}
";
}
@ -142,8 +167,18 @@ Reply with ONLY valid JSON, no markdown:
return null;
}
// strip markdown code fences if Mistral wraps the JSON in ```json ... ```
var json = content.Trim();
if (json.StartsWith("```"))
{
var firstNewline = json.IndexOf('\n');
if (firstNewline >= 0) json = json[(firstNewline + 1)..];
if (json.EndsWith("```")) json = json[..^3];
json = json.Trim();
}
// parse the JSON the model produced
var diagnostic = JsonConvert.DeserializeObject<DiagnosticResponse>(content);
var diagnostic = JsonConvert.DeserializeObject<DiagnosticResponse>(json);
return diagnostic;
}
catch (FlurlHttpException httpEx)

View File

@ -55,7 +55,7 @@ internal static class Program
Console.WriteLine("Time used for reading all batteries:" + (stopTime - startTime));
Console.WriteLine("Average SOC " + batteriesRecord?.Soc);
/* Console.WriteLine("Cell Alarm 1 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellAlarmList[0]);
Console.WriteLine("Cell Alarm 1 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellAlarmList[0]);
Console.WriteLine("Cell Alarm 2 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellAlarmList[1]);
Console.WriteLine("Cell Temperature Alarm 1 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellTemperatureAlarm[0]);
@ -68,7 +68,7 @@ internal static class Program
Console.WriteLine("Battery 2 Current Alarm : " + batteriesRecord?.Devices[1].BatteryDeligreenAlarmRecord.CurrentAlarm);
Console.WriteLine("TotalVoltage Alarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.TotalVoltageAlarm);
Console.WriteLine("PowerTemp Alarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.PowerTempAlarm);*/
Console.WriteLine("PowerTemp Alarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.PowerTempAlarm);
Console.WriteLine("CellVoltageDropoutFault : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.AlarmEvent1.CellVoltageDropoutFault);
Console.WriteLine("HighVoltageAlarmForTotalVoltage : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.AlarmEvent2.HighVoltageAlarmForTotalVoltage);
@ -85,6 +85,18 @@ internal static class Program
Console.WriteLine("ManualChargingWaiting : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.OnOffState.ChargeSwitchState);
Console.WriteLine("CurrentCalibrationNotPerformed : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.SystemState.Charge);
Console.WriteLine("Power : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.Power);
Console.WriteLine("BusCurrent : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.BusCurrent);
Console.WriteLine("BusVoltage : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.BusVoltage);
Console.WriteLine("BatteryCapacity : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.BatteryCapacity);
Console.WriteLine("NumberOfCycles : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.NumberOfCycles);
Console.WriteLine("FwVersion : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.FwVersion);
Console.WriteLine("Soc : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.Soc);
Console.WriteLine("Soh : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.Soh);
Console.WriteLine("RatedCapacity : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.RatedCapacity);
Console.WriteLine("ResidualCapacity : " + batteriesRecord?.Devices[0].BatteryDeligreenDataRecord.ResidualCapacity);
// Wait for 2 seconds before the next reading
await Task.Delay(2000); // Delay in milliseconds (2000ms = 2 seconds)
}

View File

@ -10,6 +10,6 @@ public class Configuration
public Double MaximumChargingCurrent { get; set; }
public EssMode OperatingPriority { get; set; }
public Int16 BatteriesCount { get; set; }
public Boolean ControlPermission { get; set; }
}

View File

@ -68,7 +68,8 @@ public static class MiddlewareAgent
if (config != null)
{
Console.WriteLine($"Received a configuration message: " +
"MinimumSoC is " + config.MinimumSoC + " and operating priorty is " +config.OperatingPriority + "Number of batteries is " + config.BatteriesCount + config.MaximumChargingCurrent + config.MaximumDischargingCurrent);
"MinimumSoC is " + config.MinimumSoC + " and operating priorty is " +config.OperatingPriority + "Number of batteries is " + config.BatteriesCount
+ "MaximumChargingCurrent is " + config.MaximumChargingCurrent + "MaximumDischargingCurrent " + config.MaximumDischargingCurrent + " Control permission is" + config.ControlPermission );
// Send the reply to the sender's endpoint
_udpListener.Send(replyData, replyData.Length, serverEndpoint);

View File

@ -56,6 +56,4 @@ public static class RabbitMqManager
Console.WriteLine($"Producer sent message: {message}");
}
}

View File

@ -32,13 +32,13 @@ namespace InnovEnergy.App.GrowattCommunication;
public static class Program
{
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(5);
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(6);
private const UInt16 NbrOfFileToConcatenate = 15; // add this to config file
private static UInt16 _fileCounter = 0;
//
private static Channel _growattChannel;
private const String SwVersionNumber =" V1.00.310725 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;
@ -142,14 +142,14 @@ public static class Program
statusrecord.InverterRecord.EnableEmsCommunicationFailureTime.WriteLine(" = EnableEmsCommunicationFailureTime");
statusrecord.InverterRecord.EnableCommand.WriteLine(" = EnableCommand");
statusrecord.InverterRecord.ControlPermession.WriteLine(" = ControlPermession");
statusrecord.InverterRecord.ControlPermission.WriteLine(" = ControlPermession");
statusrecord.InverterRecord.GridPower.WriteLine(" MeterPower");
statusrecord.InverterRecord.ConsumptionPower.WriteLine(" ConsumptionPower");
statusrecord.InverterRecord.ExportedPowerToGridMeter.WriteLine(" ExportedPowerToGrid");
statusrecord.InverterRecord.ImportedPowerFromGrid.WriteLine(" ImportedPowerFromGrid");
statusrecord.InverterRecord.InverterActivePower.WriteLine(" InverterActivePower");
statusrecord.InverterRecord.MaxSoc.WriteLine(" BatteryChargeCutoffVoltage "); //30409 we set power here
statusrecord.InverterRecord.MinSoc.WriteLine(" BatteryDishargeCutoffVoltage "); //30409 we set power here
statusrecord.InverterRecord.MaxSoc.WriteLine(" MaxSoc "); //30409 we set power here
statusrecord.InverterRecord.MinSoc.WriteLine(" MinSoc "); //30409 we set power here
statusrecord.InverterRecord.PowerFactor.WriteLine(" = PowerFactor");
statusrecord.InverterRecord.Battery1Soc.WriteLine(" SOC");
statusrecord.InverterRecord.Battery1Power.WriteLine(" Battery Power");
@ -157,7 +157,6 @@ public static class Program
statusrecord.InverterRecord.Battery1Voltage.WriteLine(" Battery Voltage");
statusrecord.InverterRecord.BatteryMaxChargingCurrent.WriteLine(" BatteryMaxChargeCurrent "); //30409 we set power here
statusrecord.InverterRecord.BatteryMaxDischargingCurrent.WriteLine(" BatteryMaxDischargeCurrent "); //30409 we set power here
statusrecord.InverterRecord.MinSoc.WriteLine(" MinSoc "); //30409 we set power here
statusrecord.InverterRecord.ChargeCutoffSocVoltage.WriteLine(" ChargeCutoffSoc "); //30409 we set power here
statusrecord.InverterRecord.PvPower.WriteLine(" Pv Power "); //30409 we set power here
@ -206,6 +205,7 @@ public static class Program
status.Config.MaximumDischargingCurrent = config.MaximumDischargingCurrent;
status.Config.OperatingPriority = config.OperatingPriority;
status.Config.BatteriesCount = config.BatteriesCount;
status.Config.ControlPermission = config.ControlPermission;
}
private static String EssModeControl(StatusRecord? statusrecord)
@ -247,11 +247,10 @@ public static class Program
var alarmList = new List<AlarmOrWarning>();
var warningList = new List<AlarmOrWarning>();
if (record.InverterRecord.SystemOperatingMode == GrowattSystemStatus.Fault)
{
if (record.InverterRecord.FaultMainCode != 0)
if (record.InverterRecord.WarningMainCode != 0)
{
alarmList.Add(new AlarmOrWarning
warningList.Add(new AlarmOrWarning
{
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
@ -260,9 +259,9 @@ public static class Program
});
}
if (record.InverterRecord.WarningMainCode != 0)
if (record.InverterRecord.FaultMainCode != 0)
{
warningList.Add(new AlarmOrWarning
alarmList.Add(new AlarmOrWarning
{
Date = DateTime.Now.ToString("yyyy-MM-dd"),
Time = DateTime.Now.ToString("HH:mm:ss"),
@ -270,7 +269,7 @@ public static class Program
Description = record.InverterRecord.FaultMainCode.ToString(), //to add the sub code
});
}
}
_sodiohomeAlarmState = warningList.Any()
? SodistoreAlarmState.Orange
@ -376,7 +375,7 @@ public static class Program
_lastHeartbeatTime = DateTime.Now;
}
if (s3Bucket != null)
if (s3Bucket != null) // why this is based on s3 bucket?
{
RabbitMqManager.InformMiddleware(currentSalimaxState);
}
@ -401,9 +400,8 @@ public static class Program
st.InverterRecord.EmsCommunicationFailureTime = 20; // 20 sec
st.InverterRecord.EnableEmsCommunicationFailureTime = false;
st.InverterRecord.EnableCommand = true;
st.InverterRecord.ControlPermession = true;
st.InverterRecord.MaxSoc = 60; //st.Config.BatteryChargeCutoffVoltage;
st.InverterRecord.MinSoc = 25; //st.Config.BatteryDischargeCutoffVoltage;
st.InverterRecord.ControlPermission = st.Config.ControlPermission;;
st.InverterRecord.MaxSoc = 100; //st.Config.MaxSoc;
}
private static Dictionary<String, UInt16> ConvertToModbusRegisters(Object value, String outputType,
@ -645,7 +643,7 @@ public static class Program
Console.WriteLine(error);
await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension);
Heartbit();
// Heartbit();
return false;
}
@ -654,7 +652,7 @@ public static class Program
Console.WriteLine(
"---------------------------------------- Resending FailedUploadedFiles----------------------------------------");
Heartbit();
//Heartbit();
await ResendLocalFailedFilesAsync(s3Config); // retry any pending failed files
}
@ -667,7 +665,7 @@ public static class Program
await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension);
}
Heartbit();
//Heartbit();
return false;
}
}
@ -676,7 +674,7 @@ public 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);
@ -693,7 +691,7 @@ public static class Program
if (s3Bucket != null)
RabbitMqManager.InformMiddleware(returnedStatus);
}
}
}*/
private static async Task SaveToLocalCompressedFallback(Byte[] compressedData, String fileNameWithoutExtension)
{

View File

@ -21,6 +21,8 @@ public class Config //TODO: let IE choose from config files (Json) and connect t
public required EssMode OperatingPriority { get; set; }
public required Int16 BatteriesCount { get; set; }
public required Double ModbusProtcolNumber { get; set; }
public required Boolean ControlPermission { get; set; }
public required S3Config? S3 { get; set; }
@ -36,6 +38,7 @@ public class Config //TODO: let IE choose from config files (Json) and connect t
OperatingPriority = EssMode.LoadPriority,
BatteriesCount = 0,
ModbusProtcolNumber = 1.2,
ControlPermission = false,
S3 = new()
{
Bucket = "1-3e5b3069-214a-43ee-8d85-57d72000c19d",

View File

@ -0,0 +1,33 @@
using System.Text;
namespace InnovEnergy.App.KacoCommunication.DataLogging;
public class LogFileConcatenator
{
private readonly String _LogDirectory;
public LogFileConcatenator(String logDirectory = "JsonLogDirectory/")
{
_LogDirectory = logDirectory;
}
public String ConcatenateFiles(int numberOfFiles)
{
var logFiles = Directory
.GetFiles(_LogDirectory, "log_*.json")
.OrderByDescending(file => file)
.Take(numberOfFiles)
.OrderBy(file => file)
.ToList();
var concatenatedContent = new StringBuilder();
foreach (var fileContent in logFiles.Select(File.ReadAllText))
{
concatenatedContent.AppendLine(fileContent);
//concatenatedContent.AppendLine(); // Append an empty line to separate the files // maybe we don't need this
}
return concatenatedContent.ToString();
}
}

View File

@ -0,0 +1,48 @@
using InnovEnergy.Lib.Utils;
using Microsoft.Extensions.Logging;
namespace InnovEnergy.App.KacoCommunication.DataLogging;
public class CustomLogger : ILogger
{
private readonly String _LogFilePath;
//private readonly Int64 _maxFileSizeBytes;
private readonly Int32 _MaxLogFileCount;
private Int64 _CurrentFileSizeBytes;
public CustomLogger(String logFilePath, Int32 maxLogFileCount)
{
_LogFilePath = logFilePath;
_MaxLogFileCount = maxLogFileCount;
_CurrentFileSizeBytes = File.Exists(logFilePath) ? new FileInfo(logFilePath).Length : 0;
}
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => throw new NotImplementedException();
public Boolean IsEnabled(LogLevel logLevel) => true; // Enable logging for all levels
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception, String> formatter)
{
var logMessage = formatter(state, exception!);
// Check the log file count and delete the oldest file if necessary
var logFileDir = Path.GetDirectoryName(_LogFilePath)!;
var logFileExt = Path.GetExtension(_LogFilePath);
var logFileBaseName = Path.GetFileNameWithoutExtension(_LogFilePath);
var logFiles = Directory
.GetFiles(logFileDir, $"{logFileBaseName}_*{logFileExt}")
.OrderBy(file => file)
.ToList();
if (logFiles.Count >= _MaxLogFileCount)
{
File.Delete(logFiles.First());
}
var roundedUnixTimestamp = DateTime.Now.ToUnixTime() % 2 == 0 ? DateTime.Now.ToUnixTime() : DateTime.Now.ToUnixTime() + 1;
var timestamp = "Timestamp;" + roundedUnixTimestamp + Environment.NewLine;
var logFileBackupPath = Path.Combine(logFileDir, $"{logFileBaseName}_{DateTime.Now.ToUnixTime()}{logFileExt}");
File.AppendAllText(logFileBackupPath, timestamp + logMessage + Environment.NewLine);
}
}

View File

@ -0,0 +1,39 @@
using Microsoft.Extensions.Logging;
namespace InnovEnergy.App.KacoCommunication.DataLogging;
public static class Logger
{
// Specify the maximum log file size in bytes (e.g., 1 MB)
//private const Int32 MaxFileSizeBytes = 2024 * 30; // TODO: move to settings
private const Int32 MaxLogFileCount = 5000; // TODO: move to settings
private const String LogFilePath = "JsonLogDirectory/log.json"; // TODO: move to settings
// ReSharper disable once InconsistentNaming
private static readonly ILogger _logger = new CustomLogger(LogFilePath, MaxLogFileCount);
public static T LogInfo<T>(this T t) where T : notnull
{
_logger.LogInformation(t.ToString()); // TODO: check warning
return t;
}
public static T LogDebug<T>(this T t) where T : notnull
{
// _logger.LogDebug(t.ToString()); // TODO: check warning
return t;
}
public static T LogError<T>(this T t) where T : notnull
{
// _logger.LogError(t.ToString()); // TODO: check warning
return t;
}
public static T LogWarning<T>(this T t) where T : notnull
{
// _logger.LogWarning(t.ToString()); // TODO: check warning
return t;
}
}

View File

@ -0,0 +1,9 @@
namespace InnovEnergy.App.KacoCommunication.DataTypes;
public class AlarmOrWarning
{
public String? Date { get; set; }
public String? Time { get; set; }
public String? Description { get; set; }
public String? CreatedBy { get; set; }
}

View File

@ -0,0 +1,19 @@
namespace InnovEnergy.App.KacoCommunication.DataTypes;
public class Configuration
{
public Double MinimumSoC { get; set; }
public Double MaximumDischargingCurrent { get; set; }
public Double MaximumChargingCurrent { get; set; }
//public WorkingMode OperatingPriority { get; set; }
public Int16 BatteriesCount { get; set; }
public Int16 ClusterNumber { get; set; }
public Int16 PvNumber { get; set; }
public DateTime StartTimeChargeandDischargeDayandTime { get; set; }
public DateTime StopTimeChargeandDischargeDayandTime { get; set; }
public Single TimeChargeandDischargePower { get; set; }
public Boolean ControlPermission { get; set; }
}

View File

@ -0,0 +1,8 @@
namespace InnovEnergy.App.KacoCommunication.DataTypes;
public enum SodistoreAlarmState
{
Green,
Orange,
Red
}

View File

@ -0,0 +1,17 @@
namespace InnovEnergy.App.KacoCommunication.DataTypes;
public class StatusMessage
{
public required Int32 InstallationId { get; set; }
public required Int32 Product { get; set; }
public required SodistoreAlarmState 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

@ -0,0 +1,8 @@
namespace InnovEnergy.App.KacoCommunication.Devices;
public enum DeviceState
{
Disabled,
Measured,
Computed
}

View File

@ -0,0 +1,9 @@
using InnovEnergy.Lib.Utils.Net;
namespace InnovEnergy.App.KacoCommunication.Devices;
public class SalimaxDevice : Ip4Address
{
public required DeviceState DeviceState { get; init; }
}

View File

@ -0,0 +1,23 @@
using InnovEnergy.App.KacoCommunication.SystemConfig;
using InnovEnergy.Lib.Devices.BatteryDeligreen;
using InnovEnergy.Lib.Devices.Kaco92L3;
using InnovEnergy.Lib.Devices.PLVario2Meter;
using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
namespace InnovEnergy.App.KacoCommunication.ESS;
public class StatusRecord
{
public required KacoRecord? InverterRecord { get; set; }
public required PlVarioMeterRecord? GridMeterRecord { get; set; }
public required DcDcDevicesRecord? DcDc { get; init; }
public required BatteryDeligreenRecords? BatteryKabinet1 { get; set; }
public required BatteryDeligreenRecords? BatteryKabinet2 { get; set; }
public required BatteryDeligreenRecords? BatteryKabinet3 { get; set; }
public required Config Config { get; set; }
}

View File

@ -0,0 +1,55 @@
using System.Diagnostics.CodeAnalysis;
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.KacoCommunication;
public static class Flow
{
private static readonly String RightArrowChar = ">";
private static readonly String LeftArrowChar = "<";
private static readonly String DownArrowChar = "V";
private static readonly String UpArrowChar = "^";
private static readonly String UnknownArrowChar = "?";
public static TextBlock Horizontal(Unit? amount) => Horizontal(amount, 10);
public static TextBlock Horizontal(Unit? amount, Int32 width)
{
var label = amount?.ToDisplayString() ?? "";
var arrowChar = amount switch
{
{ Value: < 0 } => LeftArrowChar,
{ Value: >= 0 } => RightArrowChar,
_ => UnknownArrowChar,
};
//var arrowChar = amount.Value < 0 ? LeftArrowChar : RightArrowChar;
var arrow = Enumerable.Repeat(arrowChar, width).Join();
// note : appending "fake label" below to make it vertically symmetric
return TextBlock.AlignCenterHorizontal(label, arrow, "");
}
public static TextBlock Vertical(Unit? amount) => Vertical(amount, 4);
[SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
[SuppressMessage("ReSharper", "CoVariantArrayConversion")]
public static TextBlock Vertical(Unit? amount, Int32 height)
{
var label = amount?.ToDisplayString() ?? UnknownArrowChar;
var arrowChar = amount switch
{
{ Value: < 0 } => UpArrowChar,
{ Value: >= 0 } => DownArrowChar,
_ => UnknownArrowChar,
};
// var arrowChar = amount is null ? UnknownArrowChar
// : amount.Value < 0 ? UpArrowChar
// : DownArrowChar;
return TextBlock.AlignCenterHorizontal(arrowChar, arrowChar, label, arrowChar, arrowChar);
}
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="../InnovEnergy.App.props" />
<PropertyGroup>
<RootNamespace>InnovEnergy.App.KacoCommunication</RootNamespace>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Lib\Devices\BatteryDeligreen\BatteryDeligreen.csproj" />
<ProjectReference Include="..\..\Lib\Devices\Kaco92L3\Kaco92L3.csproj" />
<ProjectReference Include="..\..\Lib\Devices\PLVario2Meter\PLVario2Meter.csproj" />
<ProjectReference Include="..\..\Lib\Devices\Trumpf\TruConvertDc\TruConvertDc.csproj" />
<ProjectReference Include="..\..\Lib\Units\Units.csproj" />
<ProjectReference Include="..\..\Lib\Utils\Utils.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Flurl.Http" Version="4.0.2" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="RabbitMQ.Client" Version="6.6.0" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,91 @@
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Text.Json;
using InnovEnergy.App.KacoCommunication.DataTypes;
namespace InnovEnergy.App.KacoCommunication.MiddlewareClasses;
public static class MiddlewareAgent
{
private static UdpClient _udpListener = null!;
private static IPAddress? _controllerIpAddress;
private static EndPoint? _endPoint;
public static void InitializeCommunicationToMiddleware()
{
_controllerIpAddress = FindVpnIp();
if (Equals(IPAddress.None, _controllerIpAddress))
{
Console.WriteLine("There is no VPN interface, exiting...");
}
const Int32 udpPort = 9000;
_endPoint = new IPEndPoint(_controllerIpAddress, udpPort);
_udpListener = new UdpClient();
_udpListener.Client.Blocking = false;
_udpListener.Client.Bind(_endPoint);
}
private static IPAddress FindVpnIp()
{
const String interfaceName = "innovenergy";
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
foreach (var networkInterface in networkInterfaces)
{
if (networkInterface.Name == interfaceName)
{
var ipProps = networkInterface.GetIPProperties();
var uniCastIPs = ipProps.UnicastAddresses;
var controllerIpAddress = uniCastIPs[0].Address;
Console.WriteLine("VPN IP is: "+ uniCastIPs[0].Address);
return controllerIpAddress;
}
}
return IPAddress.None;
}
public static Configuration? SetConfigurationFile()
{
if (_udpListener.Available > 0)
{
IPEndPoint? serverEndpoint = null;
var replyMessage = "ACK";
var replyData = Encoding.UTF8.GetBytes(replyMessage);
var udpMessage = _udpListener.Receive(ref serverEndpoint);
var message = Encoding.UTF8.GetString(udpMessage);
var config = JsonSerializer.Deserialize<Configuration>(message);
if (config != null)
{
Console.WriteLine($"Received a configuration message: " +
"MinimumSoC is " + config.MinimumSoC + "Number of batteries is " + config.BatteriesCount
+ "Maximum Charging current is "+ config.MaximumChargingCurrent + "/n" + "Maximum Discharging current is " + config.MaximumDischargingCurrent
+ "StartTimeChargeandDischargeDayandTime is" + config.StartTimeChargeandDischargeDayandTime + "StopTimeChargeandDischargeDayandTime is" + config.StopTimeChargeandDischargeDayandTime
+ "TimeChargeandDischargePowert is " + config.TimeChargeandDischargePower + " Control permission is" + config.ControlPermission);
// Send the reply to the sender's endpoint
_udpListener.Send(replyData, replyData.Length, serverEndpoint);
Console.WriteLine($"Replied to {serverEndpoint}: {replyMessage}");
return config;
}
}
if (_endPoint != null && !_endPoint.Equals(_udpListener.Client.LocalEndPoint as IPEndPoint))
{
Console.WriteLine("UDP address has changed, rebinding...");
InitializeCommunicationToMiddleware();
}
return null;
}
}

View File

@ -0,0 +1,64 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Text.Json;
using InnovEnergy.App.KacoCommunication.DataTypes;
using RabbitMQ.Client;
namespace InnovEnergy.App.KacoCommunication.MiddlewareClasses;
[SuppressMessage("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic access otherwise can break functionality when trimming application code")]
public static class RabbitMqManager
{
public static ConnectionFactory? Factory ;
public static IConnection ? Connection;
public static IModel? Channel;
public static Boolean SubscribeToQueue(StatusMessage currentSalimaxState, String? s3Bucket,String VpnServerIp)
{
try
{
//_factory = new ConnectionFactory { HostName = VpnServerIp };
Factory = new ConnectionFactory
{
HostName = VpnServerIp,
Port = 5672,
VirtualHost = "/",
UserName = "producer",
Password = "b187ceaddb54d5485063ddc1d41af66f",
};
Connection = Factory.CreateConnection();
Channel = Connection.CreateModel();
Channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
Console.WriteLine("The controller sends its status to the middleware for the first time");
if (s3Bucket != null) InformMiddleware(currentSalimaxState);
}
catch (Exception ex)
{
Console.WriteLine("An error occurred while connecting to the RabbitMQ queue: " + ex.Message);
return false;
}
return true;
}
public static void InformMiddleware(StatusMessage status)
{
var message = JsonSerializer.Serialize(status);
var body = Encoding.UTF8.GetBytes(message);
Channel.BasicPublish(exchange: string.Empty,
routingKey: "statusQueue",
basicProperties: null,
body: body);
Console.WriteLine($"Producer sent message: {message}");
}
}

View File

@ -0,0 +1,637 @@
// See https://aka.ms/new-console-template for more information
using System.IO.Compression;
using System.Reactive.Linq;
using System.Reactive.Threading.Tasks;
using System.Text;
using System.Text.Json;
using Flurl.Http;
using InnovEnergy.App.KacoCommunication.DataLogging;
using InnovEnergy.App.KacoCommunication.DataTypes;
using InnovEnergy.App.KacoCommunication.Devices;
using InnovEnergy.App.KacoCommunication.ESS;
using InnovEnergy.App.KacoCommunication.MiddlewareClasses;
using InnovEnergy.App.KacoCommunication.SystemConfig;
using InnovEnergy.Lib.Devices.BatteryDeligreen;
using InnovEnergy.Lib.Devices.Kaco92L3;
using InnovEnergy.Lib.Devices.Kaco92L3.DataType;
using InnovEnergy.Lib.Devices.PLVario2Meter;
using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
using InnovEnergy.Lib.Protocols.Modbus.Channels;
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Utils;
using Newtonsoft.Json;
using Formatting = Newtonsoft.Json.Formatting;
using JsonSerializer = System.Text.Json.JsonSerializer;
namespace InnovEnergy.App.KacoCommunication;
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 SodistoreAlarmState _prevSodiohomeAlarmState = SodistoreAlarmState.Green;
private static SodistoreAlarmState _sodiAlarmState = SodistoreAlarmState.Green;
private static readonly IReadOnlyList<Byte> BatteryNodes;
private static readonly Channel KacoChannel;
private static readonly Channel GridMeterChannel;
private static readonly Channel DcDcChannel;
private const String Port1Cabinet = "/dev/ttyUSB0"; // move to a config file
private const String Port2Cabinet = "/dev/ttyUSB1"; // move to a config file
private const String Port3Cabinet = "/dev/ttyUSB2"; // move to a config file
private static readonly String SwVersionNumber = " V1.00." + DateTime.Today;
private const String VpnServerIp = "10.2.0.11";
public static Boolean _subscribedToQueue = false;
public static Boolean _subscribeToQueueForTheFirstTime = false;
private static Int32 _failsCounter = 0; // move to a config file
// private static SodistoreAlarmState _prevSodiohomeAlarmState = SodistoreAlarmState.Green;
// private static SodistoreAlarmState _sodiohomeAlarmState = SodistoreAlarmState.Green;
static Program()
{
var config = Config.Load();
var d = config.Devices;
Channel CreateChannel(SalimaxDevice device) => device.DeviceState == DeviceState.Disabled
? new NullChannel()
: new TcpChannel(device);
BatteryNodes = config
.Devices
.BatteryNodes
.Select(n => n.ConvertTo<Byte>())
.ToArray(config.Devices.BatteryNodes.Length);
KacoChannel = CreateChannel(d.KacoIp);
GridMeterChannel = CreateChannel(d.GridMeterIp);
DcDcChannel = CreateChannel(d.DcDcIp);
}
public static async Task Main(String[] args)
{
while (true)
{
try
{
await Run();
}
catch (Exception e)
{
// e.LogError();
}
}
// ReSharper disable once FunctionNeverReturns
}
private static async Task Run()
{
Watchdog.NotifyReady();
Console.WriteLine("Starting Kaco Communication");
var kacoDevice = new KacoDevice(KacoChannel);
var gridMeterDevice = new PlVarioMeterDevice(GridMeterChannel);
var dcDcDevices = new TruConvertDcDcDevices(DcDcChannel);
var firstCabinetBatteriesDevice = BatteryNodes.Select(n => new BatteryDeligreenDevice(Port1Cabinet, n)).ToList();
var secondCabinetBatteriesDevice = BatteryNodes.Select(n => new BatteryDeligreenDevice(Port2Cabinet, n)).ToList();
var thirdCabinetBatteriesDevice = BatteryNodes.Select(n => new BatteryDeligreenDevice(Port3Cabinet, n)).ToList();
var batteryDevices1 = new BatteryDeligreenDevices(firstCabinetBatteriesDevice);
var batteryDevices2 = new BatteryDeligreenDevices(secondCabinetBatteriesDevice);
var batteryDevices3 = new BatteryDeligreenDevices(thirdCabinetBatteriesDevice);
StatusRecord? ReadStatus()
{
PlVarioMeterRecord? gridRecord = null;
var config = Config.Load();
var kacoRecord = kacoDevice.Read();
var gridrawRecord = gridMeterDevice.Read();
var dcDcRecord = dcDcDevices.Read();
if (gridrawRecord != null)
{
gridRecord = new PlVarioMeterRecord(gridrawRecord);
}
var batteryKabinet1 = batteryDevices1.Read();
var batteryKabinet2 = batteryDevices2.Read();
var batteryKabinet3 = batteryDevices3.Read();
return new StatusRecord
{
InverterRecord = kacoRecord,
GridMeterRecord = gridRecord,
DcDc = dcDcRecord,
BatteryKabinet1 = batteryKabinet1,
BatteryKabinet2 = batteryKabinet2,
BatteryKabinet3 = batteryKabinet3,
Config = config // load from disk every iteration, so config can be changed while running
};
}
while (true)
{
await Observable
.Interval(UpdateInterval)
.Select(_ => RunIteration())
.SelectMany(status =>
DataLogging(status, DateTime.Now.Round(UpdateInterval))
.ContinueWith(_ => status)) // back to StatusRecord
.SelectMany(SaveModbusTcpFile)
.SelectError()
.ToTask();
}
StatusRecord? RunIteration()
{
try
{
Watchdog.NotifyAlive();
var startTime = DateTime.Now;
Console.WriteLine(
"***************************** Reading Kaco Data *********************************************");
Console.WriteLine(startTime.ToString("HH:mm:ss.fff"));
// the order matter of the next three lines
var statusrecord = ReadStatus();
statusrecord?.CreateSimpleTopologyTextBlock().WriteLine();
// statusrecord?.DcDc?.Dc.Battery.Power .WriteLine(" Power");
// statusrecord?.DcDc?.Dc.Battery.Voltage .WriteLine(" Voltage");
// statusrecord?.DcDc?.Dc.Battery.Current .WriteLine(" Current");
// statusrecord?.DcDc?.Dc.Link.Voltage .WriteLine(" Dc link Voltage");
statusrecord?.GridMeterRecord?.Frequency .WriteLine(" Frequency");
statusrecord?.GridMeterRecord?.VoltageU1 .WriteLine(" VoltageU1");
statusrecord?.GridMeterRecord?.VoltageU2 .WriteLine(" VoltageU2");
statusrecord?.GridMeterRecord?.VoltageU3 .WriteLine(" VoltageU3");
statusrecord?.GridMeterRecord?.CurrentI1 .WriteLine(" CurrentI1");
statusrecord?.GridMeterRecord?.CurrentI2 .WriteLine(" CurrentI2");
statusrecord?.GridMeterRecord?.CurrentI3 .WriteLine(" CurrentI3");
statusrecord?.GridMeterRecord?.ActivePowerL1 .WriteLine(" ActivePowerL1");
statusrecord?.GridMeterRecord?.ActivePowerL2 .WriteLine(" ActivePowerL2");
statusrecord?.GridMeterRecord?.ActivePowerL3 .WriteLine(" ActivePowerL3");
statusrecord?.GridMeterRecord?.ActivePowerTotal .WriteLine(" ActivePowerTotal");
statusrecord?.InverterRecord?.CurrentState.WriteLine(" CurrentState");
statusrecord?.InverterRecord?.RequestedState.WriteLine(" RequestedState");
statusrecord?.InverterRecord?.PcuError.WriteLine(" PcuError");
statusrecord?.InverterRecord?.PcuState.WriteLine(" PcuState");
statusrecord?.InverterRecord?.BattCharId.WriteLine(" _battCharId");
statusrecord?.InverterRecord?.BattCharLength.WriteLine(" _battCharLength");
statusrecord?.InverterRecord?.MinDischargeVoltage.WriteLine(" MinDischargeVoltage");
statusrecord?.InverterRecord?.MaxDischargeCurrent.WriteLine(" MaxDischargeCurrent");
statusrecord?.InverterRecord?.DischargeCutoffCurrent.WriteLine(" DischargeCutoffCurrent");
statusrecord?.InverterRecord?.MaxChargeVoltage.WriteLine(" MaxChargeVoltage");
statusrecord?.InverterRecord?.MaxChargeCurrent.WriteLine(" MaxChargeCurrent");
statusrecord?.InverterRecord?.ChargeCutoffCurrent.WriteLine(" ChargeCutoffCurrent");
statusrecord?.InverterRecord?.ActivePowerSetPercent.WriteLine(" ActivePowerSetPercent");
statusrecord?.InverterRecord?.ReactivePowerSetPercent.WriteLine(" ReactivePowerSetPercent");
statusrecord?.InverterRecord?.WatchdogSeconds.WriteLine(" WatchdogSeconds");
InitializeKacoStartup(statusrecord);
Console.WriteLine( " ************************************ We are writing ************************************");
statusrecord?.Config.Save(); // save the config file
if (statusrecord?.InverterRecord != null) kacoDevice.Write(statusrecord.InverterRecord);
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss.fff"));
return statusrecord;
}
catch (Exception e)
{
// Handle exception and print the error
Console.WriteLine(e);
return null;
}
}
}
private static async Task SavingLocalCsvFile(Int64 timestamp, String csv)
{
const String directoryPath = "/home/inesco/salimax/csvFile";
// Ensure directory exists
if (!Directory.Exists(directoryPath))
{
Directory.CreateDirectory(directoryPath);
}
// Get all .csv files ordered by creation time (oldest first)
var csvFiles = new DirectoryInfo(directoryPath)
.GetFiles("*.csv")
.OrderBy(f => f.CreationTimeUtc)
.ToList();
// If more than 5000 files, delete the oldest
if (csvFiles.Count >= 5000)
{
var oldestFile = csvFiles.First();
try
{
oldestFile.Delete();
}
catch (Exception ex)
{
Console.WriteLine($"Failed to delete file: {oldestFile.FullName}, Error: {ex.Message}");
}
}
// Prepare the filtered CSV content
var filteredCsv = csv
.SplitLines()
.Where(l => !l.Contains("Secret"))
.JoinLines();
// Save the new CSV file
var filePath = Path.Combine(directoryPath, timestamp + ".csv");
await File.WriteAllTextAsync(filePath, filteredCsv);
}
private static async Task<Boolean> DataLogging(StatusRecord status, DateTime timeStamp)
{
var csv = status.ToCsv();
// for debug, only to be deleted.
//foreach (var item in csv.SplitLines())
//{
// Console.WriteLine(item + "");
//}
await SavingLocalCsvFile(timeStamp.ToUnixTime(), csv);
var jsonData = new Dictionary<String, Object>();
ConvertToJson(csv, jsonData).LogInfo();
var s3Config = status.Config.S3;
if (s3Config is null)
return false;
//Concatenating 15 files in one file
return await ConcatinatingAndCompressingFiles(timeStamp.ToUnixTime(), s3Config);
}
private static String ConvertToJson(String csv, Dictionary<String, Object> jsonData)
{
foreach (var line in csv.Split('\n'))
{
if (string.IsNullOrWhiteSpace(line)) continue;
var parts = line.Split(';');
var keyPath = parts[0];
var value = parts[1];
var unit = parts.Length > 2 ? parts[2].Trim() : "";
InsertIntoJson(jsonData, keyPath.Split('/'), value);
}
var jsonOutput = JsonConvert.SerializeObject(jsonData, Formatting.None);
return jsonOutput;
}
private static async Task<Boolean> ConcatinatingAndCompressingFiles(Int64 timeStamp, S3Config s3Config)
{
if (_fileCounter >= NbrOfFileToConcatenate)
{
_fileCounter = 0;
var logFileConcatenator = new LogFileConcatenator();
var jsontoSend = logFileConcatenator.ConcatenateFiles(NbrOfFileToConcatenate);
var fileNameWithoutExtension = timeStamp.ToString(); // used for both S3 and local
var s3Path = fileNameWithoutExtension + ".json";
var request = s3Config.CreatePutRequest(s3Path);
var compressedBytes = CompresseBytes(jsontoSend);
var base64String = Convert.ToBase64String(compressedBytes);
var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64");
var uploadSucceeded = false;
try
{
var response = await request.PutAsync(stringContent);
if (response.StatusCode != 200)
{
Console.WriteLine("ERROR: PUT");
var error = await response.GetStringAsync();
Console.WriteLine(error);
await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension);
Heartbit();
return false;
}
uploadSucceeded = true;
Console.WriteLine("✅ File uploaded to S3 successfully.");
Console.WriteLine(
"---------------------------------------- Resending FailedUploadedFiles----------------------------------------");
Heartbit();
await ResendLocalFailedFilesAsync(s3Config); // retry any pending failed files
}
catch (Exception ex)
{
Console.WriteLine("Upload exception: " + ex.Message);
if (!uploadSucceeded)
{
await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension);
}
Heartbit();
return false;
}
}
_fileCounter++;
return true;
}
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
{
InstallationId = installationId,
Product = 3,
Status = _sodiAlarmState,
Type = MessageType.Heartbit,
};
if (s3Bucket != null)
RabbitMqManager.InformMiddleware(returnedStatus);
}
}
private static async Task SaveToLocalCompressedFallback(Byte[] compressedData, String fileNameWithoutExtension)
{
try
{
var fallbackDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FailedUploads");
Directory.CreateDirectory(fallbackDir);
var fileName = fileNameWithoutExtension + ".json"; // Save as .json, but still compressed
var fullPath = Path.Combine(fallbackDir, fileName);
await File.WriteAllBytesAsync(fullPath, compressedData); // Compressed data
Console.WriteLine($"Saved compressed failed upload to: {fullPath}");
}
catch (Exception ex)
{
Console.WriteLine("Failed to save compressed file locally: " + ex.Message);
}
}
private static Byte[] CompresseBytes(String jsonToSend)
{
//Compress JSON data to a byte array
using var memoryStream = new MemoryStream();
//Create a zip directory and put the compressed file inside
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
var entry = archive.CreateEntry("data.json", CompressionLevel.SmallestSize); // Add JSON data to the ZIP archive
using (var entryStream = entry.Open())
using (var writer = new StreamWriter(entryStream))
{
writer.Write(jsonToSend);
}
}
var compressedBytes = memoryStream.ToArray();
return compressedBytes;
}
private static async Task ResendLocalFailedFilesAsync(S3Config s3Config)
{
var fallbackDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FailedUploads");
if (!Directory.Exists(fallbackDir))
return;
var files = Directory.GetFiles(fallbackDir, "*.json");
files.Length.WriteLine(" Number of failed files, to upload");
foreach (var filePath in files)
{
var fileName = Path.GetFileName(filePath); // e.g., "1720023600.json"
try
{
byte[] compressedBytes = await File.ReadAllBytesAsync(filePath);
var base64String = Convert.ToBase64String(compressedBytes);
var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64");
var request = s3Config.CreatePutRequest(fileName);
var response = await request.PutAsync(stringContent);
if (response.StatusCode == 200)
{
File.Delete(filePath);
Console.WriteLine($"✅ Successfully resent and deleted: {fileName}");
}
else
{
Console.WriteLine($"❌ Failed to resend {fileName}, status: {response.StatusCode}");
}
}
catch (Exception ex)
{
Console.WriteLine($"⚠️ Exception while resending {fileName}: {ex.Message}");
}
}
}
private static async Task<Boolean> SaveModbusTcpFile(StatusRecord status)
{
var modbusData = new Dictionary<String, UInt16>();
// SYSTEM DATA
var result1 = ConvertToModbusRegisters((status.Config.MinSoc * 10), "UInt16", 30001); // this to be updated to modbusTCP version
var result2 = ConvertToModbusRegisters(status.InverterRecord!.PcuError, "UInt32", 30002);
// Merge all results into one dictionary
var allResults = new[]
{
result1,result2
};
foreach (var result in allResults)
{
foreach (var entry in result)
{
modbusData[entry.Key] = entry.Value;
}
}
// Write to JSON
var json = JsonSerializer.Serialize(modbusData, new JsonSerializerOptions { WriteIndented = true });
await File.WriteAllTextAsync("/home/inesco/SodiStoreHome/ModbusTCP/modbus_tcp_data.json", json);
//Console.WriteLine("JSON file written successfully.");
//Console.WriteLine(json);
var stopTime = DateTime.Now;
Console.WriteLine(stopTime.ToString("HH:mm:ss.fff" )+ " Finish the loop");
return true;
}
private static Dictionary<String, UInt16> ConvertToModbusRegisters(Object value, String outputType,
Int32 startingAddress)
{
var registers = new Dictionary<String, UInt16>();
switch (outputType)
{
case "UInt16":
registers[startingAddress.ToString()] = Convert.ToUInt16(value);
break;
case "Int16":
var int16Val = Convert.ToInt16(value);
registers[startingAddress.ToString()] = (UInt16)int16Val; // reinterpret signed as ushort
break;
case "UInt32":
var uint32Val = Convert.ToUInt32(value);
registers[startingAddress.ToString()] = (UInt16)(uint32Val & 0xFFFF); // Low word
registers[(startingAddress + 1).ToString()] = (UInt16)(uint32Val >> 16); // High word
break;
case "Int32":
var int32Val = Convert.ToInt32(value);
var raw = unchecked((UInt32)int32Val); // reinterprets signed int as unsigned
registers[startingAddress.ToString()] = (UInt16)(raw & 0xFFFF);
registers[(startingAddress + 1).ToString()] = (UInt16)(raw >> 16);
break;
default:
throw new ArgumentException("Unsupported output type: " + outputType);
}
return registers;
}
private static void InitializeKacoStartup(StatusRecord? statusRecord)
{
//
// 1. Apply DC This part is physical and cannot be done in software.
// We assume DC power is already present.
//
//
// 2. Send valid battery limits (Model 64202)
// All values temporarily set to "1" as requested.
// You will replace them later with real values.
//
if (statusRecord?.InverterRecord != null)
{
statusRecord.InverterRecord.MinDischargeVoltage = 700f; // 64202.DisMinV
statusRecord.InverterRecord.MaxDischargeCurrent = 140f; // 64202.DisMaxA
statusRecord.InverterRecord.DischargeCutoffCurrent = 10f; // 64202.DisCutoffA
statusRecord.InverterRecord.MaxChargeVoltage = 800f; // 64202.ChaMaxV
statusRecord.InverterRecord.MaxChargeCurrent = 140f; // 64202.ChaMaxA
statusRecord.InverterRecord.ChargeCutoffCurrent = 10f; // 64202.ChaCutoffA
statusRecord.InverterRecord.WatchdogSeconds = 30; // this is additional from my seid
//
// 3. Enable limits (EnLimit)
//
statusRecord.InverterRecord.BatteryLimitsEnable = EnableDisableEnum.Enabled;
//
// After writing all values in software, send them to the inverter
//
//
// 4. Read model 64201 to observe CurrentState transition
//
// Expected sequence:
// - Before valid limits: CurrentState == 7 (ERROR)
// - After valid limits: CurrentState == 8 (STANDBY)
// - Then after grid/DC conditions: CurrentState == 1 (OFF) or 11 (GRID_CONNECTED)
//
var state = statusRecord.InverterRecord.CurrentState;
Console.WriteLine($"KACO 64201.CurrentState = {state}");
switch (state)
{
case CurrentState.Standby:
Console.WriteLine("Device is in STANDBY (8) — battery limits accepted.");
break;
case CurrentState.Off:
Console.WriteLine("Device is OFF (1) — OK for non-battery operation.");
break;
case CurrentState.GridConnected:
Console.WriteLine("Device is GRID CONNECTED (11).");
break;
default:
Console.WriteLine("Device in unexpected state: " + state);
break;
}
//Thread.Sleep(2000);
}
}
private static void InsertIntoJson(Dictionary<String, Object> jsonDict, String[] keys, String value)
{
var currentDict = jsonDict;
for (Int16 i = 1; i < keys.Length; i++) // Start at 1 to skip empty root
{
var key = keys[i];
if (!currentDict.ContainsKey(key))
{
currentDict[key] = new Dictionary<String, Object>();
}
if (i == keys.Length - 1) // Last key, store the value
{
if (!value.Contains(",") && double.TryParse(value, out Double doubleValue)) // Try to parse value as a number
{
currentDict[key] = Math.Round(doubleValue, 2); // Round to 2 decimal places
}
else
{
currentDict[key] = value; // Store as string if not a number
}
}
else
{
currentDict = (Dictionary<String, Object>)currentDict[key];
}
}
}
}

View File

@ -0,0 +1,108 @@
using System.Text.Json;
using InnovEnergy.App.KacoCommunication.Devices;
using InnovEnergy.App.KacoCommunication.ESS;
using InnovEnergy.Lib.Utils;
using static System.Text.Json.JsonSerializer;
namespace InnovEnergy.App.KacoCommunication.SystemConfig;
public class Config
{
private static String DefaultConfigFilePath => Path.Combine(Environment.CurrentDirectory, "config.json");
private static DateTime DefaultDatetime => new(2024, 03, 11, 09, 00, 00);
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
public required Double MinSoc { get; set; }
/* public required Double MaximumDischargingCurrent { get; set; }
public required Double MaximumChargingCurrent { get; set; }
public required Int16 BatteriesCount { get; set; }
public required Double ModbusProtcolNumber { get; set; }*/
public required DeviceConfig Devices { get; set; }
public required S3Config? S3 { get; set; }
private static String? LastSavedData { get; set; }
public static Config Default => new()
{
MinSoc = 20,
/* MaximumChargingCurrent = 180,
MaximumDischargingCurrent = 180,
BatteriesCount = 0,
ModbusProtcolNumber = 1.2,*/
Devices = new ()
{
KacoIp = new() { Host = "10.0.3.1", Port = 502, DeviceState = DeviceState.Measured},
DcDcIp = new() { Host = "10.0.2.1", Port = 502, DeviceState = DeviceState.Measured},
GridMeterIp = new() { Host = "192.168.1.5", Port = 502, DeviceState = DeviceState.Measured},
BatteryNodes = new []{0,1, 2, 3, 4, 5 }
},
S3 = new()
{
Bucket = "1-3e5b3069-214a-43ee-8d85-57d72000c19d",
Region = "sos-ch-dk-2",
Provider = "exo.io",
Key = "EXObb5a49acb1061781761895e7",
Secret = "sKhln0w8ii3ezZ1SJFF33yeDo8NWR1V4w2H0D4-350I",
ContentType = "text/plain; charset=utf-8"
}
};
public void Save(String? path = null)
{
var configFilePath = path ?? DefaultConfigFilePath;
try
{
var jsonString = Serialize(this, JsonOptions);
if (LastSavedData == jsonString)
return;
LastSavedData = jsonString;
File.WriteAllText(configFilePath, jsonString);
}
catch (Exception e)
{
$"Failed to write config file {configFilePath}\n{e}".WriteLine();
throw;
}
}
public static Config Load(String? path = null)
{
var configFilePath = path ?? DefaultConfigFilePath;
try
{
var jsonString = File.ReadAllText(configFilePath);
return Deserialize<Config>(jsonString)!;
}
catch (Exception e)
{
$"Failed to read config file {configFilePath}, using default config\n{e}".WriteLine();
return Default;
}
}
public static async Task<Config> LoadAsync(String? path = null)
{
var configFilePath = path ?? DefaultConfigFilePath;
try
{
var jsonString = await File.ReadAllTextAsync(configFilePath);
return Deserialize<Config>(jsonString)!;
}
catch (Exception e)
{
Console.WriteLine($"Couldn't read config file {configFilePath}, using default config");
e.Message.WriteLine();
return Default;
}
}
}

View File

@ -0,0 +1,13 @@
using InnovEnergy.App.KacoCommunication.Devices;
namespace InnovEnergy.App.KacoCommunication.SystemConfig;
public class DeviceConfig
{
public required SalimaxDevice KacoIp { get; init; }
public required SalimaxDevice DcDcIp { get; init; }
public required SalimaxDevice GridMeterIp { get; init; }
public required Int32[] BatteryNodes { get; init; }
}

View File

@ -0,0 +1,79 @@
using System.Security.Cryptography;
using Flurl;
using Flurl.Http;
using InnovEnergy.Lib.Utils;
using static System.Text.Encoding;
using Convert = System.Convert;
namespace InnovEnergy.App.KacoCommunication.SystemConfig;
public record S3Config
{
public required String Bucket { get; init; }
public required String Region { get; init; }
public required String Provider { get; init; }
public required String Key { get; init; }
public required String Secret { get; init; }
public required String ContentType { get; init; }
private String Host => $"{Bucket}.{Region}.{Provider}";
private String Url => $"https://{Host}";
public IFlurlRequest CreatePutRequest(String s3Path) => CreateRequest("PUT", s3Path);
public IFlurlRequest CreateGetRequest(String s3Path) => CreateRequest("GET", s3Path);
private IFlurlRequest CreateRequest(String method, String s3Path)
{
var date = DateTime.UtcNow.ToString("r");
var auth = CreateAuthorization(method, s3Path, date);
return Url
.AppendPathSegment(s3Path)
.WithHeader("Host", Host)
.WithHeader("Date", date)
.WithHeader("Authorization", auth)
.AllowAnyHttpStatus();
}
private String CreateAuthorization(String method,
String s3Path,
String date)
{
return CreateAuthorization
(
method : method,
bucket : Bucket,
s3Path : s3Path,
date : date,
s3Key : Key,
s3Secret : Secret,
contentType: ContentType
);
}
private static String CreateAuthorization(String method,
String bucket,
String s3Path,
String date,
String s3Key,
String s3Secret,
String contentType = "application/base64",
String md5Hash = "")
{
contentType = "application/base64; charset=utf-8";
//contentType = "text/plain; charset=utf-8"; //this to use when sending plain csv to S3
var payload = $"{method}\n{md5Hash}\n{contentType}\n{date}\n/{bucket.Trim('/')}/{s3Path.Trim('/')}";
using var hmacSha1 = new HMACSHA1(UTF8.GetBytes(s3Secret));
var signature = UTF8
.GetBytes(payload)
.Apply(hmacSha1.ComputeHash)
.Apply(Convert.ToBase64String);
return $"AWS {s3Key}:{signature}";
}
}

View File

@ -0,0 +1,265 @@
using System.Globalization;
using InnovEnergy.App.KacoCommunication.Devices;
using InnovEnergy.App.KacoCommunication.ESS;
using InnovEnergy.Lib.Devices.BatteryDeligreen;
using InnovEnergy.Lib.Units.Power;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.KacoCommunication;
//
// ┌────┐
// │ Pv │
// └────┘
// V
// V
// (i) 13.2 kW ┌────────────┐ ┌────────────┐ ┌────────────┐
// V │ Battery K1│ │ Battery K2│ │ Battery K3│
// ┌─────────┐ ┌─────────┐ V ├────────────┤ ├────────────┤ ├────────────┤
// │ Grid │ │ AC/DC │ ┌────────┐ ┌───────┐ │ 52.3 V │ │ 52.3 V │ │ 52.3 V │
// ├─────────┤ -10.3 kW├─────────┤ -11.7 kW │ Dc Bus │ 1008 W │ DC/DC │ 1008 W │ 99.1 % │ │ 99.1 % │ │ 99.1 % │
// │ -3205 W │<<<<<<<<<│ -6646 W │<<<<<<<<<<├────────┤>>>>>>>>>>├───────┤>>>>>>>>>>│ 490 mA │ │ 490 mA │ │ 490 mA │
// │ -3507 W │ (a) │ -5071 W │ (h) │ 776 V │ (k) │ 56 V │ (l) │ 250 °C │ │ 250 °C │ │ 250 °C │
// │ -3605 W │ └─────────┘ └────────┘ └───────┘ │ 445 A │ │ 445 A │ │ 445 A │
// └─────────┘ V │ │ │ │ │ │
// V │ │ │ │ │ │
// (j) 0 W └────────────┘ └────────────┘ └────────────┘
// V
// V
// ┌──────┐
// │ Load │
// └──────┘
//
// New (simplified) topology:
//
// ┌────┐
// │ PV │
// └────┘
// V
// V
// (i) 13.2 kW
// V
// V
// ┌─────────┐ ┌─────────┐ (h) ┌────────┐ (k) ┌───────┐ (l) ┌────────────┐ ┌────────────┐ ┌────────────┐
// │ Grid │<<<│ AC/DC │<<<<<<<<<<<<<│ Dc Bus │>>>>>>>>│ DC/DC │>>>>>>>>│ Battery K1 │ │ Battery K2 │ │ Battery K3 │
// ├─────────┤ ├─────────┤ ├────────┤ ├───────┤ ├────────────┤ ├────────────┤ ├────────────┤
// │ -3205 W │ │ -6646 W │ │ 776 V │ │ 56 V │ │ 52.3 V ... │ │ 52.3 V ... │ │ 52.3 V ... │
// │ -3507 W │ │ -5071 W │ └────────┘ └───────┘ └────────────┘ └────────────┘ └────────────┘
// │ -3605 W │
// └─────────┘
//
// V
// V
// (j) 0 W
// V
// V
// ┌──────┐
// │ Load │
// └──────┘
//
// Notes:
// - (a) is grid power (to/from grid)
// - (h) is AC/DC -> DC link power (or your chosen link variable)
// - (i) PV -> DC bus
// - (j) DC load
// - (k) DC bus -> DC/DC link
// - (l) DC/DC -> battery power (or total battery power)
public static class SimpleTopology
{
public static TextBlock CreateSimpleTopologyTextBlock(this StatusRecord status)
{
// Keep the same variable letters as your diagrams (where possible)
var a = status.GridMeterRecord?.ActivePowerTotal;
// In your existing code, "AC/DC column" shows per-device AC power;
// and "h" is a separate link (AcDcToDcLink?.Power.Value).
var h = 0;
var i = 0;
var j = 0;
var k = 0;
// You mentioned this changed: l is now equal total battery power
var l = status.BatteryKabinet1.Power;
var grid = status.CreateGridColumn(a);
var acdc = status.CreateAcDcColumn(h);
var dcBus = status.CreateDcBusColumn(i, j, k);
var dcdc = status.CreateDcDcColumn(l);
var batteries = status.CreateBatteriesRow();
return TextBlock.AlignCenterVertical(
grid,
acdc,
dcBus,
dcdc,
batteries
);
}
private static TextBlock CreateGridColumn(this StatusRecord status, ActivePower? a)
{
// ┌─────────┐
// │ Grid │
// ├─────────┤
// │ L1 P │
// │ L2 P │
// │ L3 P │
// └─────────┘ (a) flow to AC/DC
var gridMeterAc = status.GridMeterRecord;
var gridBox = TextBlock
.AlignLeft(
gridMeterAc?.ActivePowerL1.Value.ToString(CultureInfo.InvariantCulture) ?? "???",
gridMeterAc?.ActivePowerL2.Value.ToString(CultureInfo.InvariantCulture) ?? "???",
gridMeterAc?.ActivePowerL3.Value.ToString(CultureInfo.InvariantCulture) ?? "???"
)
.TitleBox("Grid");
// Flow from Grid to AC/DC in the picture is horizontal (left -> right), using <<<<<< for export/import.
// Your Flow.Horizontal(power) already handles arrow direction by sign (based on your existing outputs).
var flow = Flow.Horizontal(a);
return TextBlock.AlignCenterVertical(gridBox, flow);
}
private static TextBlock CreateAcDcColumn(this StatusRecord status, ActivePower? h)
{
// ┌─────────┐
// │ AC/DC │
// ├─────────┤
// │ dev1 P │
// │ dev2 P │
// └─────────┘ (h) flow to DC Bus
var acdcBox = TextBlock
.AlignLeft(status.InverterRecord?.ActivePowerSetPercent.ToString() ?? "???")
.TitleBox("AC/DC");
var flowToDcBus = Flow.Horizontal(h);
return TextBlock.AlignCenterVertical(acdcBox, flowToDcBus);
}
private static TextBlock CreateDcBusColumn(
this StatusRecord status,
ActivePower? i,
ActivePower? j,
ActivePower? k)
{
// ┌────┐
// │ PV │
// └────┘
// V
// (i) 13.2 kW
// V
// ┌────────┐ (k) >>>>>>>>> to DC/DC
// │ Dc Bus │>>>>>>>>>>>>>>>>>>>
// ├────────┤
// │ 776 V │
// └────────┘
// V
// (j) 0 W
// V
// ┌──────┐
// │ Load │
// └──────┘
// PV box + vertical flow
var pvBox = TextBlock.FromString("PV").Box();
var pvToBus = Flow.Vertical(i);
// DC bus box (voltage from your DcDc record matches your existing code)
var dcBusVoltage = 0.0;
var dcBusBox = dcBusVoltage
.ToString(CultureInfo.InvariantCulture)
.Apply(TextBlock.FromString)
.TitleBox("Dc Bus");
// Horizontal flow from DC Bus to DC/DC
var busToDcDc = Flow.Horizontal(k);
// Load box + vertical flow
var busToLoad = Flow.Vertical(j);
var loadBox = TextBlock.FromString("Load").Box();
// Assemble: put PV above DC Bus, Load below DC Bus, and the (k) flow beside the bus.
return TextBlock.AlignCenterVertical(
TextBlock.AlignCenterHorizontal(pvBox, pvToBus, dcBusBox, busToLoad, loadBox),
busToDcDc
);
}
private static TextBlock CreateDcDcColumn(this StatusRecord status, ActivePower? l)
{
// ┌───────┐
// │ DC/DC │
// ├───────┤
// │ 56 V │
// └───────┘ (l) flow to batteries
var dc48Voltage =0.0;
var dcdcBox = TextBlock
.AlignLeft(dc48Voltage)
.TitleBox("DC/DC");
var flowToBattery = Flow.Horizontal(l);
return TextBlock.AlignCenterVertical(dcdcBox, flowToBattery);
}
private static TextBlock CreateBatteriesRow(this StatusRecord status)
{
// Battery K1 | Battery K2 | Battery K3 (side-by-side)
// Each box: voltage, soc, current, temp, etc. (you can tailor)
var bat = status.BatteryKabinet1;
if (bat is null)
return TextBlock.AlignLeft("no battery").Box();
// If you actually have relay names K1/K2/K3 per battery, wire them here.
// For now we label by index as "Battery K{n}" to match your picture.
var boxes = bat.Devices
.Select((b, idx) => CreateBatteryKBox(b, idx))
.ToReadOnlyList();
// Align horizontally to match the diagram
return boxes.Any()
? TextBlock.AlignTop(boxes)
: TextBlock.AlignLeft("no battery devices").Box();
}
private static TextBlock CreateBatteryKBox(BatteryDeligreenRecord battery, int idx)
{
// Minimal “K-style” battery box matching your diagram fields
var data = battery.BatteryDeligreenDataRecord;
// Some of your sample screen values look like:
// 52.3 V, 99.1 %, 490 mA, 250 °C, 445 A
// Map these to whatever fields you trust in your record.
var voltage = data.BusVoltage.ToDisplayString();
var soc = data.Soc.ToDisplayString();
var current = data.BusCurrent.ToDisplayString();
var temp = data.TemperaturesList.PowerTemperature.ToDisplayString();
// If you have a better “pack current” field, replace this line.
// Keeping it as a separate line to mimic the pictures extra current-like line.
var extraCurrent = data.BusCurrent.ToDisplayString();
return TextBlock
.AlignLeft(
voltage,
soc,
current,
temp,
extraCurrent
)
.TitleBox($"Battery K{idx + 1}");
}
}

View File

@ -0,0 +1,21 @@
#!/bin/bash
dotnet_version='net6.0'
salimax_ip="$1"
username='ie-entwicklung'
root_password='Salimax4x25'
set -e
echo -e "\n============================ Build ============================\n"
dotnet publish \
./KacoCommunication.csproj \
-p:PublishTrimmed=false \
-c Release \
-r linux-x64
echo -e "\n============================ Deploy ============================\n"
rsync -v \
--exclude '*.pdb' \
./bin/Release/$dotnet_version/linux-x64/publish/* \
$username@"$salimax_ip":~/salimax

View File

@ -54,6 +54,7 @@ internal static class Program
private static readonly Channel RelaysTsChannel;
private static readonly Channel BatteriesChannel;
private static readonly String SwVersionNumber = " V1.00." + DateTime.Today;
private static Boolean _curtailFlag = false;
private const String VpnServerIp = "10.2.0.11";
private static Boolean _subscribedToQueue = false;
@ -247,34 +248,6 @@ internal static class Program
Watchdog.NotifyAlive();
var record = ReadStatus();
/*
if (record.Relays != null)
{
record.Relays.Do0StartPulse = true;
record.Relays.PulseOut0HighTime = 20000;
record.Relays.PulseOut0LowTime = 20000;
record.Relays.DigitalOutput0Mode = 2;
record.Relays.LedGreen = false;
record.Relays.Do0StartPulse.WriteLine(" = start pulse 0");
record.Relays.PulseOut0HighTime.WriteLine(" = PulseOut0HighTime");
record.Relays.PulseOut0LowTime.WriteLine(" = PulseOut0LowTime");
record.Relays.DigitalOutput0Mode.WriteLine(" = DigitalOutput0Mode");
record.Relays.LedGreen.WriteLine(" = LedGreen");
record.Relays.LedRed.WriteLine(" = LedRed");
}
else
{
" Relays are null".WriteLine();
}*/
SendSalimaxStateAlarm(GetSalimaxStateAlarm(record), record); // to improve
@ -518,12 +491,15 @@ internal static class Program
private static void ControlConstants(this StatusRecord r)
{
r.DcDc.ResetAlarms();// move this first to be sure it's executed
r.AcDc.ResetAlarms();// move this first to be sure it's executed
var inverters = r.AcDc.Devices;
var dcDevices = r.DcDc.Devices;
var configFile = r.Config;
var maxBatteryDischargingCurrentLive = 0.0;
var devicesConfig = r.AcDc.Devices.All(d => d.Control.Ac.GridType == GridType.GridTied400V50Hz) ? configFile.GridTie : configFile.IslandMode; // TODO if any of the grid tie mode
// This adapting the max discharging current to the current Active Strings
if (r.Battery != null)
{
@ -572,8 +548,7 @@ internal static class Program
dcDevices.ForEach(d => d.Control.VoltageLimits.MinBatteryVoltage = devicesConfig.DcDc.MinDischargeBatteryVoltage);
dcDevices.ForEach(d => d.Control.ControlMode = DcControlMode.VoltageDroop);
r.DcDc.ResetAlarms();
r.AcDc.ResetAlarms();
}
// This will be used for provider throttling, this example is only for either 100% or 0 %

View File

@ -31,6 +31,8 @@ public class CombinedAdamRelaysRecord : IRelaysRecord
set => _recordAdam6360D.K2ConnectIslandBusToGridBus = value;
}
public Boolean OldK2ConnectIslandBusToGridBus { get; set; }
public Boolean Inverter1WagoStatus => _recordAdam6360D.Inverter1WagoStatus;
public Boolean Inverter2WagoStatus => _recordAdam6360D.Inverter2WagoStatus;
public Boolean Inverter3WagoStatus => _recordAdam6360D.Inverter3WagoStatus;

View File

@ -9,6 +9,7 @@ public interface IRelaysRecord
Boolean FiWarning { get; }
Boolean FiError { get; }
Boolean K2ConnectIslandBusToGridBus { get; set; }
Boolean OldK2ConnectIslandBusToGridBus { get; set; }
// Boolean Inverter1WagoRelay { get; set; } // to add in the future
// Boolean Inverter2WagoRelay { get; set; } // to add in the future

View File

@ -122,12 +122,17 @@ public class RelaysRecordAmax : IRelaysRecord
public Boolean FiWarning => !_Regs.DigitalInput21;
public Boolean FiError => !_Regs.DigitalInput23;
public Boolean K2ConnectIslandBusToGridBus
public Boolean K2ConnectIslandBusToGridBus // IMPORTANT: this should be changed to Relay22 for Tschirren installation
{
get => _Regs.Relay23;
set => _Regs.Relay23 = value;
}
public Boolean OldK2ConnectIslandBusToGridBus
{
get => _Regs.Relay23;
set => _Regs.Relay23 = value;
}
public static implicit operator Amax5070Registers(RelaysRecordAmax d) => d._Regs;
public static implicit operator RelaysRecordAmax(Amax5070Registers d) => new RelaysRecordAmax(d);

View File

@ -4,10 +4,17 @@ namespace InnovEnergy.App.SinexcelCommunication.DataTypes;
public class Configuration
{
public Double MinimumSoC { get; set; }
public Double MaximumDischargingCurrent { get; set; }
public Double MaximumChargingCurrent { get; set; }
public WorkingMode OperatingPriority { get; set; }
public Int16 BatteriesCount { get; set; }
public Double MinimumSoC { get; set; }
public Double MaximumDischargingCurrent { get; set; }
public Double MaximumChargingCurrent { get; set; }
public WorkingMode OperatingPriority { get; set; }
public Int16 BatteriesCount { get; set; }
public Int16 ClusterNumber { get; set; }
public Int16 PvNumber { get; set; }
public DateTime StartTimeChargeandDischargeDayandTime { get; set; }
public DateTime StopTimeChargeandDischargeDayandTime { get; set; }
public Single TimeChargeandDischargePower { get; set; }
public Boolean ControlPermission { get; set; }
}

View File

@ -68,7 +68,10 @@ public static class MiddlewareAgent
if (config != null)
{
Console.WriteLine($"Received a configuration message: " +
"MinimumSoC is " + config.MinimumSoC + " and operating priorty is " +config.OperatingPriority + "Number of batteries is " + config.BatteriesCount + config.MaximumChargingCurrent + config.MaximumDischargingCurrent);
"MinimumSoC is " + config.MinimumSoC + " and operating priorty is " +config.OperatingPriority + "Number of batteries is " + config.BatteriesCount
+ "Maximum Charging current is "+ config.MaximumChargingCurrent + "/n" + "Maximum Discharging current is " + config.MaximumDischargingCurrent
+ "StartTimeChargeandDischargeDayandTime is" + config.StartTimeChargeandDischargeDayandTime + "StopTimeChargeandDischargeDayandTime is" + config.StopTimeChargeandDischargeDayandTime
+ "TimeChargeandDischargePowert is " + config.TimeChargeandDischargePower + " Control permission is" + config.ControlPermission);
// Send the reply to the sender's endpoint
_udpListener.Send(replyData, replyData.Length, serverEndpoint);

View File

@ -7,12 +7,11 @@ using RabbitMQ.Client;
namespace InnovEnergy.App.SinexcelCommunication.MiddlewareClasses;
[SuppressMessage("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic access otherwise can break functionality when trimming application code")]
public static class RabbitMqManager
{
public static ConnectionFactory? Factory ;
public static IConnection ? Connection;
public static IModel? Channel;
private static ConnectionFactory? _factory ;
private static IConnection ? _connection;
private static IModel? _channel;
public static Boolean SubscribeToQueue(StatusMessage currentSalimaxState, String? s3Bucket,String VpnServerIp)
{
@ -20,19 +19,22 @@ public static class RabbitMqManager
{
//_factory = new ConnectionFactory { HostName = VpnServerIp };
Factory = new ConnectionFactory
_factory = new ConnectionFactory
{
HostName = VpnServerIp,
Port = 5672,
VirtualHost = "/",
UserName = "producer",
Password = "b187ceaddb54d5485063ddc1d41af66f",
AutomaticRecoveryEnabled = true,
NetworkRecoveryInterval = TimeSpan.FromSeconds(10),
RequestedHeartbeat = TimeSpan.FromSeconds(30)
};
Connection = Factory.CreateConnection();
Channel = Connection.CreateModel();
Channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
_connection = _factory.CreateConnection();
_channel = _connection.CreateModel();
_channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
Console.WriteLine("The controller sends its status to the middleware for the first time");
if (s3Bucket != null) InformMiddleware(currentSalimaxState);
@ -52,13 +54,36 @@ public static class RabbitMqManager
var message = JsonSerializer.Serialize(status);
var body = Encoding.UTF8.GetBytes(message);
Channel.BasicPublish(exchange: string.Empty,
_channel.BasicPublish(exchange: string.Empty,
routingKey: "statusQueue",
basicProperties: null,
body: body);
Console.WriteLine($"Producer sent message: {message}");
}
public static Boolean EnsureConnected(StatusMessage currentSalimaxState, string? s3Bucket, string vpnServerIp)
{
try
{
if (_connection == null || !_connection.IsOpen ||
_channel == null || _channel.IsClosed)
{
Console.WriteLine("⚠ RabbitMQ connection lost. Reconnecting...");
_connection?.Dispose();
_channel?.Dispose();
return SubscribeToQueue(currentSalimaxState, s3Bucket, vpnServerIp);
}
return true;
}
catch (Exception ex)
{
Console.WriteLine("❌ Error while ensuring RabbitMQ connection: " + ex);
return false;
}
}
}

View File

@ -16,15 +16,23 @@ public class Config
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
public required Double MinSoc { get; set; }
public required Double GridSetPoint { get; set; }
public required Double MaximumDischargingCurrent { get; set; }
public required Double MaximumChargingCurrent { get; set; }
public required WorkingMode OperatingPriority { get; set; }
public required Int16 BatteriesCount { get; set; }
public required Double ModbusProtcolNumber { get; set; }
public required Double MinSoc { get; set; }
public required Double GridSetPoint { get; set; }
public required Double MaximumDischargingCurrent { get; set; }
public required Double MaximumChargingCurrent { get; set; }
public required WorkingMode OperatingPriority { get; set; }
public required Int16 BatteriesCount { get; set; }
public required Int16 ClusterNumber { get; set; }
public required Int16 PvNumber { get; set; }
public required Double ModbusProtcolNumber { get; set; }
public required DateTime StartTimeChargeandDischargeDayandTime { get; set; }
public required DateTime StopTimeChargeandDischargeDayandTime { get; set; }
public required S3Config? S3 { get; set; }
public required Single TimeChargeandDischargePower { get; set; }
public required Boolean ControlPermission { get; set; }
public required S3Config? S3 { get; set; }
private static String? LastSavedData { get; set; }
@ -36,7 +44,14 @@ public class Config
MaximumDischargingCurrent = 180,
OperatingPriority = WorkingMode.TimeChargeDischarge,
BatteriesCount = 0,
ClusterNumber = 0,
PvNumber = 0,
ModbusProtcolNumber = 1.2,
StartTimeChargeandDischargeDayandTime = DefaultDatetime,
StopTimeChargeandDischargeDayandTime = DefaultDatetime,
TimeChargeandDischargePower = 3,
ControlPermission = false,
S3 = new()
{
Bucket = "1-3e5b3069-214a-43ee-8d85-57d72000c19d",

View File

@ -5,7 +5,7 @@ is_release="$2" # Pass --release if this is a real release
username='inesco'
root_password='Sodistore0918425'
release_flag_file="./bin/Release/$dotnet_version/linux-arm64/publish/.release.flag"
release_flag_file="./bin/Release/$dotnet_version/linux-arm64/publish/.release.flag"
set -e
@ -28,5 +28,5 @@ rsync -v \
echo -e "\n✅ Real release. Triggering sync to server..."
touch "$release_flag_file"
else
echo -e "\n Test build. Not syncing to main release server."
echo -e "\n🚫 Test build. Not syncing to main release server."
fi

View File

@ -0,0 +1,21 @@
#!/bin/bash
WATCHDIR="$HOME/sync/work/Code/CSharp/git_trunk/csharp/App/SinexcelCommunication/bin/Release/net6.0/linux-arm64/publish"
DEST="ubuntu@91.92.155.224:/home/ubuntu/SinexcelReleases"
echo "👀 Watching for real releases in $WATCHDIR..."
inotifywait -m -e close_write --format '%w%f' "$WATCHDIR" | while read file; do
filename="$(basename "$file")"
if [[ "$filename" == ".release.flag" ]]; then
echo "🚀 Release flag detected. Syncing full release to $DEST..."
rm "$file"
rsync -avz \
--exclude '*.pdb' \
"$WATCHDIR/" "$DEST/"
echo "✅ Sync completed and flag cleared."
fi
done

View File

@ -58,7 +58,7 @@ internal static class Program
//
private const String SwVersionNumber =" V1.35.220925 beta";
private static readonly String SwVersionNumber = " V1.00." + DateTime.Today;
private static Boolean _curtailFlag = false;
private const String VpnServerIp = "10.2.0.11";
private static Boolean _subscribedToQueue = false;
@ -281,10 +281,10 @@ internal static class Program
//record.PerformLed();
WriteControl(record);
record.Battery.Eoc.WriteLine(" Total EOC");
record.Battery?.Eoc.WriteLine(" Total EOC");
$"{record.StateMachine.State}: {record.StateMachine.Message}".WriteLine();
$"{DateTime.Now.Round(UpdateInterval).ToUnixTime()} : {DateTime.Now.Round(UpdateInterval):dd/MM/yyyy HH:mm:ss} : {SwVersionNumber}".WriteLine();
$"{DateTime.Now.Round(UpdateInterval).ToUnixTime()} : {DateTime.Now.Round(UpdateInterval):dd/MM/yyyy HH:mm:ss} : { SwVersionNumber}".WriteLine();
record.CreateTopologyTextBlock().WriteLine();

View File

@ -32,8 +32,8 @@ namespace InnovEnergy.Lib.Devices.Amax5070
[Coil(25)] public Boolean Relay20 { get; set; } // Relay5060 2
[Coil(26)] public Boolean Relay21 { get; set; } // Relay5060 2
[Coil(27)] public Boolean Relay22 { get; set; } // Relay5060 2
[Coil(28)] public Boolean Relay23 { get; set; } // Relay5060 2
[Coil(27, writable: true)] public Boolean Relay22 { get; set; }
[Coil(28)] public Boolean Relay23 { get; set; }
[Coil(33)] public Boolean Relay30 { get; set; } // Relay5060 3
[Coil(34)] public Boolean Relay31 { get; set; } // Relay5060 3

View File

@ -0,0 +1,14 @@
namespace InnovEnergy.Lib.Devices.Kaco92L3.DataType;
public enum ControlMode
{
RpcLocal = 0,
RpcRemote = 1
}
public enum EnableDisableEnum : ushort
{
Disabled = 0,
Enabled = 1
}

View File

@ -0,0 +1,17 @@
namespace InnovEnergy.Lib.Devices.Kaco92L3.DataType;
public enum CurrentState
{
Off = 1,
Sleeping = 2,
Starting = 3,
Mppt = 4,
Throttled = 5,
ShuttingDown = 6,
Fault = 7,
Standby = 8,
Precharge = 9,
GridPreConnected = 10,
GridConnected = 11,
NoErrorPending = 12
}

View File

@ -0,0 +1,16 @@
namespace InnovEnergy.Lib.Devices.Kaco92L3.DataType;
public enum ErrorPcu
{
NoEvent = 0,
OverTemp = 1,
OverVolt = 2,
UnderVolt = 3,
BattPolIncorrect = 4,
CounterTooHigh = 5,
DuringPrecharge = 6,
BattVoltOutOfRange = 7,
I2CComm = 8,
CanComm = 9,
SwitchOffAcDsp = 10,
}

View File

@ -0,0 +1,9 @@
namespace InnovEnergy.Lib.Devices.Kaco92L3.DataType;
public enum ReuqestedState
{
Off = 1,
Standby = 8,
GridPreConnected = 10,
GridConnected = 11,
}

View File

@ -0,0 +1,14 @@
namespace InnovEnergy.Lib.Devices.Kaco92L3.DataType;
public enum StatePcu
{
WaitForStartup = 1,
Standby = 2,
SwitchRelMinus = 3,
SwitchRelPrecharge = 4,
SwitchRelPlus = 5,
Running = 6,
Cooldown = 7,
Error = 8,
ClearError = 9,
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>InnovEnergy.Lib.Devices.Kaco92L3;</RootNamespace>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Protocols\Modbus\Modbus.csproj" />
<ProjectReference Include="..\..\Units\Units.csproj" />
<ProjectReference Include="..\..\Utils\Utils.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,49 @@
using InnovEnergy.Lib.Protocols.Modbus.Channels;
using InnovEnergy.Lib.Protocols.Modbus.Clients;
using InnovEnergy.Lib.Protocols.Modbus.Slaves;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Devices.Kaco92L3;
public class KacoDevice: ModbusDevice<KacoRecord>
{
public KacoDevice(String hostname, UInt16 port = 502, Byte slaveId = 1) : this(new TcpChannel(hostname, port), slaveId)
{
}
public KacoDevice(Channel channel, Byte slaveId = 1) : base(new ModbusTcpClient(channel, slaveId))
{
}
public KacoDevice(ModbusClient client) : base(client)
{
}
public new KacoRecord? Read()
{
try
{
return base.Read();
}
catch
{
"Failed to read data from Kaco".WriteLine();
return null;
}
}
public new void Write(KacoRecord registers)
{
try
{
base.Write(registers);
}
catch (Exception e)
{
// TODO: Log
Console.WriteLine(e);
}
}
}

View File

@ -0,0 +1,255 @@
using InnovEnergy.Lib.Devices.Kaco92L3.DataType;
namespace InnovEnergy.Lib.Devices.Kaco92L3;
public partial class KacoRecord
{
private static float ScaleSunspec(Int16 value, Int16 sf)
{
// -32768 is SunSpec "not implemented"
if (value == -32768)
return float.NaN;
if (sf == 0)
return value;
return (float)(value * Math.Pow(10, sf));
}
private static float ScaleSunspec(UInt16 value, Int16 sf)
{
// interpret unsigned as signed when applying SF; range is the same
return ScaleSunspec(unchecked((Int16)value), sf);
}
private static Int16 UnscaleSunspecToInt16(float value, Int16 sf)
{
if (float.IsNaN(value) || float.IsInfinity(value))
return -32768; // "not implemented" / invalid
if (sf == 0)
return (Int16)Math.Round(value);
var raw = value / (float)Math.Pow(10, sf);
return (Int16)Math.Round(raw);
}
private static UInt16 UnscaleSunspecToUInt16(float value, Int16 sf)
{
var raw = UnscaleSunspecToInt16(value, sf);
return unchecked((UInt16)raw);
}
/****************************** High-level API for Model 64201 ****************************/
// ───────────────────────────────────────────────
// States & control
// ───────────────────────────────────────────────
// Header
public UInt16 BattCharId => _battCharId; // ID = 64202
public UInt16 BattCharLength => _battCharLength; // L = 6 + (RBCount * 8)
public ReuqestedState RequestedState
{
get => (ReuqestedState)_requestedState;
set => _requestedState = (UInt16)value;
}
public CurrentState CurrentState => (CurrentState)_currentState;
public ControlMode ControlMode
{
get => (ControlMode)_controlMode;
set => _controlMode = (UInt16)value;
}
public bool IsGridConnected => CurrentState == CurrentState.GridConnected;
public bool IsStandby => CurrentState == CurrentState.Standby;
public bool IsOff => CurrentState == CurrentState.Off;
// Watchdog seconds (no scale factor)
public UInt16 WatchdogSeconds
{
get => _watchdog;
set => _watchdog = value;
}
// ───────────────────────────────────────────────
// Setpoints (scaled)
// ───────────────────────────────────────────────
/// <summary>Active power setpoint in percent of WMax [%].</summary>
public float ActivePowerSetPercent
{
get => ScaleSunspec(_wSetPct, _wSetPctSf);
set => _wSetPct = UnscaleSunspecToInt16(value, _wSetPctSf);
}
/// <summary>Reactive power setpoint in percent of SMax [%].</summary>
public float ReactivePowerSetPercent
{
get => ScaleSunspec(_varWMaxSetPct, _varSetPctSf);
set => _varWMaxSetPct = UnscaleSunspecToInt16(value, _varSetPctSf);
}
// ───────────────────────────────────────────────
// Ramp parameters (scaled)
// ───────────────────────────────────────────────
/// <summary>Active power PT1 ramp time [s].</summary>
public float ActivePowerRampTimeSeconds
{
get => ScaleSunspec(_wParamRmpTms, _rmpTmsSf);
set => _wParamRmpTms = UnscaleSunspecToUInt16(value, _rmpTmsSf);
}
/// <summary>Active power ramp-down rate [% ref / min].</summary>
public float ActivePowerRampDownPercentPerMin
{
get => ScaleSunspec(_wParamRmpDecTmn, _rmpIncDecSf);
set => _wParamRmpDecTmn = UnscaleSunspecToUInt16(value, _rmpIncDecSf);
}
/// <summary>Active power ramp-up rate [% ref / min].</summary>
public float ActivePowerRampUpPercentPerMin
{
get => ScaleSunspec(_wParamRmpIncTmn, _rmpIncDecSf);
set => _wParamRmpIncTmn = UnscaleSunspecToUInt16(value, _rmpIncDecSf);
}
/// <summary>Reactive power PT1 ramp time [s].</summary>
public float ReactivePowerRampTimeSeconds
{
get => ScaleSunspec(_varParamRmpTms, _rmpTmsSf);
set => _varParamRmpTms = UnscaleSunspecToUInt16(value, _rmpTmsSf);
}
/// <summary>Reactive power ramp-down rate [% ref / min].</summary>
public float ReactivePowerRampDownPercentPerMin
{
get => ScaleSunspec(_varParamRmpDecTmn, _rmpIncDecSf);
set => _varParamRmpDecTmn = UnscaleSunspecToUInt16(value, _rmpIncDecSf);
}
/// <summary>Reactive power ramp-up rate [% ref / min].</summary>
public float ReactivePowerRampUpPercentPerMin
{
get => ScaleSunspec(_varParamRmpIncTmn, _rmpIncDecSf);
set => _varParamRmpIncTmn = UnscaleSunspecToUInt16(value, _rmpIncDecSf);
}
// Ramp enable flags
public EnableDisableEnum ActivePowerRampEnable
{
get => (EnableDisableEnum)_wParamEna;
set => _wParamEna = (UInt16)value;
}
public EnableDisableEnum ReactivePowerRampEnable
{
get => (EnableDisableEnum)_varParamEna;
set => _varParamEna = (UInt16)value;
}
// ───────────────────────────────────────────────
// Status / error (read-only enum views)
// ───────────────────────────────────────────────
//public VendorStateEnum VendorState => (VendorStateEnum)_stVnd;
//public PuStateEnum PuState => (PuStateEnum)_stPu;
public StatePcu PcuState => (StatePcu)_stPcu;
public ErrorPcu PcuError => (ErrorPcu)_errPcu;
public UInt16 BatteryCharVersion => _battCharVersion;
public UInt16 BatteryCharMinorVersion => _battCharVerMinor;
/// <summary>
/// Scale factor for battery voltages (V_SF).
/// </summary>
public Int16 BatteryVoltageScaleFactor => _battCharVSf;
/// <summary>
/// Scale factor for battery currents (A_SF).
/// </summary>
public Int16 BatteryCurrentScaleFactor => _battCharASf;
// Helper wrappers for scaled values
private float ScaleBattVoltage(UInt16 raw) => ScaleSunspec(raw, _battCharVSf);
private float ScaleBattCurrent(UInt16 raw) => ScaleSunspec(raw, _battCharASf);
private UInt16 UnscaleBattVoltage(float value) => UnscaleSunspecToUInt16(value, _battCharVSf);
private UInt16 UnscaleBattCurrent(float value) => UnscaleSunspecToUInt16(value, _battCharASf);
// ───────────────────────────────────────────────
// Battery discharge limits (scaled, RW)
// ───────────────────────────────────────────────
/// <summary>Minimum discharge voltage [V].</summary>
public float MinDischargeVoltage
{
get => ScaleBattVoltage(_disMinVRaw);
set => _disMinVRaw = UnscaleBattVoltage(value);
}
/// <summary>Maximum discharge current [A].</summary>
public float MaxDischargeCurrent
{
get => ScaleBattCurrent(_disMaxARaw);
set => _disMaxARaw = UnscaleBattCurrent(value);
}
/// <summary>Discharge cutoff current [A]. If discharge current falls below this, it disconnects (optional according to sheet).</summary>
public float DischargeCutoffCurrent
{
get => ScaleBattCurrent(_disCutoffARaw);
set => _disCutoffARaw = UnscaleBattCurrent(value);
}
// ───────────────────────────────────────────────
// Battery charge limits (scaled, RW)
// ───────────────────────────────────────────────
/// <summary>Maximum charge voltage [V].</summary>
public float MaxChargeVoltage
{
get => ScaleBattVoltage(_chaMaxVRaw);
set => _chaMaxVRaw = UnscaleBattVoltage(value);
}
/// <summary>Maximum charge current [A].</summary>
public float MaxChargeCurrent
{
get => ScaleBattCurrent(_chaMaxARaw);
set => _chaMaxARaw = UnscaleBattCurrent(value);
}
/// <summary>Charge cutoff current [A]. If charge current falls below this, it disconnects.</summary>
public float ChargeCutoffCurrent
{
get => ScaleBattCurrent(_chaCutoffARaw);
set => _chaCutoffARaw = UnscaleBattCurrent(value);
}
// ───────────────────────────────────────────────
// Limit enable flag
// ───────────────────────────────────────────────
/// <summary>
/// When EnLimit = 1, new battery limits are activated.
/// </summary>
public EnableDisableEnum BatteryLimitsEnable
{
get => (EnableDisableEnum)_enLimitRaw;
set => _enLimitRaw = (UInt16)value;
}
/// <summary>Convenience bool wrapper for EnLimit.</summary>
public bool BatteryLimitsEnabled
{
get => BatteryLimitsEnable == EnableDisableEnum.Enabled;
set => BatteryLimitsEnable = value ? EnableDisableEnum.Enabled : EnableDisableEnum.Disabled;
}
}

View File

@ -0,0 +1,110 @@
using System.Diagnostics.CodeAnalysis;
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Units.Power;
namespace InnovEnergy.Lib.Devices.Kaco92L3;
[AddressOffset(-1)]
public partial class KacoRecord
{
/****************************** Holding registers SunSpec Model 64201 ****************************/
// Model 64201 header
[HoldingRegister<UInt16>(41061)] private UInt16 _model64201Id; // 0xA065 (DID = 64201)
[HoldingRegister<UInt16>(41062)] private UInt16 _model64201Length; // 0xA066 (L = 52)
// Version info
[HoldingRegister<UInt16>(41063)] private UInt16 _version; // 0xA067
[HoldingRegister<UInt16>(41064)] private UInt16 _versionMinor; // 0xA068
// State control
[HoldingRegister<UInt16>(41065, writable: true)] private UInt16 _requestedState; // 0xA069 RequestedState (enum16, RW)
[HoldingRegister<UInt16>(41066)] private UInt16 _currentState; // 0xA06A CurrentState (enum16, R)
[HoldingRegister<UInt16>(41067, writable: true)] private UInt16 _controlMode; // 0xA06B ControlMode (enum16, RW)
[HoldingRegister<UInt16>(41068)] private UInt16 _reserved7; // 0xA06C Reserved
// Watchdog / setpoints
//Enable Watchdog countdown. Register must be filled with the desired watchdog timeout in seconds. 0 means watchdog is disabled. It is recommended to re-write the register at least 10 seconds before the timeout is activated.
[HoldingRegister<UInt16>(41069, writable: true)] private UInt16 _watchdog; // 0xA06D Watchdog (uint16, RW, seconds) 0 to 600
[HoldingRegister<Int16> (41070, writable: true)] private Int16 _wSetPct; // 0xA06E WSetPct (int16, RW, %WMax) unscaled: -100 to 100. Set power output to specified level.
[HoldingRegister<Int16> (41071, writable: true)] private Int16 _varWMaxSetPct; // 0xA06F VarWMaxSetPct (int16, RW, %SMax) unscaled: -100 to 100
// Reserved padding
[HoldingRegister<UInt16>(41072)] private UInt16 _reserved11; // 0xA070
[HoldingRegister<UInt16>(41073)] private UInt16 _reserved12; // 0xA071
[HoldingRegister<UInt16>(41074)] private UInt16 _reserved13; // 0xA072
[HoldingRegister<UInt16>(41075)] private UInt16 _reserved14; // 0xA073
[HoldingRegister<UInt16>(41076)] private UInt16 _reserved15; // 0xA074
[HoldingRegister<UInt16>(41077)] private UInt16 _reserved16; // 0xA075
// Status / error
[HoldingRegister<UInt16>(41078)] private UInt16 _stVnd; // 0xA076 StVnd (enum16, R) PrologState
[HoldingRegister<UInt16>(41079)] private UInt16 _stPu; // 0xA077 StPu (enum16, R) Power Unit State (DSP)
[HoldingRegister<UInt16>(41080)] private UInt16 _stPcu; // 0xA078 StPcu (enum16, R) pcu state
[HoldingRegister<UInt16>(41081)] private UInt16 _errPcu; // 0xA079 ErrPcu (enum16, R) pcu error
// Active power ramp parameters
[HoldingRegister<UInt16>(41082, writable: true)] private UInt16 _wParamRmpTms; // 0xA07A WParamRmpTms (uint16, RW, s)
[HoldingRegister<UInt16>(41083, writable: true)] private UInt16 _wParamRmpDecTmn; // 0xA07B WParamRmpDecTmn (uint16, RW, %ref/min)
[HoldingRegister<UInt16>(41084, writable: true)] private UInt16 _wParamRmpIncTmn; // 0xA07C WParamRmpIncTmn (uint16, RW, %ref/min)
[HoldingRegister<UInt16>(41085)] private UInt16 _reserved24; // 0xA07D Reserved
[HoldingRegister<UInt16>(41086)] private UInt16 _reserved25; // 0xA07E Reserved
[HoldingRegister<UInt16>(41087, writable: true)] private UInt16 _wParamEna; // 0xA07F WParam_Ena (enum16, RW) WSet_Ena control 0 or 1
// Reactive power ramp parameters
[HoldingRegister<UInt16>(41088, writable: true)] private UInt16 _varParamRmpTms; // 0xA080 VarParamRmpTms (uint16, RW, s)
[HoldingRegister<UInt16>(41089, writable: true)] private UInt16 _varParamRmpDecTmn; // 0xA081 VarParamRmpDecTmn (uint16, RW, %ref/min)
[HoldingRegister<UInt16>(41090, writable: true)] private UInt16 _varParamRmpIncTmn; // 0xA082 VarParamRmpIncTmn (uint16, RW, %ref/min)
[HoldingRegister<UInt16>(41091)] private UInt16 _reserved30; // 0xA083 Reserved
[HoldingRegister<UInt16>(41092)] private UInt16 _reserved31; // 0xA084 Reserved
[HoldingRegister<UInt16>(41093, writable: true)] private UInt16 _varParamEna; // 0xA085 VarParam_Ena (enum16, RW) Enumerated valued. Percent limit VAr enable/disable control.
// Measurements (read-only)
[HoldingRegister<UInt16>(41094)] private UInt16 _phaseVoltageAN; // 0xA086 PhVphA (uint16, R, V, V_SF)
[HoldingRegister<UInt16>(41095)] private UInt16 _phaseVoltageBN; // 0xA087 PhVphB (uint16, R, V, V_SF)
[HoldingRegister<UInt16>(41096)] private UInt16 _phaseVoltageCN; // 0xA088 PhVphC (uint16, R, V, V_SF)
[HoldingRegister<Int16> (41097)] private Int16 _activePowerW; // 0xA089 W (int16, R, W, W_SF)
[HoldingRegister<Int16> (41098)] private Int16 _reactivePowerVar; // 0xA08A VAR (int16, R, var, Var_SF)
[HoldingRegister<Int16> (41099)] private Int16 _lineFrequencyHz; // 0xA08B Hz (int16, R, Hz, Hz_SF)
// Scale factors (SunSpec sunsf)
// Scale factor for active power percent.
[HoldingRegister<Int16>(41107)] private Int16 _wSetPctSf; // 0xA0F3 WSetPct_SF
// Scale factor for reactive power percent.
[HoldingRegister<Int16>(41108)] private Int16 _varSetPctSf; // 0xA0F4 VarSetPct_SF
// Scale factor for PT1 (ramp time).
[HoldingRegister<Int16>(41109)] private Int16 _rmpTmsSf; // 0xA0F5 RmpTms_SF
// Scale factor for increment and decrement ramps.
[HoldingRegister<Int16>(41110)] private Int16 _rmpIncDecSf; // 0xA0F6 RmpIncDec_SF
// Header
[HoldingRegister<UInt16>(41115)] private UInt16 _battCharId; // ID = 64202
[HoldingRegister<UInt16>(41116)] private UInt16 _battCharLength; // L = 6 + (RBCount * 8)
// Fixed block
[HoldingRegister<UInt16>(41117)] private UInt16 _battCharVersion; // Version (uint16, R)
[HoldingRegister<UInt16>(41118)] private UInt16 _battCharVerMinor; // VerMinor (uint16, R)
[HoldingRegister<UInt16>(41119)] private UInt16 _battCharRsrvd1; // Rsrvd_1 (pad)
[HoldingRegister<UInt16>(41120)] private UInt16 _battCharRsrvd2; // Rsrvd_2 (pad)
[HoldingRegister<Int16> (41121)] private Int16 _battCharVSf; // V_SF (sunsf, R)
[HoldingRegister<Int16> (41122)] private Int16 _battCharASf; // A_SF (sunsf, R)
// Repeating block #0 (you said there is only one block)
[HoldingRegister<UInt16>(41123, writable: true)] private UInt16 _disMinVRaw; // DisMinV (uint16, V, V_SF, RW) min. discharge voltage
[HoldingRegister<UInt16>(41124, writable: true)] private UInt16 _disMaxARaw; // DisMaxA (uint16, A, A_SF, RW)max. discharge current
[HoldingRegister<UInt16>(41125, writable: true)] private UInt16 _disCutoffARaw; // DisCutoffA (uint16, A, A_SF, RW)Disconnect if discharge current lower than DisCutoffA
[HoldingRegister<UInt16>(41126, writable: true)] private UInt16 _chaMaxVRaw; // ChaMaxV (uint16, V, V_SF, RW)max. charge voltage
[HoldingRegister<UInt16>(41127, writable: true)] private UInt16 _chaMaxARaw; // ChaMaxA (uint16, A, A_SF, RW)max. charge current
[HoldingRegister<UInt16>(41128, writable: true)] private UInt16 _chaCutoffARaw; // ChaCutoffA (uint16, A, A_SF, RW)Disconnect if charge current lower than ChaCuttoffA
[HoldingRegister<UInt16>(41129)] private UInt16 _battCharPad; // Pad (pad, R)
[HoldingRegister<UInt16>(41130, writable: true)] private UInt16 _enLimitRaw; // EnLimit (uint16, RW)new battery limits are activated when EnLimit is 1
}

View File

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>InnovEnergy.Lib.Devices.PLVario2Meter</RootNamespace>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Protocols\Modbus\Modbus.csproj" />
<ProjectReference Include="..\..\StatusApi\StatusApi.csproj" />
<ProjectReference Include="..\..\Units\Units.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,47 @@
using InnovEnergy.Lib.Protocols.Modbus.Channels;
using InnovEnergy.Lib.Protocols.Modbus.Clients;
using InnovEnergy.Lib.Protocols.Modbus.Slaves;
using InnovEnergy.Lib.Utils;
namespace InnovEnergy.Lib.Devices.PLVario2Meter;
public class PlVarioMeterDevice: ModbusDevice<PlVarioMeterRegisters>
{
public PlVarioMeterDevice(String hostname, UInt16 port = 502, Byte slaveId = 2) : this(new TcpChannel(hostname, port), slaveId)
{
}
public PlVarioMeterDevice(Channel channel, Byte slaveId = 2) : base(new ModbusTcpClient(channel, slaveId))
{
}
public PlVarioMeterDevice(ModbusClient client) : base(client)
{
}
public new PlVarioMeterRegisters? Read()
{
try
{
return base.Read();
}
catch
{
"Failed to read data from PLVario-II meter".WriteLine();
return null;
}
}
public new void Write(PlVarioMeterRegisters registers)
{
try
{
base.Write(registers);
}
catch (Exception e)
{
// TODO: Log
Console.WriteLine(e);
}
}
}

View File

@ -0,0 +1,133 @@
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Units.Power;
namespace InnovEnergy.Lib.Devices.PLVario2Meter;
public class PlVarioMeterRecord
{
private readonly PlVarioMeterRegisters _raw;
public PlVarioMeterRecord(PlVarioMeterRegisters raw)
{
_raw = raw ?? throw new ArgumentNullException(nameof(raw));
}
static ushort SwapBytes16(ushort x)
=> (ushort)((x << 8) | (x >> 8));
static float DecodeFloat_BADC(ushort w0, ushort w1)
{
// Build bytes: [B A D C] = swap bytes in each word, and use W1 then W0
ushort w0s = SwapBytes16(w0); // F1AA
ushort w1s = SwapBytes16(w1); // 4247
uint raw = ((uint)w1s << 16) | w0s; // 0x4247F1AA
return BitConverter.Int32BitsToSingle(unchecked((int)raw));
}
// -------------------------------------------------------------------------
// Decoders (DCBA) from raw UInt16 pairs
// -------------------------------------------------------------------------
private static uint DecodeUInt32DCBA(ushort lowWord, ushort highWord)
=> ((uint)highWord << 16) | (uint)lowWord;
private static float DecodeFloatDCBA(ushort lowWord, ushort highWord)
{
uint raw = ((uint)highWord << 16) | (uint)lowWord;
return BitConverter.Int32BitsToSingle(unchecked((int)raw));
}
private static Double F(ushort w0, ushort w1) => DecodeFloat_BADC(w0, w1);
private static UInt32 U32(ushort w0, ushort w1) => DecodeUInt32DCBA(w0, w1);
// -------------------------------------------------------------------------
// Time
// -------------------------------------------------------------------------
/// <summary>Timestamp in seconds (as provided by the device).</summary>
public uint TimestampSeconds => U32(_raw.Timestamp_W0, _raw.Timestamp_W1);
/// <summary>Timestamp interpreted as Unix epoch seconds (UTC).</summary>
public DateTimeOffset TimestampUtc => DateTimeOffset.FromUnixTimeSeconds(TimestampSeconds);
// -------------------------------------------------------------------------
// Voltages (phase-neutral and line-line)
// -------------------------------------------------------------------------
public VoltageRms VoltageU1 => new(F(_raw.VoltageU1_W0, _raw.VoltageU1_W1)); // U-1
public VoltageRms VoltageU2 => new(F(_raw.VoltageU2_W0, _raw.VoltageU2_W1)); // U-2
public VoltageRms VoltageU3 => new(F(_raw.VoltageU3_W0, _raw.VoltageU3_W1)); // U-3
// Friendly aliases (match your naming style)
public VoltageRms GridAbLineVoltage => (VoltageRms)new(F(_raw.VoltageUL1_W0, _raw.VoltageUL1_W1));
public VoltageRms GridBcLineVoltage => (VoltageRms)new(F(_raw.VoltageUL2_W0, _raw.VoltageUL2_W1));
public VoltageRms GridCaLineVoltage => (VoltageRms)new(F(_raw.VoltageUL3_W0, _raw.VoltageUL3_W1));
// -------------------------------------------------------------------------
// Frequency
// -------------------------------------------------------------------------
public Double Frequency => (F(_raw.Frequency_W0, _raw.Frequency_W1));
// -------------------------------------------------------------------------
// Currents
// -------------------------------------------------------------------------
public CurrentRms CurrentI1 => new(F(_raw.CurrentI1_W0, _raw.CurrentI1_W1));
public CurrentRms CurrentI2 => new(F(_raw.CurrentI2_W0, _raw.CurrentI2_W1));
public CurrentRms CurrentI3 => new(F(_raw.CurrentI3_W0, _raw.CurrentI3_W1));
public CurrentRms CurrentI4 => new(F(_raw.CurrentI4_W0, _raw.CurrentI4_W1)); // optional channel
public CurrentRms CurrentTotal => new(F(_raw.CurrentTotal_W0, _raw.CurrentTotal_W1));
// -------------------------------------------------------------------------
// Active Power (kW)
// -------------------------------------------------------------------------
public ActivePower ActivePowerL1 => new(F(_raw.ActivePowerP1_W0, _raw.ActivePowerP1_W1) * 1000);
public ActivePower ActivePowerL2 => new(F(_raw.ActivePowerP2_W0, _raw.ActivePowerP2_W1) * 1000);
public ActivePower ActivePowerL3 => new(F(_raw.ActivePowerP3_W0, _raw.ActivePowerP3_W1) * 1000);
public ActivePower ActivePowerCh4 => new(F(_raw.ActivePowerP4_W0, _raw.ActivePowerP4_W1) * 1000); // optional channel
public ActivePower ActivePowerTotal => new(F(_raw.ActivePowerTotal_W0, _raw.ActivePowerTotal_W1) * 1000);
// If you later map import/export separately, you can redefine these.
// For now, the table provides only total P (signed), so we expose it as "GridPower".
public ActivePower GridPower => ActivePowerTotal;
// -------------------------------------------------------------------------
// Reactive Power (kVAr)
// -------------------------------------------------------------------------
public ReactivePower ReactivePowerL1 => new(F(_raw.ReactivePowerQ1_W0, _raw.ReactivePowerQ1_W1));
public ReactivePower ReactivePowerL2 => new(F(_raw.ReactivePowerQ2_W0, _raw.ReactivePowerQ2_W1));
public ReactivePower ReactivePowerL3 => new(F(_raw.ReactivePowerQ3_W0, _raw.ReactivePowerQ3_W1));
public ReactivePower ReactivePowerCh4 => new(F(_raw.ReactivePowerQ4_W0, _raw.ReactivePowerQ4_W1));
public ReactivePower ReactivePowerTotal => new(F(_raw.ReactivePowerTotal_W0, _raw.ReactivePowerTotal_W1));
// Friendly alias
public ReactivePower GridReactivePower => ReactivePowerTotal;
// -------------------------------------------------------------------------
// Apparent Power (kVA)
// -------------------------------------------------------------------------
public ApparentPower ApparentPowerL1 => new(F(_raw.ApparentPowerS1_W0, _raw.ApparentPowerS1_W1));
public ApparentPower ApparentPowerL2 => new(F(_raw.ApparentPowerS2_W0, _raw.ApparentPowerS2_W1));
public ApparentPower ApparentPowerL3 => new(F(_raw.ApparentPowerS3_W0, _raw.ApparentPowerS3_W1));
public ApparentPower ApparentPowerCh4 => new(F(_raw.ApparentPowerS4_W0, _raw.ApparentPowerS4_W1));
public ApparentPower ApparentPowerTotal => new(F(_raw.ApparentPowerTotal_W0, _raw.ApparentPowerTotal_W1));
// -------------------------------------------------------------------------
// Power Factor
// -------------------------------------------------------------------------
public Double PowerFactorL1 => (F(_raw.PowerFactorPF1_W0, _raw.PowerFactorPF1_W1));
public Double PowerFactorL2 => (F(_raw.PowerFactorPF2_W0, _raw.PowerFactorPF2_W1));
public Double PowerFactorL3 => (F(_raw.PowerFactorPF3_W0, _raw.PowerFactorPF3_W1));
public Double PowerFactorCh4 => (F(_raw.PowerFactorPF4_W0, _raw.PowerFactorPF4_W1));
public Double PowerFactorTotal => (F(_raw.PowerFactorTotal_W0, _raw.PowerFactorTotal_W1));
// -------------------------------------------------------------------------
// Repeated totals (table shows duplicates at offsets 66..74)
// Keep them exposed in case your installation populates them differently.
// -------------------------------------------------------------------------
public CurrentRms CurrentTotalRepeat => new(F(_raw.CurrentTotalRepeat_W0, _raw.CurrentTotalRepeat_W1));
public ActivePower ActivePowerTotalRepeat => new(F(_raw.ActivePowerTotalRepeat_W0, _raw.ActivePowerTotalRepeat_W1));
public ReactivePower ReactivePowerTotalRepeat => new(F(_raw.ReactivePowerTotalRepeat_W0, _raw.ReactivePowerTotalRepeat_W1));
public ApparentPower ApparentPowerTotalRepeat => new(F(_raw.ApparentPowerTotalRepeat_W0, _raw.ApparentPowerTotalRepeat_W1));
public Double PowerFactorTotalRepeat => (F(_raw.PowerFactorTotalRepeat_W0, _raw.PowerFactorTotalRepeat_W1));
}

View File

@ -0,0 +1,168 @@
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
using InnovEnergy.Lib.StatusApi.DeviceTypes;
using InnovEnergy.Lib.Units.Composite;
// ReSharper disable InconsistentNaming
namespace InnovEnergy.Lib.Devices.PLVario2Meter;
using Float32 = Single;
// If your project already defines Float32 as an alias/struct, keep that.
// Otherwise, you can just use float. Here we use float for simplicity.
public class PlVarioMeterRegisters : IAc3Meter
{
// -------------------------------------------------------------------------
// Timestamp (UINT32) -> 2 x UInt16
// Offset 0: Timestamp [s]
// -------------------------------------------------------------------------
[HoldingRegister<UInt16>(1000, writable: false)] public UInt16 Timestamp_W0; // low word
[HoldingRegister<UInt16>(1001, writable: false)] public UInt16 Timestamp_W1; // high word
// -------------------------------------------------------------------------
// Voltages phase-neutral (FLOAT32) -> 2 x UInt16 each
// Offsets 2,4,6
// -------------------------------------------------------------------------
[HoldingRegister<UInt16>(1002, writable: false)] public UInt16 VoltageU1_W0;
[HoldingRegister<UInt16>(1003, writable: false)] public UInt16 VoltageU1_W1;
[HoldingRegister<UInt16>(1004, writable: false)] public UInt16 VoltageU2_W0;
[HoldingRegister<UInt16>(1005, writable: false)] public UInt16 VoltageU2_W1;
[HoldingRegister<UInt16>(1006, writable: false)] public UInt16 VoltageU3_W0;
[HoldingRegister<UInt16>(1007, writable: false)] public UInt16 VoltageU3_W1;
// -------------------------------------------------------------------------
// Voltages line-line (FLOAT32) -> 2 x UInt16 each
// Offsets 8,10,12
// -------------------------------------------------------------------------
[HoldingRegister<UInt16>(1008, writable: false)] public UInt16 VoltageUL1_W0;
[HoldingRegister<UInt16>(1009, writable: false)] public UInt16 VoltageUL1_W1;
[HoldingRegister<UInt16>(1010, writable: false)] public UInt16 VoltageUL2_W0;
[HoldingRegister<UInt16>(1011, writable: false)] public UInt16 VoltageUL2_W1;
[HoldingRegister<UInt16>(1012, writable: false)] public UInt16 VoltageUL3_W0;
[HoldingRegister<UInt16>(1013, writable: false)] public UInt16 VoltageUL3_W1;
// -------------------------------------------------------------------------
// Frequency (FLOAT32) -> 2 x UInt16
// Offset 14
// -------------------------------------------------------------------------
[HoldingRegister<UInt16>(1014, writable: false)] public UInt16 Frequency_W0;
[HoldingRegister<UInt16>(1015, writable: false)] public UInt16 Frequency_W1;
// -------------------------------------------------------------------------
// Currents (FLOAT32) -> 2 x UInt16 each
// Offsets 16..24
// -------------------------------------------------------------------------
[HoldingRegister<UInt16>(1016, writable: false)] public UInt16 CurrentI1_W0;
[HoldingRegister<UInt16>(1017, writable: false)] public UInt16 CurrentI1_W1;
[HoldingRegister<UInt16>(1018, writable: false)] public UInt16 CurrentI2_W0;
[HoldingRegister<UInt16>(1019, writable: false)] public UInt16 CurrentI2_W1;
[HoldingRegister<UInt16>(1020, writable: false)] public UInt16 CurrentI3_W0;
[HoldingRegister<UInt16>(1021, writable: false)] public UInt16 CurrentI3_W1;
[HoldingRegister<UInt16>(1022, writable: false)] public UInt16 CurrentI4_W0;
[HoldingRegister<UInt16>(1023, writable: false)] public UInt16 CurrentI4_W1;
[HoldingRegister<UInt16>(1024, writable: false)] public UInt16 CurrentTotal_W0;
[HoldingRegister<UInt16>(1025, writable: false)] public UInt16 CurrentTotal_W1;
// -------------------------------------------------------------------------
// Active power P (FLOAT32, kW) -> 2 x UInt16 each
// Offsets 26..34
// -------------------------------------------------------------------------
[HoldingRegister<UInt16>(1026, writable: false)] public UInt16 ActivePowerP1_W0;
[HoldingRegister<UInt16>(1027, writable: false)] public UInt16 ActivePowerP1_W1;
[HoldingRegister<UInt16>(1028, writable: false)] public UInt16 ActivePowerP2_W0;
[HoldingRegister<UInt16>(1029, writable: false)] public UInt16 ActivePowerP2_W1;
[HoldingRegister<UInt16>(1030, writable: false)] public UInt16 ActivePowerP3_W0;
[HoldingRegister<UInt16>(1031, writable: false)] public UInt16 ActivePowerP3_W1;
[HoldingRegister<UInt16>(1032, writable: false)] public UInt16 ActivePowerP4_W0;
[HoldingRegister<UInt16>(1033, writable: false)] public UInt16 ActivePowerP4_W1;
[HoldingRegister<UInt16>(1034, writable: false)] public UInt16 ActivePowerTotal_W0;
[HoldingRegister<UInt16>(1035, writable: false)] public UInt16 ActivePowerTotal_W1;
// -------------------------------------------------------------------------
// Reactive power Q (FLOAT32, kVAr) -> 2 x UInt16 each
// Offsets 36..44
// -------------------------------------------------------------------------
[HoldingRegister<UInt16>(1036, writable: false)] public UInt16 ReactivePowerQ1_W0;
[HoldingRegister<UInt16>(1037, writable: false)] public UInt16 ReactivePowerQ1_W1;
[HoldingRegister<UInt16>(1038, writable: false)] public UInt16 ReactivePowerQ2_W0;
[HoldingRegister<UInt16>(1039, writable: false)] public UInt16 ReactivePowerQ2_W1;
[HoldingRegister<UInt16>(1040, writable: false)] public UInt16 ReactivePowerQ3_W0;
[HoldingRegister<UInt16>(1041, writable: false)] public UInt16 ReactivePowerQ3_W1;
[HoldingRegister<UInt16>(1042, writable: false)] public UInt16 ReactivePowerQ4_W0;
[HoldingRegister<UInt16>(1043, writable: false)] public UInt16 ReactivePowerQ4_W1;
[HoldingRegister<UInt16>(1044, writable: false)] public UInt16 ReactivePowerTotal_W0;
[HoldingRegister<UInt16>(1045, writable: false)] public UInt16 ReactivePowerTotal_W1;
// -------------------------------------------------------------------------
// Apparent power S (FLOAT32, kVA) -> 2 x UInt16 each
// Offsets 46..54
// -------------------------------------------------------------------------
[HoldingRegister<UInt16>(1046, writable: false)] public UInt16 ApparentPowerS1_W0;
[HoldingRegister<UInt16>(1047, writable: false)] public UInt16 ApparentPowerS1_W1;
[HoldingRegister<UInt16>(1048, writable: false)] public UInt16 ApparentPowerS2_W0;
[HoldingRegister<UInt16>(1049, writable: false)] public UInt16 ApparentPowerS2_W1;
[HoldingRegister<UInt16>(1050, writable: false)] public UInt16 ApparentPowerS3_W0;
[HoldingRegister<UInt16>(1051, writable: false)] public UInt16 ApparentPowerS3_W1;
[HoldingRegister<UInt16>(1052, writable: false)] public UInt16 ApparentPowerS4_W0;
[HoldingRegister<UInt16>(1053, writable: false)] public UInt16 ApparentPowerS4_W1;
[HoldingRegister<UInt16>(1054, writable: false)] public UInt16 ApparentPowerTotal_W0;
[HoldingRegister<UInt16>(1055, writable: false)] public UInt16 ApparentPowerTotal_W1;
// -------------------------------------------------------------------------
// Power factor PF (FLOAT32) -> 2 x UInt16 each
// Offsets 56..64
// -------------------------------------------------------------------------
[HoldingRegister<UInt16>(1056, writable: false)] public UInt16 PowerFactorPF1_W0;
[HoldingRegister<UInt16>(1057, writable: false)] public UInt16 PowerFactorPF1_W1;
[HoldingRegister<UInt16>(1058, writable: false)] public UInt16 PowerFactorPF2_W0;
[HoldingRegister<UInt16>(1059, writable: false)] public UInt16 PowerFactorPF2_W1;
[HoldingRegister<UInt16>(1060, writable: false)] public UInt16 PowerFactorPF3_W0;
[HoldingRegister<UInt16>(1061, writable: false)] public UInt16 PowerFactorPF3_W1;
[HoldingRegister<UInt16>(1062, writable: false)] public UInt16 PowerFactorPF4_W0;
[HoldingRegister<UInt16>(1063, writable: false)] public UInt16 PowerFactorPF4_W1;
[HoldingRegister<UInt16>(1064, writable: false)] public UInt16 PowerFactorTotal_W0;
[HoldingRegister<UInt16>(1065, writable: false)] public UInt16 PowerFactorTotal_W1;
// -------------------------------------------------------------------------
// Repeated totals (as shown in the table) at offsets 66..74
// -------------------------------------------------------------------------
[HoldingRegister<UInt16>(1066, writable: false)] public UInt16 CurrentTotalRepeat_W0;
[HoldingRegister<UInt16>(1067, writable: false)] public UInt16 CurrentTotalRepeat_W1;
[HoldingRegister<UInt16>(1068, writable: false)] public UInt16 ActivePowerTotalRepeat_W0;
[HoldingRegister<UInt16>(1069, writable: false)] public UInt16 ActivePowerTotalRepeat_W1;
[HoldingRegister<UInt16>(1070, writable: false)] public UInt16 ReactivePowerTotalRepeat_W0;
[HoldingRegister<UInt16>(1071, writable: false)] public UInt16 ReactivePowerTotalRepeat_W1;
[HoldingRegister<UInt16>(1072, writable: false)] public UInt16 ApparentPowerTotalRepeat_W0;
[HoldingRegister<UInt16>(1073, writable: false)] public UInt16 ApparentPowerTotalRepeat_W1;
[HoldingRegister<UInt16>(1074, writable: false)] public UInt16 PowerFactorTotalRepeat_W0;
[HoldingRegister<UInt16>(1075, writable: false)] public UInt16 PowerFactorTotalRepeat_W1;
public Ac3Bus Ac { get; }
}

View File

@ -0,0 +1,58 @@
namespace InnovEnergy.Lib.Devices.WITGrowatt4_15K.DataType;
public enum GrowattErrorCode
{
NoAlarm = 0,
ExportLimitationFailSafe = 311,
DcBiasAbnormal = 400,
HighDcComponentOutputCurrent = 402,
BusVoltageSamplingAbnormal = 404,
RelayFault = 405,
OverTemperature = 408,
BusVoltageAbnormal = 409,
InternalCommunicationFailure = 411,
TemperatureSensorDisconnected = 412,
IgbtDriveFault = 413,
EepromError = 414,
AuxiliaryPowerAbnormal = 415,
DcAcOvercurrentProtection = 416,
CommunicationProtocolMismatch = 417,
DspComFirmwareMismatch = 418,
DspSoftwareHardwareMismatch = 419,
CpldAbnormal = 421,
RedundancySamplingInconsistent = 422,
PwmPassThroughSignalFailure = 423,
AfciSelfTestFailure = 425,
PvCurrentSamplingAbnormal = 426,
AcCurrentSamplingAbnormal = 427,
BusSoftbootFailure = 429,
EpoFault = 430,
MonitoringChipBootVerificationFailed = 431,
BmsCommunicationFailure = 500,
BmsChargeDischargeFailure = 501,
BatteryVoltageLow = 502,
BatteryVoltageHigh = 503,
BatteryTemperatureAbnormal = 504,
BatteryReversed = 505,
BatteryOpenCircuit = 506,
BatteryOverloadProtection = 507,
Bus2VoltageAbnormal = 508,
BatteryChargeOcp = 509,
BatteryDischargeOcp = 510,
BatterySoftStartFailed = 511,
EpsOutputShortCircuited = 600,
OffGridBusVoltageLow = 601,
OffGridTerminalVoltageAbnormal = 602,
SoftStartFailed = 603,
OffGridOutputVoltageAbnormal = 604,
BalancedCircuitSelfTestFailed = 605,
HighDcComponentOutputVoltage = 606,
OffGridOutputOverload = 607,
OffGridParallelSignalAbnormal = 608
}

View File

@ -0,0 +1,58 @@
namespace InnovEnergy.Lib.Devices.WITGrowatt4_15K.DataType;
public enum GrowattWarningCode : UInt16
{
NoAlarm =0,
StringFault = 200,
PvStringPidQuickConnectAbnormal = 201,
DcSpdFunctionAbnormal = 202,
PvShortCircuited = 203,
PvBoostDriverAbnormal = 204,
AcSpdFunctionAbnormal = 205,
DcFuseBlown = 208,
DcInputVoltageTooHigh = 209,
PvReversed = 210,
PidFunctionAbnormal = 219,
PvStringDisconnected = 220,
PvStringCurrentUnbalanced = 221,
NoUtilityGrid = 300,
GridVoltageOutOfRange = 301,
GridFrequencyOutOfRange = 302,
Overload = 303,
MeterDisconnected = 308,
MeterReverselyConnected = 309,
LinePeVoltageAbnormal = 310,
PhaseSequenceError = 311,
FanFailure = 400,
MeterAbnormal = 401,
OptimizerCommunicationAbnormal = 402,
OverTemperature = 407,
OverTemperatureAlarm = 408,
NtcTemperatureSensorBroken = 409,
SyncSignalAbnormal = 411,
GridStartupConditionsNotMet = 412,
BatteryCommunicationFailure = 500,
BatteryDisconnected = 501,
BatteryVoltageTooHigh = 502,
BatteryVoltageTooLow = 503,
BatteryReverseConnected = 504,
LeadAcidTempSensorDisconnected = 505,
BatteryTemperatureOutOfRange = 506,
BmsFault = 507,
LithiumBatteryOverload = 508,
BmsCommunicationAbnormal = 509,
BatterySpdAbnormal = 510,
OutputDcComponentBiasAbnormal = 600,
DcComponentOverHighOutputVoltage = 601,
OffGridOutputVoltageTooLow = 602,
OffGridOutputVoltageTooHigh = 603,
OffGridOutputOverCurrent = 604,
OffGridBusVoltageTooLow = 605,
OffGridOutputOverload = 606,
BalancedCircuitAbnormal = 609
}

View File

@ -42,10 +42,10 @@ public partial class WITGrowatRecord
public BatteryoperatinStatus BatteryOperatingMode => (BatteryoperatinStatus) _batteryOperatingMode;
public OperatingPriority OperatingMode => (OperatingPriority)_operatingPriority;
public UInt16 FaultMainCode => _faultMainCode; // need to pre proceesed
public UInt16 FaultSubCode => _faultSubCode; // need to pre proceesed
public UInt16 WarningMainCode => _warningMainCode; // need to pre proceesed
public UInt16 WarningSubCode => _warningSubCode; // need to pre proceesed
public GrowattErrorCode FaultMainCode => _faultMainCode; // need to pre proceesed
public UInt16 FaultSubCode => _faultSubCode; // need to pre proceesed
public GrowattWarningCode WarningMainCode => _warningMainCode; // need to pre proceesed
public UInt16 WarningSubCode => _warningSubCode; // need to pre proceesed
public Voltage Pv1Voltage => _pv1Voltage;
public Current Pv1Current => _pv1Current;
@ -96,10 +96,10 @@ public partial class WITGrowatRecord
get => _VppProtocolVerNumber;
}
public Boolean ControlPermession
public Boolean ControlPermission
{
get => _ControlPermession;
set => _ControlPermession = value;
get => _ControlPermission;
set => _ControlPermission = value;
}
public Boolean EnableCommand
@ -172,16 +172,16 @@ public partial class WITGrowatRecord
set => _BatteryMaxDischargePower = value;
}
public Percent ChargeCutoffSocVoltage
public Voltage ChargeCutoffSocVoltage
{
get => _BatteryChargeCutoffVoltage;
set => _BatteryChargeCutoffVoltage = (UInt16)value;
get => _batteryChargeCutoffVoltage;
set => _batteryChargeCutoffVoltage = (UInt16)value;
}
public Percent DischargeCutoffVoltage
public Voltage DischargeCutoffVoltage
{
get => _BatteryDischargeCutoffVoltage;
set => _BatteryDischargeCutoffVoltage = (UInt16)value;
get => _batteryDischargeCutoffVoltage;
set => _batteryDischargeCutoffVoltage = (UInt16)value;
}
public Percent LoadPriorityDischargeCutoffSoc
@ -249,9 +249,8 @@ public partial class WITGrowatRecord
set => _batteryMaxDischargingCurrent = (UInt16) value;
}
public Double Battery1MaxDischargingCurrent => _batteryMaxDischargingCurrent;
public Double Battery1MaxChargingCurrent => _batteryMaxChargingCurrent;
public Double Battery1MaxDischargingCurrent => _batteryMaxDischargingCurrent;
public Double Battery1MaxChargingCurrent => _batteryMaxChargingCurrent;
public Percent Battery1Soc => _BatterySoc1;
public Percent Battery1Soh => _BatterySoh1;

View File

@ -1,3 +1,4 @@
using InnovEnergy.Lib.Devices.WITGrowatt4_15K.DataType;
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
namespace InnovEnergy.Lib.Devices.WITGrowatt4_15K;
@ -19,9 +20,9 @@ public partial class WITGrowatRecord
[InputRegister(31002)] private UInt16 _operatingPriority;
[InputRegister(31003)] private UInt16 _reserved1;
[InputRegister(31004)] private UInt16 _reserved2;
[InputRegister(31005)] private UInt16 _faultMainCode;// Can we change this to warning?
[InputRegister(31005)] private GrowattErrorCode _faultMainCode;// Can we change this to warning?
[InputRegister(31006)] private UInt16 _faultSubCode; // Can we change this to warning?
[InputRegister(31007)] private UInt16 _warningMainCode;
[InputRegister(31007)] private GrowattWarningCode _warningMainCode;
[InputRegister(31008)] private UInt16 _warningSubCode;
[InputRegister(31009)] private UInt16 _reserved3;
// 3101031099 — PV Parameters
@ -144,7 +145,7 @@ public partial class WITGrowatRecord
[HoldingRegister(30030)] private UInt16 _BatteryType;
[HoldingRegister(30099)] private UInt16 _VppProtocolVerNumber;
[HoldingRegister(30100, writable: true)] private Boolean _ControlPermession; // 0 Disabled, 1 enabled
[HoldingRegister(30100, writable: true)] private Boolean _ControlPermission; // 0 Disabled, 1 enabled
[HoldingRegister(30101, writable: true)] private Boolean _EnableCommand; // 0: Off, 1: On; Defaut is 1; not stored, must enable this register to control inverter
[HoldingRegister(30102)] private UInt16 _CountryRegionCode;
[HoldingRegister(30103)] private UInt16 _Reserved8;
@ -166,7 +167,7 @@ public partial class WITGrowatRecord
[HoldingRegister(30162)] private UInt16 _PowerFactor; // [0, 2000] [18000, 20000]; Default: 20000; Actual PF = (Register Value - 10000)
[HoldingRegister(30203, writable : true)] private UInt16 _EmsCommunicationFailureTime; // [1,300] TODO to 30
[HoldingRegister(30204, writable : true)] private Boolean _EnableEmsCommunicationFailureTime; // 0: disabled, 1 = enabled we should enable this TODO
[HoldingRegister(30204, writable : true)] private Boolean _EnableEmsCommunicationFailureTime; // 0: disabled, 1 = enabled we should not enable this the naming is not correct
[HoldingRegister(30300)] private UInt16 _BatteryClusterIndex; // [0..3]
[HoldingRegister<UInt32>(30400 , writable: true)] private UInt32 _BatteryMaxChargePower; //
@ -182,8 +183,8 @@ public partial class WITGrowatRecord
[HoldingRegister(30474)] private UInt16 _ActualChargeDischargePowerControlValue; //
[HoldingRegister(30475)] private UInt16 _OffGridDischargeCutoffSoc; //
[HoldingRegister(30496, writable: true, Scale = 0.1)] private UInt16 _BatteryChargeCutoffVoltage; //
[HoldingRegister(30497, writable: true, Scale = 0.1)] private UInt16 _BatteryDischargeCutoffVoltage; //
[HoldingRegister(30496, writable: true, Scale = 0.1)] private UInt16 _batteryChargeCutoffVoltage; //
[HoldingRegister(30497, writable: true, Scale = 0.1)] private UInt16 _batteryDischargeCutoffVoltage; //
[HoldingRegister(30498, writable: true, Scale = 0.1)] private UInt16 _batteryMaxChargingCurrent; //
[HoldingRegister(30499, writable: true, Scale = 0.1)] private UInt16 _batteryMaxDischargingCurrent; //*/
}

View File

@ -15,5 +15,6 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Channels
{
public abstract IReadOnlyList<byte> Read(int nBytes);
public abstract void Write(IReadOnlyList<byte> bytes);
public virtual void ClearBuffers() { }
}
}

View File

@ -22,7 +22,11 @@ public abstract class ConnectionChannel<T> : Channel, IDisposable
_CloseAfterSuccessfulWrite = closeAfterSuccessfulWrite;
_CloseAfterException = closeAfterException ?? (_ => true);
}
// 👇 NEW: optional per-connection buffer clear
protected virtual void ClearBuffers(T connection)
{
// default: do nothing
}
public override IReadOnlyList<Byte> Read(Int32 nBytes)
{
try
@ -66,7 +70,7 @@ public abstract class ConnectionChannel<T> : Channel, IDisposable
private T Connection => _Connection ??= Open();
private void Close()
{

View File

@ -36,7 +36,23 @@ public class SerialPortChannel : ConnectionChannel<SerialPort>
return serialPort;
};
}
// 👇 NEW: this is where we actually flush the serial buffers
protected override void ClearBuffers(SerialPort serialPort)
{
try
{
serialPort.DiscardInBuffer();
serialPort.DiscardOutBuffer();
Console.WriteLine(" Buffer in and out discarded.");
}
catch
{
Console.WriteLine(" couldn't clear the Buffer .");
// ignore if port is closed / disposed
}
}
protected override SerialPort Open() => _Open();
protected override void Close(SerialPort serialPort) => serialPort.Dispose();

View File

@ -36,4 +36,5 @@ public abstract class ModbusClient
SlaveId = slaveId;
Endian = endian;
}
}

View File

@ -4,7 +4,11 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
public class Coil : ModbusBoolean
{
public Coil(UInt16 address) : base(address, ModbusKind.Coil)
public bool Writable { get; }
public Coil(ushort address, bool writable = true)
: base(address, ModbusKind.Coil)
{
Writable = writable;
}
}

View File

@ -37,7 +37,7 @@ public static class Batches
{
if (CloseBatch(member))
{
//PrintBatchStart(batchMembers);
PrintBatchStart(batchMembers);
yield return MakeBatch<R>(mb, batchMembers);
batchMembers = new List<ModbusMember>();
}
@ -47,7 +47,6 @@ public static class Batches
if (batchMembers.Count > 0)
{
//PrintBatchStart(batchMembers);
yield return MakeBatch<R>(mb, batchMembers);
}
@ -74,8 +73,16 @@ public static class Batches
: $"{first.StartAddress}-{last.EndAddress - 1}";
var writable = first.IsWritable ? "Writable" : "ReadOnly";
Console.WriteLine($"📦 New Batch: {first.Kind} [{range}] ({writable})");
foreach (var member in members)
{
if (member.Kind == ModbusKind.Coil)
{
Console.WriteLine($"👀 Coil: {member.StartAddress}, Name: {member.Member.Name}, Writable: {member.IsWritable}");
}
}
}
private static Batch<R> MakeBatch<R>(ModbusClient modbusClient, IReadOnlyList<ModbusMember> members)
{
@ -86,7 +93,7 @@ public static class Batches
//var isWritable = kind is ModbusKind.HoldingRegister or ModbusKind.Coil;
var debugString = $"{kind}: {startAddress}" + (endAddress - 1 == startAddress ? "" : $"-{endAddress - 1}");
var isWritable = members.Any(m => m.IsWritable);
var isWritable = kind == ModbusKind.Coil || members.Any(m => m.IsWritable);
//foreach (var m in members)
// Console.WriteLine($"🧪 Address {m.StartAddress} Writable: {m.IsWritable}");

View File

@ -8,10 +8,12 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Slaves;
public class ModbusDevice<[DynamicallyAccessedMembers(All)] R> where R : notnull
{
private readonly IReadOnlyList<Batch<R>> _Batches;
private readonly ModbusClient _modbusClient;
public ModbusDevice(ModbusClient modbusClient, Int32 addressOffset = 0)
{
_Batches = modbusClient.MakeBatchesFor<R>(addressOffset);
_modbusClient = modbusClient;
}
public R Read()
@ -40,6 +42,7 @@ public class ModbusDevice<[DynamicallyAccessedMembers(All)] R> where R : notnull
//Console.WriteLine($"Reading { _Batches.Count } Modbus batches.");
foreach (var batch in _Batches)
{
_modbusClient.Channel.ClearBuffers();
batch.Read(record);
//Thread.Sleep(30); // this added mainly for Growatt reading
}
@ -50,6 +53,7 @@ public class ModbusDevice<[DynamicallyAccessedMembers(All)] R> where R : notnull
{
foreach (var batch in _Batches)
{
_modbusClient.Channel.ClearBuffers();
batch.Write(record);
//Thread.Sleep(50); // this added mainly for Growatt reading
}

View File

@ -0,0 +1,48 @@
namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
public enum SinexcelAbnormalFlag : ushort
{
Normal = 0,
Abnormal = 1
}
public enum SinexcelInverterOperatingState : ushort
{
Standby = 0,
Ready = 1,
OffGrid = 2,
OnGrid = 3
}
public enum SinexcelPvOperatingState : ushort
{
Standby = 0,
Run = 1
}
public enum SinexcelBatteryOperatingState : ushort
{
Standby = 0,
Charging = 1,
Discharging = 2,
FullyCharged = 3,
SelfInspection = 4
}
public enum SinexcelDcdcState : ushort
{
Shutdown = 0,
Booting = 1
}
public enum SinexcelPowerGridStatus : ushort
{
Normal = 0,
Abnormal = 1
}
public enum SinexcelOredState : ushort
{
State0 = 0, State1, State2, State3, State4,
State5, State6, State7, State8, State9
}

View File

@ -6,7 +6,193 @@ namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL;
public partial class SinexcelRecord
{
/*
// --------------------------------------------------------
// Helpers
// --------------------------------------------------------
private static ushort Bit(ushort v) => (ushort)(v & 0x01);
private static ushort Compose3(ushort b0, ushort b1, ushort b2)
=> (ushort)(Bit(b0) | (Bit(b1) << 1) | (Bit(b2) << 2));
private static ushort Compose4(ushort b0, ushort b1, ushort b2, ushort b3)
=> (ushort)(Bit(b0) | (Bit(b1) << 1) | (Bit(b2) << 2) | (Bit(b3) << 3));
// ---- Operating states
public SinexcelInverterOperatingState InverterOperatingState
=> (SinexcelInverterOperatingState)(_inverterOperatingState & 0x07);
public SinexcelPvOperatingState Pv1OperatingState
=> (SinexcelPvOperatingState)Bit(_pv1OperatingState);
public SinexcelPvOperatingState Pv2OperatingState
=> (SinexcelPvOperatingState)Bit(_pv2OperatingState);
public SinexcelPvOperatingState Pv3OperatingState
=> (SinexcelPvOperatingState)Bit(_pv3OperatingState);
public SinexcelPvOperatingState Pv4OperatingState
=> (SinexcelPvOperatingState)Bit(_pv4OperatingState);
public SinexcelBatteryOperatingState Battery1OperatingState
=> (SinexcelBatteryOperatingState)(_battery1OperatingState & 0x07);
public SinexcelBatteryOperatingState Battery2OperatingState
=> (SinexcelBatteryOperatingState)Compose3(_bat2StateBit0, _bat2StateBit1, _bat2StateBit2);
public SinexcelOredState OredState
=> (SinexcelOredState)Compose4(_oredStateBit0, _oredStateBit1, _oredStateBit2, _oredStateBit3);
public SinexcelDcdcState DcdcStateDc
=> (SinexcelDcdcState)Bit(_dcdcStateDc);
public SinexcelDcdcState DcdcStateAc
=> (SinexcelDcdcState)Bit(_dcdcStateAc);
public SinexcelPowerGridStatus PowerGridStatus
=> (SinexcelPowerGridStatus)Bit(_powerGridStatus);
// ---- Alarm / fault flags
public SinexcelAbnormalFlag AbnormalGridVoltage => (SinexcelAbnormalFlag)Bit(_abnormalGridVoltage);
public SinexcelAbnormalFlag AbnormalGridFrequency => (SinexcelAbnormalFlag)Bit(_abnormalGridFrequency);
public SinexcelAbnormalFlag InvertedSequenceOfGridVoltage => (SinexcelAbnormalFlag)Bit(_invertedSequenceOfGridVoltage);
public SinexcelAbnormalFlag GridVoltagePhaseLoss => (SinexcelAbnormalFlag)Bit(_gridVoltagePhaseLoss);
public SinexcelAbnormalFlag AbnormalOutputVoltage => (SinexcelAbnormalFlag)Bit(_abnormalOutputVoltage);
public SinexcelAbnormalFlag AbnormalOutputFrequency => (SinexcelAbnormalFlag)Bit(_abnormalOutputFrequency);
public SinexcelAbnormalFlag AbnormalNullLine => (SinexcelAbnormalFlag)Bit(_abnormalNullLine);
public SinexcelAbnormalFlag ExcessivelyHighAmbientTemperature => (SinexcelAbnormalFlag)Bit(_excessivelyHighAmbientTemperature);
public SinexcelAbnormalFlag ExcessiveRadiatorTemperature => (SinexcelAbnormalFlag)Bit(_excessiveRadiatorTemperature);
public SinexcelAbnormalFlag InsulationFault => (SinexcelAbnormalFlag)Bit(_insulationFault);
public SinexcelAbnormalFlag LeakageProtectionFault => (SinexcelAbnormalFlag)Bit(_leakageProtectionFault);
public SinexcelAbnormalFlag AuxiliaryPowerFault => (SinexcelAbnormalFlag)Bit(_auxiliaryPowerFault);
public SinexcelAbnormalFlag FanFault => (SinexcelAbnormalFlag)Bit(_fanFault);
public SinexcelAbnormalFlag ModelCapacityFault => (SinexcelAbnormalFlag)Bit(_modelCapacityFault);
public SinexcelAbnormalFlag AbnormalLightningArrester => (SinexcelAbnormalFlag)Bit(_abnormalLightningArrester);
public SinexcelAbnormalFlag IslandProtection => (SinexcelAbnormalFlag)Bit(_islandProtection);
public SinexcelAbnormalFlag Battery1NotConnected => (SinexcelAbnormalFlag)Bit(_battery1NotConnected);
public SinexcelAbnormalFlag Battery1Overvoltage => (SinexcelAbnormalFlag)Bit(_battery1Overvoltage);
public SinexcelAbnormalFlag Battery1Undervoltage => (SinexcelAbnormalFlag)Bit(_battery1Undervoltage);
public SinexcelAbnormalFlag Battery1DischargeEnd => (SinexcelAbnormalFlag)Bit(_battery1DischargeEnd);
public SinexcelAbnormalFlag Battery1Inverted => (SinexcelAbnormalFlag)Bit(_battery1Inverted);
public SinexcelAbnormalFlag Battery2NotConnected => (SinexcelAbnormalFlag)Bit(_battery2NotConnected);
public SinexcelAbnormalFlag Battery2Overvoltage => (SinexcelAbnormalFlag)Bit(_battery2Overvoltage);
public SinexcelAbnormalFlag Battery2Undervoltage => (SinexcelAbnormalFlag)Bit(_battery2Undervoltage);
public SinexcelAbnormalFlag Battery2DischargeEnd => (SinexcelAbnormalFlag)Bit(_battery2DischargeEnd);
public SinexcelAbnormalFlag Battery2Inverted => (SinexcelAbnormalFlag)Bit(_battery2Inverted);
public SinexcelAbnormalFlag Pv1NotAccessed => (SinexcelAbnormalFlag)Bit(_pv1NotAccessed);
public SinexcelAbnormalFlag Pv1Overvoltage => (SinexcelAbnormalFlag)Bit(_pv1Overvoltage);
public SinexcelAbnormalFlag AbnormalPv1CurrentSharing => (SinexcelAbnormalFlag)Bit(_abnormalPv1CurrentSharing);
public SinexcelAbnormalFlag Pv2NotAccessed => (SinexcelAbnormalFlag)Bit(_pv2NotAccessed);
public SinexcelAbnormalFlag Pv2Overvoltage => (SinexcelAbnormalFlag)Bit(_pv2Overvoltage);
public SinexcelAbnormalFlag AbnormalPv2CurrentSharing => (SinexcelAbnormalFlag)Bit(_abnormalPv2CurrentSharing);
public SinexcelAbnormalFlag DcBusOvervoltage => (SinexcelAbnormalFlag)Bit(_dcBusOvervoltage);
public SinexcelAbnormalFlag DcBusUndervoltage => (SinexcelAbnormalFlag)Bit(_dcBusUndervoltage);
public SinexcelAbnormalFlag DcBusVoltageUnbalance => (SinexcelAbnormalFlag)Bit(_dcBusVoltageUnbalance);
public SinexcelAbnormalFlag Pv1PowerTubeFault => (SinexcelAbnormalFlag)Bit(_pv1PowerTubeFault);
public SinexcelAbnormalFlag Pv2PowerTubeFault => (SinexcelAbnormalFlag)Bit(_pv2PowerTubeFault);
public SinexcelAbnormalFlag Battery1PowerTubeFault => (SinexcelAbnormalFlag)Bit(_battery1PowerTubeFault);
public SinexcelAbnormalFlag Battery2PowerTubeFault => (SinexcelAbnormalFlag)Bit(_battery2PowerTubeFault);
public SinexcelAbnormalFlag InverterPowerTubeFault => (SinexcelAbnormalFlag)Bit(_inverterPowerTubeFault);
public SinexcelAbnormalFlag SystemOutputOverload => (SinexcelAbnormalFlag)Bit(_systemOutputOverload);
public SinexcelAbnormalFlag InverterOverload => (SinexcelAbnormalFlag)Bit(_inverterOverload);
public SinexcelAbnormalFlag InverterOverloadTimeout => (SinexcelAbnormalFlag)Bit(_inverterOverloadTimeout);
public SinexcelAbnormalFlag Battery1OverloadTimeout => (SinexcelAbnormalFlag)Bit(_battery1OverloadTimeout);
public SinexcelAbnormalFlag Battery2OverloadTimeout => (SinexcelAbnormalFlag)Bit(_battery2OverloadTimeout);
public SinexcelAbnormalFlag InverterSoftStartFailure => (SinexcelAbnormalFlag)Bit(_inverterSoftStartFailure);
public SinexcelAbnormalFlag Battery1SoftStartFailure => (SinexcelAbnormalFlag)Bit(_battery1SoftStartFailure);
public SinexcelAbnormalFlag Battery2SoftStartFailure => (SinexcelAbnormalFlag)Bit(_battery2SoftStartFailure);
public SinexcelAbnormalFlag Dsp1ParameterSettingFault => (SinexcelAbnormalFlag)Bit(_dsp1ParameterSettingFault);
public SinexcelAbnormalFlag Dsp2ParameterSettingFault => (SinexcelAbnormalFlag)Bit(_dsp2ParameterSettingFault);
public SinexcelAbnormalFlag DspVersionCompatibilityFault => (SinexcelAbnormalFlag)Bit(_dspVersionCompatibilityFault);
public SinexcelAbnormalFlag CpldVersionCompatibilityFault => (SinexcelAbnormalFlag)Bit(_cpldVersionCompatibilityFault);
public SinexcelAbnormalFlag CpldCommunicationFault => (SinexcelAbnormalFlag)Bit(_cpldCommunicationFault);
public SinexcelAbnormalFlag DspCommunicationFault => (SinexcelAbnormalFlag)Bit(_dspCommunicationFault);
public SinexcelAbnormalFlag OutputVoltageDcOverlimit => (SinexcelAbnormalFlag)Bit(_outputVoltageDcOverlimit);
public SinexcelAbnormalFlag OutputCurrentDcOverlimit => (SinexcelAbnormalFlag)Bit(_outputCurrentDcOverlimit);
public SinexcelAbnormalFlag RelaySelfCheckFails => (SinexcelAbnormalFlag)Bit(_relaySelfCheckFails);
public SinexcelAbnormalFlag AbnormalInverter => (SinexcelAbnormalFlag)Bit(_abnormalInverter);
public SinexcelAbnormalFlag PoorGrounding => (SinexcelAbnormalFlag)Bit(_poorGrounding);
public SinexcelAbnormalFlag Pv1SoftStartFailure => (SinexcelAbnormalFlag)Bit(_pv1SoftStartFailure);
public SinexcelAbnormalFlag Pv2SoftStartFailure => (SinexcelAbnormalFlag)Bit(_pv2SoftStartFailure2);
public SinexcelAbnormalFlag BalancedCircuitOverloadTimeout => (SinexcelAbnormalFlag)Bit(_balancedCircuitOverloadTimeout);
public SinexcelAbnormalFlag Pv1OverloadTimeout => (SinexcelAbnormalFlag)Bit(_pv1OverloadTimeout);
public SinexcelAbnormalFlag Pv2OverloadTimeout => (SinexcelAbnormalFlag)Bit(_pv2OverloadTimeout);
public SinexcelAbnormalFlag PcbOvertemperature => (SinexcelAbnormalFlag)Bit(_pcbOvertemperature);
public SinexcelAbnormalFlag DcConverterOvertemperature => (SinexcelAbnormalFlag)Bit(_dcConverterOvertemperature);
public SinexcelAbnormalFlag BusSlowOvervoltage => (SinexcelAbnormalFlag)Bit(_busSlowOvervoltage);
public SinexcelAbnormalFlag AbnormalOffGridOutputVoltage => (SinexcelAbnormalFlag)Bit(_abnormalOffGridOutputVoltage);
public SinexcelAbnormalFlag HardwareBusOvervoltage => (SinexcelAbnormalFlag)Bit(_hardwareBusOvervoltage);
public SinexcelAbnormalFlag HardwareOvercurrent => (SinexcelAbnormalFlag)Bit(_hardwareOvercurrent);
public SinexcelAbnormalFlag DcConverterOvervoltage => (SinexcelAbnormalFlag)Bit(_dcConverterOvervoltage);
public SinexcelAbnormalFlag DcConverterHardwareOvervoltage => (SinexcelAbnormalFlag)Bit(_dcConverterHardwareOvervoltage);
public SinexcelAbnormalFlag DcConverterOvercurrent => (SinexcelAbnormalFlag)Bit(_dcConverterOvercurrent);
public SinexcelAbnormalFlag DcConverterHardwareOvercurrent => (SinexcelAbnormalFlag)Bit(_dcConverterHardwareOvercurrent);
public SinexcelAbnormalFlag DcConverterResonatorOvercurrent => (SinexcelAbnormalFlag)Bit(_dcConverterResonatorOvercurrent);
public SinexcelAbnormalFlag Pv1InsufficientPower => (SinexcelAbnormalFlag)Bit(_pv1InsufficientPower);
public SinexcelAbnormalFlag Pv2InsufficientPower => (SinexcelAbnormalFlag)Bit(_pv2InsufficientPower);
public SinexcelAbnormalFlag Battery1InsufficientPower => (SinexcelAbnormalFlag)Bit(_battery1InsufficientPower);
public SinexcelAbnormalFlag Battery2InsufficientPower => (SinexcelAbnormalFlag)Bit(_battery2InsufficientPower);
public SinexcelAbnormalFlag LithiumBattery1ChargeForbidden => (SinexcelAbnormalFlag)Bit(_lithiumBattery1ChargeForbidden);
public SinexcelAbnormalFlag LithiumBattery1DischargeForbidden => (SinexcelAbnormalFlag)Bit(_lithiumBattery1DischargeForbidden);
public SinexcelAbnormalFlag LithiumBattery2ChargeForbidden => (SinexcelAbnormalFlag)Bit(_lithiumBattery2ChargeForbidden);
public SinexcelAbnormalFlag LithiumBattery2DischargeForbidden => (SinexcelAbnormalFlag)Bit(_lithiumBattery2DischargeForbidden);
public SinexcelAbnormalFlag LithiumBattery1Full => (SinexcelAbnormalFlag)Bit(_lithiumBattery1Full);
public SinexcelAbnormalFlag LithiumBattery1DischargeEnd => (SinexcelAbnormalFlag)Bit(_lithiumBattery1DischargeEnd);
public SinexcelAbnormalFlag LithiumBattery2Full => (SinexcelAbnormalFlag)Bit(_lithiumBattery2Full);
public SinexcelAbnormalFlag LithiumBattery2DischargeEnd => (SinexcelAbnormalFlag)Bit(_lithiumBattery2DischargeEnd);
public SinexcelAbnormalFlag LoadPowerOverload => (SinexcelAbnormalFlag)Bit(_loadPowerOverload);
public SinexcelAbnormalFlag AbnormalLeakageSelfCheck => (SinexcelAbnormalFlag)Bit(_abnormalLeakageSelfCheck);
public SinexcelAbnormalFlag InverterOvertemperatureAlarm => (SinexcelAbnormalFlag)Bit(_inverterOvertemperatureAlarm);
public SinexcelAbnormalFlag InverterOvertemperature => (SinexcelAbnormalFlag)Bit(_inverterOvertemperature);
public SinexcelAbnormalFlag DcConverterOvertemperatureAlarm => (SinexcelAbnormalFlag)Bit(_dcConverterOvertemperatureAlarm);
public SinexcelAbnormalFlag ParallelCommunicationAlarm => (SinexcelAbnormalFlag)Bit(_parallelCommunicationAlarm);
public SinexcelAbnormalFlag SystemDerating => (SinexcelAbnormalFlag)Bit(_systemDerating);
public SinexcelAbnormalFlag InverterRelayOpen => (SinexcelAbnormalFlag)Bit(_inverterRelayOpen);
public SinexcelAbnormalFlag InverterRelayShortCircuit => (SinexcelAbnormalFlag)Bit(_inverterRelayShortCircuit);
public SinexcelAbnormalFlag PvAccessMethodErrorAlarm => (SinexcelAbnormalFlag)Bit(_pvAccessMethodErrorAlarm);
public SinexcelAbnormalFlag ParallelModuleMissing => (SinexcelAbnormalFlag)Bit(_parallelModuleMissing);
public SinexcelAbnormalFlag DuplicateMachineNumbersForParallelModules=> (SinexcelAbnormalFlag)Bit(_duplicateMachineNumbersForParallelModules);
public SinexcelAbnormalFlag ParameterConflictInParallelModule => (SinexcelAbnormalFlag)Bit(_parameterConflictInParallelModule);
public SinexcelAbnormalFlag ReservedAlarms4 => (SinexcelAbnormalFlag)Bit(_reservedAlarms4);
public SinexcelAbnormalFlag ReverseMeterConnection => (SinexcelAbnormalFlag)Bit(_reverseMeterConnection);
public SinexcelAbnormalFlag InverterSealPulse => (SinexcelAbnormalFlag)Bit(_inverterSealPulse);
public SinexcelAbnormalFlag Pv3NotConnected => (SinexcelAbnormalFlag)Bit(_pv3NotConnected);
public SinexcelAbnormalFlag Pv3Overvoltage => (SinexcelAbnormalFlag)Bit(_pv3Overvoltage);
public SinexcelAbnormalFlag Pv3AverageCurrentAnomaly => (SinexcelAbnormalFlag)Bit(_pv3AverageCurrentAnomaly);
public SinexcelAbnormalFlag Pv4NotConnected => (SinexcelAbnormalFlag)Bit(_pv4NotConnected);
public SinexcelAbnormalFlag Pv4Overvoltage => (SinexcelAbnormalFlag)Bit(_pv4Overvoltage);
public SinexcelAbnormalFlag Pv4AverageCurrentAnomaly => (SinexcelAbnormalFlag)Bit(_pv4AverageCurrentAnomaly);
public SinexcelAbnormalFlag Pv3PowerTubeFailure => (SinexcelAbnormalFlag)Bit(_pv3PowerTubeFailure);
public SinexcelAbnormalFlag Pv4PowerTubeFailure => (SinexcelAbnormalFlag)Bit(_pv4PowerTubeFailure);
public SinexcelAbnormalFlag Pv3SoftStartFailure => (SinexcelAbnormalFlag)Bit(_pv3SoftStartFailure);
public SinexcelAbnormalFlag Pv4SoftStartFailure => (SinexcelAbnormalFlag)Bit(_pv4SoftStartFailure);
public SinexcelAbnormalFlag Pv3OverloadTimeout => (SinexcelAbnormalFlag)Bit(_pv3OverloadTimeout);
public SinexcelAbnormalFlag Pv4OverloadTimeout => (SinexcelAbnormalFlag)Bit(_pv4OverloadTimeout);
public SinexcelAbnormalFlag Pv3ReverseConnection => (SinexcelAbnormalFlag)Bit(_pv3ReverseConnection);
public SinexcelAbnormalFlag Pv4ReverseConnection => (SinexcelAbnormalFlag)Bit(_pv4ReverseConnection);
public SinexcelAbnormalFlag AbnormalDieselGeneratorVoltage => (SinexcelAbnormalFlag)Bit(_abnormalDieselGeneratorVoltage);
public SinexcelAbnormalFlag AbnormalDieselGeneratorFrequency => (SinexcelAbnormalFlag)Bit(_abnormalDieselGeneratorFrequency);
public SinexcelAbnormalFlag DieselGeneratorVoltageReverseSequence => (SinexcelAbnormalFlag)Bit(_dieselGeneratorVoltageReverseSequence);
public SinexcelAbnormalFlag DieselGeneratorVoltageOutOfPhase => (SinexcelAbnormalFlag)Bit(_dieselGeneratorVoltageOutOfPhase);
public SinexcelAbnormalFlag LeadBatteryTemperatureAbnormality => (SinexcelAbnormalFlag)Bit(_leadBatteryTemperatureAbnormality);
public SinexcelAbnormalFlag BatteryAccessMethodError => (SinexcelAbnormalFlag)Bit(_batteryAccessMethodError);
public SinexcelAbnormalFlag ReservedAlarms5 => (SinexcelAbnormalFlag)Bit(_reservedAlarms5);
public SinexcelAbnormalFlag Battery1BackupProhibited => (SinexcelAbnormalFlag)Bit(_battery1BackupProhibited);
public SinexcelAbnormalFlag Battery2BackupProhibited => (SinexcelAbnormalFlag)Bit(_battery2BackupProhibited);
public SinexcelAbnormalFlag AbnormalGridCurrent => (SinexcelAbnormalFlag)Bit(_abnormalGridCurrent);
public SinexcelAbnormalFlag GeneratorOverload => (SinexcelAbnormalFlag)Bit(_generatorOverload);
public SinexcelAbnormalFlag BusSoftStartFailure => (SinexcelAbnormalFlag)Bit(_busSoftStartFailure);
public SinexcelAbnormalFlag OpenCircuitOfPowerGridRelay => (SinexcelAbnormalFlag)Bit(_openCircuitOfPowerGridRelay);
public SinexcelAbnormalFlag ShortCircuitOfPowerGridRelay => (SinexcelAbnormalFlag)Bit(_shortCircuitOfPowerGridRelay);
public SinexcelAbnormalFlag GeneratorRelayOpenCircuit => (SinexcelAbnormalFlag)Bit(_generatorRelayOpenCircuit);
public SinexcelAbnormalFlag GeneratorRelayShortCircuit => (SinexcelAbnormalFlag)Bit(_generatorRelayShortCircuit);
public SinexcelAbnormalFlag InsufficientPhotovoltaicPower => (SinexcelAbnormalFlag)Bit(_insufficientPhotovoltaicPower);
public SinexcelAbnormalFlag Photovoltaic1Overcurrent => (SinexcelAbnormalFlag)Bit(_photovoltaic1Overcurrent);
*/
private Int16 _factorFromKwtoW = 1000;
// ───────────────────────────────────────────────
// Public API — Decoded Float Values
@ -153,7 +339,7 @@ public partial class SinexcelRecord
public Voltage Battery1Voltage => ConvertBitPatternToFloat(_batteryVoltage1);
public Current Battery1Current => ConvertBitPatternToFloat(_batteryCurrent1);
public ActivePower Battery1Power => ConvertBitPatternToFloat(_batteryPower1) * _factorFromKwtoW;
public ActivePower Battery1Power => ConvertBitPatternToFloat(_batteryPower1) * _factorFromKwtoW;
public Percent Battery1Soc => ConvertBitPatternToFloat(_batterySoc1);
public float BatteryFullLoadDuration1 => ConvertBitPatternToFloat(_batteryFullLoadDuration1); // this is in hour
@ -190,14 +376,15 @@ public partial class SinexcelRecord
public ActivePower TotalGridPower => ConvertBitPatternToFloat(_totalGridPower) * _factorFromKwtoW ;
public ActivePower ImportantLoadTotalPower => ConvertBitPatternToFloat(_importantLoadTotalPower)* _factorFromKwtoW;
public ActivePower GeneralLoadTotalPower => ConvertBitPatternToFloat(_generalLoadTotalPower)* _factorFromKwtoW;
public Voltage Pv3Voltage => ConvertBitPatternToFloat(_pv3Voltage);
public Current Pv3Current => ConvertBitPatternToFloat(_pv3Current);
public ActivePower Pv3Power => ConvertBitPatternToFloat(_pv3Power) * _factorFromKwtoW;
public Voltage Pv4Voltage => ConvertBitPatternToFloat(_pv4Voltage);
public Current Pv4Current => ConvertBitPatternToFloat(_pv4Current);
public ActivePower Pv4Power => ConvertBitPatternToFloat(_pv4Power) * _factorFromKwtoW;
public Voltage PvVoltage3 => ConvertBitPatternToFloat(_pv3Voltage);
public Current PvCurrent3 => ConvertBitPatternToFloat(_pv3Current);
public ActivePower PvPower3 => ConvertBitPatternToFloat(_pv3Power) * _factorFromKwtoW;
public Voltage PvVoltage4 => ConvertBitPatternToFloat(_pv4Voltage);
public Current PvCurrent4 => ConvertBitPatternToFloat(_pv4Current);
public ActivePower PvPower4 => ConvertBitPatternToFloat(_pv4Power) * _factorFromKwtoW;
public ActivePower GeneratorTotalPower => ConvertBitPatternToFloat(_generatorTotalPower);
public ActivePower PvTotalPower => PvPower1 + PvPower2 + PvPower3 + PvPower4;
// ───────────────────────────────────────────────
// Manufacturer Information & Software Versions
// ───────────────────────────────────────────────
@ -215,269 +402,292 @@ public partial class SinexcelRecord
public ApparentPower RatedPowerKva => _ratedPower / 100f; // 0x2008 (value / 100)
public SinexcelModel Model => (SinexcelModel)_model; // 0x200D
//public ThreePhaseWireSystem ThreePhaseWireSystem
//{
// get => (ThreePhaseWireSystem)_threePhaseWireSystem;
// //set => _threePhaseWireSystem = (UInt32)value;
//}
public ThreePhaseWireSystem ThreePhaseWireSystem
{
get => (ThreePhaseWireSystem)_threePhaseWireSystem;
set => _threePhaseWireSystem = (UInt32)value;
}
//
//public InputFrequency InputFrequencyClass
//{
// get => (InputFrequency)_inputFrequencyClass;
// //set => _inputFrequencyClass = (UInt32)value;
//}
public InputFrequency InputFrequencyClass
{
get => (InputFrequency)_inputFrequencyClass;
set => _inputFrequencyClass = (UInt32)value;
}
//
public WorkingMode WorkingMode
{
get => (WorkingMode)(int)BitConverter.Int32BitsToSingle(unchecked((int)_workingMode));
set => _workingMode = BitConverter.ToUInt32(BitConverter.GetBytes((float)value), 0);
get => (WorkingMode)(Int32)BitConverter.Int32BitsToSingle(unchecked((Int32)_workingMode));
set => _workingMode = BitConverter.ToUInt32(BitConverter.GetBytes((Single)value), 0);
}
public GridSwitchMethod GridSwitchMethod
{
get => (GridSwitchMethod)(Int32)BitConverter.Int32BitsToSingle(unchecked((Int32)_methodSwitchMode));
set => _methodSwitchMode = BitConverter.ToUInt32(BitConverter.GetBytes((Single)value), 0);
}
// ───────────────────────────────────────────────
// Inverter Control Configuration
// ───────────────────────────────────────────────
public SinexcelUnbalanceCompensation EnableOnGridUnbalanceCompensation
{
get => (SinexcelUnbalanceCompensation)_enableOnGridUnbalanceCompensation;
set => _enableOnGridUnbalanceCompensation = (UInt32)value;
}
/*
public SinexcelTemperatureDrop TemperatureDrop
{
get => (SinexcelTemperatureDrop)ConvertBitPatternToFloat(_temperatureDrop);
//set => _temperatureDrop = (UInt32)value;
}
//
//public GridSwitchMethod GridSwitchMethod
//{
// get => (GridSwitchMethod)_methodSwitchMode;
// //set => _methodSwitchMode = (UInt32)value;
//}
public SinexcelHvrt Hvrt
{
get => (SinexcelHvrt)_hvrt;
// set => _hvrt = (UInt32)value;
}
//
//// ───────────────────────────────────────────────
//// Inverter Control Configuration
//// ───────────────────────────────────────────────
//public SinexcelUnbalanceCompensation EnableOnGridUnbalanceCompensation
//{
// get => (SinexcelUnbalanceCompensation)_enableOnGridUnbalanceCompensation;
// //set => _enableOnGridUnbalanceCompensation = (UInt32)value;
//}
//
//public SinexcelTemperatureDrop TemperatureDrop
//{
// get => (SinexcelTemperatureDrop)ConvertBitPatternToFloat(_temperatureDrop);
// //set => _temperatureDrop = (UInt32)value;
//}
//
//public SinexcelHvrt Hvrt
//{
// get => (SinexcelHvrt)_hvrt;
// //set => _hvrt = (UInt32)value;
//}
//
//public SinexcelLvrt Lvrt
//{
// get => (SinexcelLvrt)_lvrt;
// //set => _lvrt = (UInt32)value;
//}
public SinexcelLvrt Lvrt
{
get => (SinexcelLvrt)_lvrt;
// set => _lvrt = (UInt32)value;
}
//
// F//an Gear — numeric [0.5 ~ 1.0], default 1.0
//public float FanGear
//{
// get => BitConverter.Int32BitsToSingle(unchecked((Int32)_fanGear));
// //set => _fanGear = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
public float FanGear
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_fanGear));
// set => _fanGear = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
*/
//// ───────────────────────────────────────────────
//// Battery Configuration
//// ───────────────────────────────────────────────
//public SinexcelBatteryAccessMethod BatteryAccessMethod
//{
// get => (SinexcelBatteryAccessMethod)_batteryAccessMethod;
// //set => _batteryAccessMethod = (UInt32)value;
//}
//
//public SinexcelMeterAccessEnable MeterAccessEnable
//{
// get => (SinexcelMeterAccessEnable)ConvertBitPatternToFloat(_meterAccessEnable);
// //set => _meterAccessEnable = (UInt32)value;
//}
//
//public SinexcelBatteryEnable EnableBattery1
//{
// get => (SinexcelBatteryEnable)ConvertBitPatternToFloat(_enableBattery1);
// //set => _enableBattery1 = (UInt32)value;
//}
//
//public SinexcelBatteryEnable EnableBattery2
//{
// get => (SinexcelBatteryEnable)ConvertBitPatternToFloat(_enableBattery2);
// //set => _enableBattery2 = (UInt32)value;
//}
//
//public SinexcelPvEnable EnablePv1
//{
// get => (SinexcelPvEnable)ConvertBitPatternToFloat(_enablePv1);
// //set => _enablePv1 = (UInt32)value;
//}
//
//public SinexcelPvEnable EnablePv2
//{
// get => (SinexcelPvEnable)ConvertBitPatternToFloat(_enablePv2);
// //set => _enablePv2 = (UInt32)value;
//}
//
//public SinexcelBatteryType BatteryType
//{
// get => (SinexcelBatteryType)_batteryType;
// //set => _batteryType = (UInt32)value;
//}
//
//public float BatteryCapacity1
//{
// get => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCapacity1));
// //set => _batteryCapacity1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float Battery1MaxChargingCurrent
//{
// get => BitConverter.Int32BitsToSingle(unchecked((Int32)_maxChargingCurrentBattery1));
// //set => _maxChargingCurrentBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float Battery1MaxDischargingCurrent
//{
// get => BitConverter.Int32BitsToSingle(unchecked((Int32)_maxDischargingCurrentBattery1));
// //set => _maxDischargingCurrentBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float RatedBatteryVoltage1
//{
// get => BitConverter.Int32BitsToSingle(unchecked((Int32)_ratedBatteryVoltage1));
// //set => _ratedBatteryVoltage1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float Battery1MinSoc
//{
// get => BitConverter.Int32BitsToSingle(unchecked((Int32)_minSocBattery1));
// //set => _minSocBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float SetValueBattery1
//{
// get => BitConverter.Int32BitsToSingle(unchecked((Int32)_setValueBattery1));
// //set => _setValueBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public SinexcelActiveChargeDischarge ActiveChargeDischarge
//{
// get => (SinexcelActiveChargeDischarge)ConvertBitPatternToFloat(_activeChargeDischarge);
// //set => _activeChargeDischarge = (UInt32)value;
//}
//
//public float ActiveChargeDischargePower
//{
// get => ConvertBitPatternToFloat(_activeChargeDischargePower) * _factorFromKwtoW;
// //set => _activeChargeDischargePower = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); // we should check this may need to convert from W to KW
//}
//
//// ───────────────────────────────────────────────
//// Protection & PV Mode
//// ───────────────────────────────────────────────
//public SinexcelIslandProtection EnableIslandProtection
//{
// get => (SinexcelIslandProtection)_enableIslandProtection;
// //set => _enableIslandProtection = (UInt32)value;
//}
//
//public SinexcelPvAccessMode PvAccessMode
//{
// get => (SinexcelPvAccessMode)_pvAccessMode;
// //set => _pvAccessMode = (UInt32)value;
//}
//
//// ───────────────────────────────────────────────
//// System-Level Parameters
//// ───────────────────────────────────────────────
//public float OutputVoltageAdjustmentFactor
//{
// get => BitConverter.Int32BitsToSingle(unchecked((int)_outputVoltageAdjustmentFactor));
// //set => _outputVoltageAdjustmentFactor = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float SetValueBatteryUndervoltage1
//{
// get => BitConverter.Int32BitsToSingle(unchecked((int)_setValueBatteryUndervoltage1));
// //set => _setValueBatteryUndervoltage1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float InverterPowerLimit
//{
// get => BitConverter.Int32BitsToSingle(unchecked((int)_inverterPowerLimit));
// //set => _inverterPowerLimit = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//// ───────────────────────────────────────────────
//// Battery 2 Parameters
//// ───────────────────────────────────────────────
//public float Battery2Capacity
//{
// get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2Capacity));
// //set => _battery2Capacity = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float Battery2MaxChargingCurrent
//{
// get => BitConverter.Int32BitsToSingle(unchecked((int)_maxChargingCurrentBattery2));
// //set => _maxChargingCurrentBattery2 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float Battery2MaxDischargingCurrent
//{
// get => BitConverter.Int32BitsToSingle(unchecked((int)_maxDischargingCurrentBattery2));
// //set => _maxDischargingCurrentBattery2 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float Battery2RatedVoltage
//{
// get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2RatedVoltage));
// //set => _battery2RatedVoltage = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float Battery2MinSoc
//{
// get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2MinSoc));
// //set => _battery2MinSoc = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float Battery2OverVoltageSetting
//{
// get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2OverVoltageSetting));
// //set => _battery2OverVoltageSetting = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float Battery2UnderVoltageSetpoint
//{
// get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2UnderVoltageSetpoint));
// //set => _battery2UnderVoltageSetpoint = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//// ───────────────────────────────────────────────
//// Parallel / System Settings
//// ───────────────────────────────────────────────
//public SinexcelMachineMode MachineMode
//{
// get => (SinexcelMachineMode)ConvertBitPatternToFloat(_singleOrParallelMachine);
// //set => _singleOrParallelMachine = (UInt32)value;
//}
//
//public UInt32 NumberOfSystemModules
//{
// get => (UInt32)ConvertBitPatternToFloat(_numberOfSystemModules);
// //set => _numberOfSystemModules = value;
//}
//
//public UInt32 ParallelModuleMachineNumber
//{
// get => (UInt32)ConvertBitPatternToFloat(_parallelModuleMachineNumber);
// //set => _parallelModuleMachineNumber = value;
//}
//
//public AccreditedCountry AccreditedCountry
//{
// get => (AccreditedCountry)ConvertBitPatternToFloat(_accreditedCountries);
// //set => _accreditedCountries = (UInt32)value;
//}
//
//// ───────────────────────────────────────────────
//// Control Commands
//// ───────────────────────────────────────────────
public SinexcelBatteryAccessMethod BatteryAccessMethod
{
get => (SinexcelBatteryAccessMethod)_batteryAccessMethod;
set => _batteryAccessMethod = (UInt32)value;
}
public SinexcelMeterAccessEnable MeterAccessEnable
{
get => (SinexcelMeterAccessEnable)ConvertBitPatternToFloat(_meterAccessEnable);
set => _meterAccessEnable = (UInt32)value;
}
public SinexcelBatteryEnable EnableBattery1
{
get => (SinexcelBatteryEnable)ConvertBitPatternToFloat(_enableBattery1);
set => _enableBattery1 = (UInt32)value;
}
public SinexcelBatteryEnable EnableBattery2
{
get => (SinexcelBatteryEnable)ConvertBitPatternToFloat(_enableBattery2);
set => _enableBattery2 = (UInt32)value;
}
public SinexcelPvEnable EnablePv1
{
get => (SinexcelPvEnable)ConvertBitPatternToFloat(_enablePv1);
set => _enablePv1 = (UInt32)value;
}
public SinexcelPvEnable EnablePv2
{
get => (SinexcelPvEnable)ConvertBitPatternToFloat(_enablePv2);
set => _enablePv2 = (UInt32)value;
}
public SinexcelBatteryType BatteryType
{
get => (SinexcelBatteryType)_batteryType;
set => _batteryType = (UInt32)value;
}
public float BatteryCapacity1
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCapacity1));
set => _batteryCapacity1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float Battery1MaxChargingCurrent
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_maxChargingCurrentBattery1));
set => _maxChargingCurrentBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float Battery1MaxDischargingCurrent
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_maxDischargingCurrentBattery1));
set => _maxDischargingCurrentBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float RatedBatteryVoltage1
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_ratedBatteryVoltage1));
set => _ratedBatteryVoltage1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float Battery1MinSoc
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_minSocBattery1));
set => _minSocBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float SetValueBattery1
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_setValueBattery1));
set => _setValueBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
/* public SinexcelActiveChargeDischarge ActiveChargeDischarge
{
get => (SinexcelActiveChargeDischarge)(Int32)BitConverter.Int32BitsToSingle(unchecked((Int32)_activeChargeDischarge));
set => _activeChargeDischarge = BitConverter.ToUInt32(BitConverter.GetBytes((Single)value), 0);
}*/
public float ActiveChargeDischargePower
{
get => ConvertBitPatternToFloat(_activeChargeDischargePower) * _factorFromKwtoW;
set => _activeChargeDischargePower = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0); // we should check this may need to convert from W to KW
}
// ───────────────────────────────────────────────
// Protection & PV Mode
// ───────────────────────────────────────────────
public SinexcelIslandProtection EnableIslandProtection
{
get => (SinexcelIslandProtection)_enableIslandProtection;
set => _enableIslandProtection = (UInt32)value;
}
public SinexcelPvAccessMode PvAccessMode
{
get => (SinexcelPvAccessMode)_pvAccessMode;
set => _pvAccessMode = (UInt32)value;
}
// ───────────────────────────────────────────────
// System-Level Parameters
// ───────────────────────────────────────────────
public float OutputVoltageAdjustmentFactor
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_outputVoltageAdjustmentFactor));
set => _outputVoltageAdjustmentFactor = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float SetValueBatteryUndervoltage1
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_setValueBatteryUndervoltage1));
set => _setValueBatteryUndervoltage1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float InverterPowerLimit
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_inverterPowerLimit));
set => _inverterPowerLimit = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
// ───────────────────────────────────────────────
// Battery 2 Parameters
// ───────────────────────────────────────────────
public float Battery2Capacity
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2Capacity));
set => _battery2Capacity = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float Battery2MaxChargingCurrent
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_maxChargingCurrentBattery2));
set => _maxChargingCurrentBattery2 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float Battery2MaxDischargingCurrent
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_maxDischargingCurrentBattery2));
set => _maxDischargingCurrentBattery2 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float Battery2RatedVoltage
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2RatedVoltage));
set => _battery2RatedVoltage = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float Battery2MinSoc
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2MinSoc));
set => _battery2MinSoc = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float Battery2OverVoltageSetting
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2OverVoltageSetting));
set => _battery2OverVoltageSetting = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float Battery2UnderVoltageSetpoint
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2UnderVoltageSetpoint));
set => _battery2UnderVoltageSetpoint = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float Battery1BackupSoc
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_battery1BackupSOC));
set => _battery1BackupSOC = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float Battery2BackupSoc
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_battery2BackupSOC));
set => _battery2BackupSOC = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
// to be tested
public float EnableGridExport
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_enableGridExport));
set => _enableGridExport = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float PowerGridExportLimit
{
get => BitConverter.Int32BitsToSingle(unchecked((int)_powerGridExportLimit));
set => _powerGridExportLimit = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
// ───────────────────────────────────────────────
// Parallel / System Settings
// ───────────────────────────────────────────────
public SinexcelMachineMode MachineMode
{
get => (SinexcelMachineMode)ConvertBitPatternToFloat(_singleOrParallelMachine);
set => _singleOrParallelMachine = (UInt32)value;
}
public UInt32 NumberOfSystemModules
{
get => (UInt32)ConvertBitPatternToFloat(_numberOfSystemModules);
set => _numberOfSystemModules = value;
}
public UInt32 ParallelModuleMachineNumber
{
get => (UInt32)ConvertBitPatternToFloat(_parallelModuleMachineNumber);
set => _parallelModuleMachineNumber = value;
}
public AccreditedCountry AccreditedCountry
{
get => (AccreditedCountry)(Int32)BitConverter.Int32BitsToSingle(unchecked((Int32)_accreditedCountries));
set => _accreditedCountries = BitConverter.ToUInt32(BitConverter.GetBytes((Single)value), 0);
}
// ───────────────────────────────────────────────
// Control Commands
// ───────────────────────────────────────────────
public float PowerOn
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_powerOn));
@ -489,39 +699,40 @@ public partial class SinexcelRecord
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_powerOff));
set => _powerOff = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
public float FaultClearing
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_faultClearing));
set => _faultClearing = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
//
//public float FaultClearingf
//{
// get => BitConverter.Int32BitsToSingle(unchecked((Int32)_faultClearing));
// // //set => _faultClearing = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
// ───────────────────────────────────────────────
// Meter & Battery Control
// ───────────────────────────────────────────────
public float MeterReverseManualDetection
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_meterReverseManualDetection));
set => _meterReverseManualDetection = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
// This causing the inverter to switch off, please consider testing it first before uncommenting
/*public SinexcelBatteryRating SinexcelBatteryRating
{
get => (SinexcelBatteryRating)(Int32)BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryVoltageRating));
set => _batteryVoltageRating = BitConverter.ToUInt32(BitConverter.GetBytes((Single)value), 0);
}*/
public float Battery1Activation
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_battery1Activation));
set => _battery1Activation = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
//
//// ───────────────────────────────────────────────
//// Meter & Battery Control
//// ───────────────────────────────────────────────
//public float MeterReverseManualDetection
//{
// get => BitConverter.Int32BitsToSingle(unchecked((Int32)_meterReverseManualDetection));
// //set => _meterReverseManualDetection = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public SinexcelBatteryRating BatteryVoltageRating
//{
// get => (SinexcelBatteryRating)ConvertBitPatternToFloat(_batteryVoltageRating);
// //set => _batteryVoltageRating = (UInt32)value;
//}
//
//public float Battery1Activation
//{
// get => BitConverter.Int32BitsToSingle(unchecked((Int32)_battery1Activation));
// //set => _battery1Activation = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
//
//public float Battery2Activation
//{
// get => BitConverter.Int32BitsToSingle(unchecked((Int32)_battery2Activation));
// //set => _battery2Activation = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
public float Battery2Activation
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_battery2Activation));
set => _battery2Activation = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
}
// ───────────────────────────────────────────────
// Electric Meter Operating State
@ -609,44 +820,30 @@ public partial class SinexcelRecord
// Pack Voltage / Current / Temperature
public Voltage Battery2PackTotalVoltage => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2PackTotalVoltage)); // 0xB200 (0.01 V resolution)
public Current Battery2PackTotalCurrent => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2PackTotalCurrent)); // 0xB202 (0.01 A resolution)
public Temperature Battery2Socsecondvalue => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2Soc)); // 0xB206 %
public Percent Battery2Socsecondvalue => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2Soc)); // 0xB206 %
public Percent Battery2Soh => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2Soh)); // 0xB208 %
// Repetitive-week mask (bit-mapped 06 = SunSat)
//public float TimedChargeAndDischargeOff
//{
// get => BitConverter.Int32BitsToSingle(unchecked((Int32)_Timed_Charge_and_Discharge_Off)) ; // only 7 bits used
// //set => _Timed_Charge_and_Discharge_Off = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
//}
// Repetitive-week mask (bit-mapped 06 = SunSat)
/* public float OtherTimePeriodMode
{
get => _Other_time_period_mode ; // only 7 bits used
//set => _Other_time_period_mode = (UInt32)value;
}*/
// Repetitive-week mask (bit-mapped 06 = SunSat)
public SinexcelWeekDays RepetitiveWeeks
{
get => (SinexcelWeekDays)(_repetitiveWeeks & 0x7F); // only 7 bits used
set => _repetitiveWeeks = (UInt32)value;
}
//
// Effective start / end as UNIX timestamps
public DateTime EffectiveStartDate
{
get => DateTimeOffset.FromUnixTimeSeconds(_effectiveStartDate).DateTime;
set => _effectiveStartDate = (UInt32)new DateTimeOffset(value).ToUnixTimeSeconds();
set => _effectiveStartDate = (UInt32)new DateTimeOffset(value).ToUnixTimeSeconds();
}
//
public DateTime EffectiveEndDate
{
get => DateTimeOffset.FromUnixTimeSeconds(_effectiveEndDate).DateTime;
set => _effectiveEndDate = (UInt32)new DateTimeOffset(value).ToUnixTimeSeconds();
}
// // Charging power during time period 1 (kW)
// Charging power during time period 1 (kW)
public float ChargingPowerPeriod1
{
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_chargingPowerPeriod1));

View File

@ -11,7 +11,178 @@ namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL;
[BigEndian]
public partial class SinexcelRecord
{
/****************************** Input registers ****************************/
/*
// ========================================================
// Register declarations
// ========================================================
// to test by block first
// ---- Operating states (0x10000x1014)
[HoldingRegister<UInt16>(4096, writable: false)] private UInt16 _notUsedRemain; // 0x1000
[HoldingRegister<UInt16>(4098, writable: false)] private UInt16 _inverterOperatingState; // 0x1002 (3 bits)
[HoldingRegister<UInt16>(4100, writable: false)] private UInt16 _pv1OperatingState; // 0x1004 (1 bit)
[HoldingRegister<UInt16>(4101, writable: false)] private UInt16 _pv2OperatingState; // 0x1005 (1 bit)
[HoldingRegister<UInt16>(4103, writable: false)] private UInt16 _battery1OperatingState; // 0x1007 (3 bits)
// Battery 2 operating state bits (0x10090x100B)
[HoldingRegister<UInt16>(4105, writable: false)] private UInt16 _bat2StateBit0; // 0x1009
[HoldingRegister<UInt16>(4106, writable: false)] private UInt16 _bat2StateBit1; // 0x100A
[HoldingRegister<UInt16>(4107, writable: false)] private UInt16 _bat2StateBit2; // 0x100B
// ORED state bits (0x100C0x100F)
[HoldingRegister<UInt16>(4108, writable: false)] private UInt16 _oredStateBit0; // 0x100C
[HoldingRegister<UInt16>(4109, writable: false)] private UInt16 _oredStateBit1; // 0x100D
[HoldingRegister<UInt16>(4110, writable: false)] private UInt16 _oredStateBit2; // 0x100E
[HoldingRegister<UInt16>(4111, writable: false)] private UInt16 _oredStateBit3; // 0x100F
// DCDC state (separate bits for DC/AC variants)
[HoldingRegister<UInt16>(4112, writable: false)] private UInt16 _dcdcStateDc; // 0x1010
[HoldingRegister<UInt16>(4113, writable: false)] private UInt16 _dcdcStateAc; // 0x1011
// PV3 / PV4 operational state
[HoldingRegister<UInt16>(4114, writable: false)] private UInt16 _pv3OperatingState; // 0x1012
[HoldingRegister<UInt16>(4115, writable: false)] private UInt16 _pv4OperatingState; // 0x1013
// Power grid status
[HoldingRegister<UInt16>(4116, writable: false)] private UInt16 _powerGridStatus; // 0x1014
// ---- Alarm / fault flags (0x10480x10CE) (0: Normal, 1: Abnormal)
[HoldingRegister<UInt16>(4168, writable: false)] private UInt16 _abnormalGridVoltage; // 0x1048 Abnormal grid voltage
[HoldingRegister<UInt16>(4169, writable: false)] private UInt16 _abnormalGridFrequency; // 0x1049 Abnormal grid frequency
[HoldingRegister<UInt16>(4170, writable: false)] private UInt16 _invertedSequenceOfGridVoltage; // 0x104A Inverted sequence of grid voltage
[HoldingRegister<UInt16>(4171, writable: false)] private UInt16 _gridVoltagePhaseLoss; // 0x104B Grid voltage phase loss
[HoldingRegister<UInt16>(4172, writable: false)] private UInt16 _abnormalOutputVoltage; // 0x104C Abnormal output voltage
[HoldingRegister<UInt16>(4173, writable: false)] private UInt16 _abnormalOutputFrequency; // 0x104D Abnormal output frequency
[HoldingRegister<UInt16>(4174, writable: false)] private UInt16 _abnormalNullLine; // 0x104E Abnormal null line
[HoldingRegister<UInt16>(4175, writable: false)] private UInt16 _excessivelyHighAmbientTemperature; // 0x104F Excessively high ambient temperature
[HoldingRegister<UInt16>(4176, writable: false)] private UInt16 _excessiveRadiatorTemperature; // 0x1050 Excessive radiator temperature
[HoldingRegister<UInt16>(4177, writable: false)] private UInt16 _insulationFault; // 0x1051 Insulation fault
[HoldingRegister<UInt16>(4178, writable: false)] private UInt16 _leakageProtectionFault; // 0x1052 Leakage protection fault
[HoldingRegister<UInt16>(4179, writable: false)] private UInt16 _auxiliaryPowerFault; // 0x1053 Auxiliary power fault
[HoldingRegister<UInt16>(4180, writable: false)] private UInt16 _fanFault; // 0x1054 Fan fault
[HoldingRegister<UInt16>(4181, writable: false)] private UInt16 _modelCapacityFault; // 0x1055 Model capacity fault
[HoldingRegister<UInt16>(4182, writable: false)] private UInt16 _abnormalLightningArrester; // 0x1056 Abnormal lightning arrester
[HoldingRegister<UInt16>(4183, writable: false)] private UInt16 _islandProtection; // 0x1057 Island protection
[HoldingRegister<UInt16>(4184, writable: false)] private UInt16 _battery1NotConnected; // 0x1058 Battery 1 not connected
[HoldingRegister<UInt16>(4185, writable: false)] private UInt16 _battery1Overvoltage; // 0x1059 Battery 1 overvoltage
[HoldingRegister<UInt16>(4186, writable: false)] private UInt16 _battery1Undervoltage; // 0x105A Battery 1 undervoltage
[HoldingRegister<UInt16>(4187, writable: false)] private UInt16 _battery1DischargeEnd; // 0x105B Battery 1 discharge end
[HoldingRegister<UInt16>(4188, writable: false)] private UInt16 _battery1Inverted; // 0x105C Battery 1 inverted
[HoldingRegister<UInt16>(4189, writable: false)] private UInt16 _battery2NotConnected; // 0x105D Battery 2 not connected
[HoldingRegister<UInt16>(4190, writable: false)] private UInt16 _battery2Overvoltage; // 0x105E Battery 2 overvoltage
[HoldingRegister<UInt16>(4191, writable: false)] private UInt16 _battery2Undervoltage; // 0x105F Battery 2 undervoltage
[HoldingRegister<UInt16>(4192, writable: false)] private UInt16 _battery2DischargeEnd; // 0x1060 Battery 2 discharge end
[HoldingRegister<UInt16>(4193, writable: false)] private UInt16 _battery2Inverted; // 0x1061 Battery 2 inverted
[HoldingRegister<UInt16>(4194, writable: false)] private UInt16 _pv1NotAccessed; // 0x1062 PV1 not accessed
[HoldingRegister<UInt16>(4195, writable: false)] private UInt16 _pv1Overvoltage; // 0x1063 PV1 overvoltage
[HoldingRegister<UInt16>(4196, writable: false)] private UInt16 _abnormalPv1CurrentSharing; // 0x1064 Abnormal PV1 current sharing
[HoldingRegister<UInt16>(4197, writable: false)] private UInt16 _pv2NotAccessed; // 0x1065 PV2 not accessed
[HoldingRegister<UInt16>(4198, writable: false)] private UInt16 _pv2Overvoltage; // 0x1066 PV2 overvoltage
[HoldingRegister<UInt16>(4199, writable: false)] private UInt16 _abnormalPv2CurrentSharing; // 0x1067 Abnormal PV2 current sharing
[HoldingRegister<UInt16>(4200, writable: false)] private UInt16 _dcBusOvervoltage; // 0x1068 DC bus overvoltage
[HoldingRegister<UInt16>(4201, writable: false)] private UInt16 _dcBusUndervoltage; // 0x1069 DC bus undervoltage
[HoldingRegister<UInt16>(4202, writable: false)] private UInt16 _dcBusVoltageUnbalance; // 0x106A DC bus voltage unbalance
[HoldingRegister<UInt16>(4203, writable: false)] private UInt16 _pv1PowerTubeFault; // 0x106B PV1 power tube fault
[HoldingRegister<UInt16>(4204, writable: false)] private UInt16 _pv2PowerTubeFault; // 0x106C PV2 power tube fault
[HoldingRegister<UInt16>(4205, writable: false)] private UInt16 _battery1PowerTubeFault; // 0x106D Battery 1 power tube fault
[HoldingRegister<UInt16>(4206, writable: false)] private UInt16 _battery2PowerTubeFault; // 0x106E Battery 2 power tube fault
[HoldingRegister<UInt16>(4207, writable: false)] private UInt16 _inverterPowerTubeFault; // 0x106F Inverter power tube fault
[HoldingRegister<UInt16>(4208, writable: false)] private UInt16 _systemOutputOverload; // 0x1070 System output overload
[HoldingRegister<UInt16>(4209, writable: false)] private UInt16 _inverterOverload; // 0x1071 Inverter overload
[HoldingRegister<UInt16>(4210, writable: false)] private UInt16 _inverterOverloadTimeout; // 0x1072 Inverter overload timeout
[HoldingRegister<UInt16>(4211, writable: false)] private UInt16 _battery1OverloadTimeout; // 0x1073 Battery 1 overload timeout
[HoldingRegister<UInt16>(4212, writable: false)] private UInt16 _battery2OverloadTimeout; // 0x1074 Battery 2 overload timeout
[HoldingRegister<UInt16>(4213, writable: false)] private UInt16 _inverterSoftStartFailure; // 0x1075 Inverter soft start failure
[HoldingRegister<UInt16>(4214, writable: false)] private UInt16 _battery1SoftStartFailure; // 0x1076 Battery 1 soft start failure
[HoldingRegister<UInt16>(4215, writable: false)] private UInt16 _battery2SoftStartFailure; // 0x1077 Battery 2 soft start failure
[HoldingRegister<UInt16>(4216, writable: false)] private UInt16 _dsp1ParameterSettingFault; // 0x1078 DSP1 parameter setting fault
[HoldingRegister<UInt16>(4217, writable: false)] private UInt16 _dsp2ParameterSettingFault; // 0x1079 DSP2 parameter setting fault
[HoldingRegister<UInt16>(4218, writable: false)] private UInt16 _dspVersionCompatibilityFault; // 0x107A DSP version compatibility fault
[HoldingRegister<UInt16>(4219, writable: false)] private UInt16 _cpldVersionCompatibilityFault; // 0x107B CPLD version compatibility fault
[HoldingRegister<UInt16>(4220, writable: false)] private UInt16 _cpldCommunicationFault; // 0x107C CPLD communication fault
[HoldingRegister<UInt16>(4221, writable: false)] private UInt16 _dspCommunicationFault; // 0x107D DSP communication fault
[HoldingRegister<UInt16>(4222, writable: false)] private UInt16 _outputVoltageDcOverlimit; // 0x107E Output voltage DC overlimit
[HoldingRegister<UInt16>(4223, writable: false)] private UInt16 _outputCurrentDcOverlimit; // 0x107F Output current DC overlimit
[HoldingRegister<UInt16>(4224, writable: false)] private UInt16 _relaySelfCheckFails; // 0x1080 Relay self-check fails
[HoldingRegister<UInt16>(4225, writable: false)] private UInt16 _abnormalInverter; // 0x1081 Abnormal inverter
[HoldingRegister<UInt16>(4226, writable: false)] private UInt16 _poorGrounding; // 0x1082 Poor grounding
[HoldingRegister<UInt16>(4227, writable: false)] private UInt16 _pv1SoftStartFailure; // 0x1083 PV1 soft start failure
[HoldingRegister<UInt16>(4228, writable: false)] private UInt16 _pv2SoftStartFailure2; // 0x1084 PV2 soft start failure
[HoldingRegister<UInt16>(4229, writable: false)] private UInt16 _balancedCircuitOverloadTimeout; // 0x1085 Balanced circuit overload timeout
[HoldingRegister<UInt16>(4230, writable: false)] private UInt16 _pv1OverloadTimeout; // 0x1086 PV1 overload timeout
[HoldingRegister<UInt16>(4231, writable: false)] private UInt16 _pv2OverloadTimeout; // 0x1087 PV2 overload timeout
[HoldingRegister<UInt16>(4232, writable: false)] private UInt16 _pcbOvertemperature; // 0x1088 PCB overtemperature
[HoldingRegister<UInt16>(4233, writable: false)] private UInt16 _dcConverterOvertemperature; // 0x1089 DC converter overtemperature
[HoldingRegister<UInt16>(4234, writable: false)] private UInt16 _busSlowOvervoltage; // 0x108A Bus slow overvoltage
[HoldingRegister<UInt16>(4235, writable: false)] private UInt16 _abnormalOffGridOutputVoltage; // 0x108B Abnormal off-grid output voltage
[HoldingRegister<UInt16>(4236, writable: false)] private UInt16 _hardwareBusOvervoltage; // 0x108C Hardware bus overvoltage
[HoldingRegister<UInt16>(4237, writable: false)] private UInt16 _hardwareOvercurrent; // 0x108D Hardware overcurrent
[HoldingRegister<UInt16>(4238, writable: false)] private UInt16 _dcConverterOvervoltage; // 0x108E DC converter overvoltage
[HoldingRegister<UInt16>(4239, writable: false)] private UInt16 _dcConverterHardwareOvervoltage; // 0x108F DC converter hardware overvoltage
[HoldingRegister<UInt16>(4240, writable: false)] private UInt16 _dcConverterOvercurrent; // 0x1090 DC converter overcurrent
[HoldingRegister<UInt16>(4241, writable: false)] private UInt16 _dcConverterHardwareOvercurrent; // 0x1091 DC converter hardware overcurrent
[HoldingRegister<UInt16>(4242, writable: false)] private UInt16 _dcConverterResonatorOvercurrent; // 0x1092 DC converter resonator overcurrent
[HoldingRegister<UInt16>(4243, writable: false)] private UInt16 _pv1InsufficientPower; // 0x1093 PV1 insufficient power
[HoldingRegister<UInt16>(4244, writable: false)] private UInt16 _pv2InsufficientPower; // 0x1094 PV2 insufficient power
[HoldingRegister<UInt16>(4245, writable: false)] private UInt16 _battery1InsufficientPower; // 0x1095 Battery 1 insufficient power
[HoldingRegister<UInt16>(4246, writable: false)] private UInt16 _battery2InsufficientPower; // 0x1096 Battery 2 insufficient power
[HoldingRegister<UInt16>(4247, writable: false)] private UInt16 _lithiumBattery1ChargeForbidden; // 0x1097 Lithium battery 1 charge forbidden
[HoldingRegister<UInt16>(4248, writable: false)] private UInt16 _lithiumBattery1DischargeForbidden; // 0x1098 Lithium battery 1 discharge forbidden
[HoldingRegister<UInt16>(4249, writable: false)] private UInt16 _lithiumBattery2ChargeForbidden; // 0x1099 Lithium battery 2 charge forbidden
[HoldingRegister<UInt16>(4250, writable: false)] private UInt16 _lithiumBattery2DischargeForbidden; // 0x109A Lithium battery 2 discharge forbidden
[HoldingRegister<UInt16>(4251, writable: false)] private UInt16 _lithiumBattery1Full; // 0x109B Lithium battery 1 full
[HoldingRegister<UInt16>(4252, writable: false)] private UInt16 _lithiumBattery1DischargeEnd; // 0x109C Lithium battery 1 discharge end
[HoldingRegister<UInt16>(4253, writable: false)] private UInt16 _lithiumBattery2Full; // 0x109D LithiumBattery2 full
[HoldingRegister<UInt16>(4254, writable: false)] private UInt16 _lithiumBattery2DischargeEnd; // 0x109E Lithium battery 2 discharge end
[HoldingRegister<UInt16>(4255, writable: false)] private UInt16 _loadPowerOverload; // 0x109F Load power overload
[HoldingRegister<UInt16>(4256, writable: false)] private UInt16 _abnormalLeakageSelfCheck; // 0x10A0 Abnormal leakage self-check
[HoldingRegister<UInt16>(4257, writable: false)] private UInt16 _inverterOvertemperatureAlarm; // 0x10A1 Inverter overtemperature alarm
[HoldingRegister<UInt16>(4258, writable: false)] private UInt16 _inverterOvertemperature; // 0x10A2 Inverter overtemperature
[HoldingRegister<UInt16>(4259, writable: false)] private UInt16 _dcConverterOvertemperatureAlarm; // 0x10A3 DC converter overtemperature alarm
[HoldingRegister<UInt16>(4260, writable: false)] private UInt16 _parallelCommunicationAlarm; // 0x10A4 Parallel communication alarm
[HoldingRegister<UInt16>(4261, writable: false)] private UInt16 _systemDerating; // 0x10A5 System derating
[HoldingRegister<UInt16>(4262, writable: false)] private UInt16 _inverterRelayOpen; // 0x10A6 Inverter relay open
[HoldingRegister<UInt16>(4263, writable: false)] private UInt16 _inverterRelayShortCircuit; // 0x10A7 Inverter relay short circuit
[HoldingRegister<UInt16>(4264, writable: false)] private UInt16 _pvAccessMethodErrorAlarm; // 0x10A8 PV access method error alarm
[HoldingRegister<UInt16>(4265, writable: false)] private UInt16 _parallelModuleMissing; // 0x10A9 Parallel module missing
[HoldingRegister<UInt16>(4266, writable: false)] private UInt16 _duplicateMachineNumbersForParallelModules; // 0x10AA Duplicate machine numbers for parallel modules
[HoldingRegister<UInt16>(4267, writable: false)] private UInt16 _parameterConflictInParallelModule; // 0x10AB Parameter conflict in parallel module
[HoldingRegister<UInt16>(4268, writable: false)] private UInt16 _reservedAlarms4; // 0x10AC Reserved alarms4
[HoldingRegister<UInt16>(4269, writable: false)] private UInt16 _reverseMeterConnection; // 0x10AD Reverse meter connection
[HoldingRegister<UInt16>(4270, writable: false)] private UInt16 _inverterSealPulse; // 0x10AE Inverter Seal Pulse
[HoldingRegister<UInt16>(4271, writable: false)] private UInt16 _pv3NotConnected; // 0x10AF PV3 not connected
[HoldingRegister<UInt16>(4272, writable: false)] private UInt16 _pv3Overvoltage; // 0x10B0 PV3 overvoltage
[HoldingRegister<UInt16>(4273, writable: false)] private UInt16 _pv3AverageCurrentAnomaly; // 0x10B1 PV3 average current anomaly
[HoldingRegister<UInt16>(4274, writable: false)] private UInt16 _pv4NotConnected; // 0x10B2 PV4 not connected
[HoldingRegister<UInt16>(4275, writable: false)] private UInt16 _pv4Overvoltage; // 0x10B3 PV4 overvoltage
[HoldingRegister<UInt16>(4276, writable: false)] private UInt16 _pv4AverageCurrentAnomaly; // 0x10B4 PV4 average current anomaly
[HoldingRegister<UInt16>(4277, writable: false)] private UInt16 _pv3PowerTubeFailure; // 0x10B5 PV3 power tube failure
[HoldingRegister<UInt16>(4278, writable: false)] private UInt16 _pv4PowerTubeFailure; // 0x10B6 PV4 power tube failure
[HoldingRegister<UInt16>(4279, writable: false)] private UInt16 _pv3SoftStartFailure; // 0x10B7 PV3 soft start failure
[HoldingRegister<UInt16>(4280, writable: false)] private UInt16 _pv4SoftStartFailure; // 0x10B8 PV4 soft start failure
[HoldingRegister<UInt16>(4281, writable: false)] private UInt16 _pv3OverloadTimeout; // 0x10B9 PV3 overload timeout
[HoldingRegister<UInt16>(4282, writable: false)] private UInt16 _pv4OverloadTimeout; // 0x10BA PV4 overload timeout
[HoldingRegister<UInt16>(4283, writable: false)] private UInt16 _pv3ReverseConnection; // 0x10BB PV3 reverse connection
[HoldingRegister<UInt16>(4284, writable: false)] private UInt16 _pv4ReverseConnection; // 0x10BC PV4 reverse connection
[HoldingRegister<UInt16>(4285, writable: false)] private UInt16 _abnormalDieselGeneratorVoltage; // 0x10BD Abnormal diesel generator voltage
[HoldingRegister<UInt16>(4286, writable: false)] private UInt16 _abnormalDieselGeneratorFrequency; // 0x10BE Abnormal diesel generator frequency
[HoldingRegister<UInt16>(4287, writable: false)] private UInt16 _dieselGeneratorVoltageReverseSequence; // 0x10BF Diesel generator voltage reverse sequence
[HoldingRegister<UInt16>(4288, writable: false)] private UInt16 _dieselGeneratorVoltageOutOfPhase; // 0x10C0 Diesel generator voltage out of phase
[HoldingRegister<UInt16>(4289, writable: false)] private UInt16 _leadBatteryTemperatureAbnormality; // 0x10C1 Lead battery temperature abnormality
[HoldingRegister<UInt16>(4290, writable: false)] private UInt16 _batteryAccessMethodError; // 0x10C2 Battery access method error
[HoldingRegister<UInt16>(4291, writable: false)] private UInt16 _reservedAlarms5; // 0x10C3 Reserved alarms5
[HoldingRegister<UInt16>(4292, writable: false)] private UInt16 _battery1BackupProhibited; // 0x10C4 Battery 1 backup prohibited
[HoldingRegister<UInt16>(4293, writable: false)] private UInt16 _battery2BackupProhibited; // 0x10C5 Battery 2 backup prohibited
[HoldingRegister<UInt16>(4294, writable: false)] private UInt16 _abnormalGridCurrent; // 0x10C6 Abnormal grid current
[HoldingRegister<UInt16>(4295, writable: false)] private UInt16 _generatorOverload; // 0x10C7 Generator overload
[HoldingRegister<UInt16>(4296, writable: false)] private UInt16 _busSoftStartFailure; // 0x10C8 Bus soft start failure
[HoldingRegister<UInt16>(4297, writable: false)] private UInt16 _openCircuitOfPowerGridRelay; // 0x10C9 Open circuit of power grid relay
[HoldingRegister<UInt16>(4298, writable: false)] private UInt16 _shortCircuitOfPowerGridRelay; // 0x10CA Short circuit of power grid relay
[HoldingRegister<UInt16>(4299, writable: false)] private UInt16 _generatorRelayOpenCircuit; // 0x10CB Generator relay open circuit
[HoldingRegister<UInt16>(4300, writable: false)] private UInt16 _generatorRelayShortCircuit; // 0x10CC Generator relay short circuit
[HoldingRegister<UInt16>(4301, writable: false)] private UInt16 _insufficientPhotovoltaicPower; // 0x10CD Insufficient photovoltaic power
[HoldingRegister<UInt16>(4302, writable: false)] private UInt16 _photovoltaic1Overcurrent; // 0x10CE Photovoltaic 1 overcurrent*/
/****************************** Holding registers ****************************/
// Voltages
[HoldingRegister<UInt32>(4096)] public UInt32 _gridAPhaseVoltage; // 0x1000
[HoldingRegister<UInt32>(4098)] private UInt32 _grid_B_Phase_Voltage; // 0x1002
@ -206,6 +377,7 @@ public partial class SinexcelRecord
[HoldingRegister<UInt32>(4740)] private UInt32 _pv4Current; // 0x1284
[HoldingRegister<UInt32>(4742)] private UInt32 _pv4Power; // 0x1286
[HoldingRegister<UInt32>(4744)] private UInt32 _generatorTotalPower; // 0x128C
// ───────────────────────────────────────────────
// Manufacturer Information & Software Versions
// ───────────────────────────────────────────────
@ -226,15 +398,15 @@ public partial class SinexcelRecord
// ───────────────────────────────────────────────
// System configuration / operation registers
// ───────────────────────────────────────────────
//[HoldingRegister<UInt32>(12294, writable: true)] private UInt32 _threePhaseWireSystem; // 0x3006
[HoldingRegister<UInt32>(12294, writable: true)] private UInt32 _threePhaseWireSystem; // 0x3006
//[HoldingRegister<UInt32>(12296, writable: true)] private UInt32 _remainnotused; // 0x3008
//[HoldingRegister<UInt32>(12298, writable: true)] private UInt32 _inputFrequencyClass; // 0x300A
[HoldingRegister<UInt32>(12298, writable: true)] private UInt32 _inputFrequencyClass; // 0x300A
[HoldingRegister<UInt32>(12300, writable: true)] private UInt32 _workingMode; // 0x300C
//[HoldingRegister<UInt32>(12302, writable: true)] private UInt32 _methodSwitchMode; // 0x300E
[HoldingRegister<UInt32>(12302, writable: true)] private UInt32 _methodSwitchMode; // 0x300E
// ───────────────────────────────────────────────
// Inverter Control and Protection Configuration
// ───────────────────────────────────────────────
//[HoldingRegister<UInt32>(12304, writable: true)] private UInt32 _enableOnGridUnbalanceCompensation; // 0x3010
[HoldingRegister<UInt32>(12304, writable: true)] private UInt32 _enableOnGridUnbalanceCompensation; // 0x3010
//[HoldingRegister<UInt32>(12306, writable: true)] private UInt32 _temperatureDrop; // 0x3012
//[HoldingRegister<UInt32>(12308, writable: true)] private UInt32 _hvrt; // 0x3014
//[HoldingRegister<UInt32>(12310, writable: true)] private UInt32 _lvrt; // 0x3016
@ -243,53 +415,60 @@ public partial class SinexcelRecord
// ───────────────────────────────────────────────
// Battery & PV Configuration
// ───────────────────────────────────────────────
//[HoldingRegister<UInt32>(12336, writable: true)] private UInt32 _batteryAccessMethod; // 0x3030
//[HoldingRegister<UInt32>(12338, writable: true)] private UInt32 _meterAccessEnable; // 0x3032
//[HoldingRegister<UInt32>(12340, writable: true)] private UInt32 _enableBattery1; // 0x3034
//[HoldingRegister<UInt32>(12342, writable: true)] private UInt32 _enableBattery2; // 0x3036
//[HoldingRegister<UInt32>(12344, writable: true)] private UInt32 _enablePv1; // 0x3038
//[HoldingRegister<UInt32>(12346, writable: true)] private UInt32 _enablePv2; // 0x303A
//[HoldingRegister<UInt32>(12348, writable: true)] private UInt32 _batteryType; // 0x303C
//[HoldingRegister<UInt32>(12350, writable: true)] private UInt32 _batteryCapacity1; // 0x303E
//[HoldingRegister<UInt32>(12352, writable: true)] private UInt32 _maxChargingCurrentBattery1; // 0x3040
//[HoldingRegister<UInt32>(12354, writable: true)] private UInt32 _maxDischargingCurrentBattery1; // 0x3042
//[HoldingRegister<UInt32>(12362, writable: true)] private UInt32 _ratedBatteryVoltage1; // 0x304A
//[HoldingRegister<UInt32>(12358, writable: true)] private UInt32 _minSocBattery1; // 0x3046
//[HoldingRegister<UInt32>(12360, writable: true)] private UInt32 _setValueBattery1; // 0x3048
//[HoldingRegister<UInt32>(12364, writable: true)] private UInt32 _activeChargeDischarge; // 0x304A
//[HoldingRegister<UInt32>(12366, writable: true)] private UInt32 _activeChargeDischargePower; // 0x304C
//[HoldingRegister<UInt32>(12380, writable: true)] private UInt32 _enableIslandProtection; // 0x305C
//[HoldingRegister<UInt32>(12382, writable: true)] private UInt32 _pvAccessMode; // 0x305E
[HoldingRegister<UInt32>(12336, writable: true)] private UInt32 _batteryAccessMethod; // 0x3030
[HoldingRegister<UInt32>(12338, writable: true)] private UInt32 _meterAccessEnable; // 0x3032
[HoldingRegister<UInt32>(12340, writable: true)] private UInt32 _enableBattery1; // 0x3034
[HoldingRegister<UInt32>(12342, writable: true)] private UInt32 _enableBattery2; // 0x3036
[HoldingRegister<UInt32>(12344, writable: true)] private UInt32 _enablePv1; // 0x3038
[HoldingRegister<UInt32>(12346, writable: true)] private UInt32 _enablePv2; // 0x303A
[HoldingRegister<UInt32>(12348, writable: true)] private UInt32 _batteryType; // 0x303C
[HoldingRegister<UInt32>(12350, writable: true)] private UInt32 _batteryCapacity1; // 0x303E
[HoldingRegister<UInt32>(12352, writable: true)] private UInt32 _maxChargingCurrentBattery1; // 0x3040
[HoldingRegister<UInt32>(12354, writable: true)] private UInt32 _maxDischargingCurrentBattery1; // 0x3042
[HoldingRegister<UInt32>(12356, writable: true)] private UInt32 _ratedBatteryVoltage1; // 0x304A
[HoldingRegister<UInt32>(12358, writable: true)] private UInt32 _minSocBattery1; // 0x3046
[HoldingRegister<UInt32>(12360, writable: true)] private UInt32 _setValueBattery1; // 0x3048
//[HoldingRegister<UInt32>(12362, writable: true)] private UInt32 _activeChargeDischarge; // 0x304A // this
[HoldingRegister<UInt32>(12364, writable: true)] private UInt32 _activeChargeDischargePower; // 0x304C
[HoldingRegister<UInt32>(12380, writable: true)] private UInt32 _enableIslandProtection; // 0x305C
[HoldingRegister<UInt32>(12382, writable: true)] private UInt32 _pvAccessMode; // 0x305E
// ───────────────────────────────────────────────
// System & Battery-2 Configuration
// ───────────────────────────────────────────────
//[HoldingRegister<UInt32>(12384, writable: true)] private UInt32 _outputVoltageAdjustmentFactor; // 0x3060
//[HoldingRegister<UInt32>(12386, writable: true)] private UInt32 _setValueBatteryUndervoltage1; // 0x3062
//[HoldingRegister<UInt32>(12388, writable: true)] private UInt32 _inverterPowerLimit; // 0x3064
//[HoldingRegister<UInt32>(12400, writable: true)] private UInt32 _battery2Capacity; // 0x30B0
//[HoldingRegister<UInt32>(12402, writable: true)] private UInt32 _maxChargingCurrentBattery2; // 0x30B2
//[HoldingRegister<UInt32>(12404, writable: true)] private UInt32 _maxDischargingCurrentBattery2; // 0x30B4
//[HoldingRegister<UInt32>(12406, writable: true)] private UInt32 _battery2RatedVoltage; // 0x30B6
//[HoldingRegister<UInt32>(12408, writable: true)] private UInt32 _battery2MinSoc; // 0x30B8
//[HoldingRegister<UInt32>(12410, writable: true)] private UInt32 _battery2OverVoltageSetting; // 0x30BA
//[HoldingRegister<UInt32>(12412, writable: true)] private UInt32 _battery2UnderVoltageSetpoint; // 0x30BC
[HoldingRegister<UInt32>(12384, writable: true)] private UInt32 _outputVoltageAdjustmentFactor; // 0x3060
[HoldingRegister<UInt32>(12386, writable: true)] private UInt32 _setValueBatteryUndervoltage1; // 0x3062
[HoldingRegister<UInt32>(12388, writable: true)] private UInt32 _inverterPowerLimit; // 0x3064
[HoldingRegister<UInt32>(12400, writable: true)] private UInt32 _battery2Capacity; // 0x30B0
[HoldingRegister<UInt32>(12402, writable: true)] private UInt32 _maxChargingCurrentBattery2; // 0x30B2
[HoldingRegister<UInt32>(12404, writable: true)] private UInt32 _maxDischargingCurrentBattery2; // 0x30B4
[HoldingRegister<UInt32>(12406, writable: true)] private UInt32 _battery2RatedVoltage; // 0x30B6
[HoldingRegister<UInt32>(12408, writable: true)] private UInt32 _battery2MinSoc; // 0x30B8
[HoldingRegister<UInt32>(12410, writable: true)] private UInt32 _battery2OverVoltageSetting; // 0x30BA
[HoldingRegister<UInt32>(12412, writable: true)] private UInt32 _battery2UnderVoltageSetpoint; // 0x30BC
//
//[HoldingRegister<UInt32>(12414, writable: true)] private UInt32 _singleOrParallelMachine; // 0x30BE
//[HoldingRegister<UInt32>(12416, writable: true)] private UInt32 _numberOfSystemModules; // 0x30C0
//[HoldingRegister<UInt32>(12418, writable: true)] private UInt32 _parallelModuleMachineNumber; // 0x30C2
//[HoldingRegister<UInt32>(12420, writable: true)] private UInt32 _accreditedCountries; // 0x30C4
[HoldingRegister<UInt32>(12414, writable: true)] private UInt32 _singleOrParallelMachine; // 0x30BE
[HoldingRegister<UInt32>(12416, writable: true)] private UInt32 _numberOfSystemModules; // 0x30C0
[HoldingRegister<UInt32>(12418, writable: true)] private UInt32 _parallelModuleMachineNumber; // 0x30C2
[HoldingRegister<UInt32>(12420, writable: true)] private UInt32 _accreditedCountries; // 0x30C4
[HoldingRegister<UInt32>(12618, writable: true)] private UInt32 _battery1BackupSOC; // 0x314A
[HoldingRegister<UInt32>(12620, writable: true)] private UInt32 _battery2BackupSOC; // 0x314C
[HoldingRegister<UInt32>(12746, writable: true)] private UInt32 _enableGridExport; // 0x314A
[HoldingRegister<UInt32>(12748, writable: true)] private UInt32 _powerGridExportLimit; // 0x314C
// ───────────────────────────────────────────────
// System Control & Diagnostic Registers
// ───────────────────────────────────────────────
[HoldingRegister<UInt32>(15426, writable: true)] private UInt32 _powerOn; // 0x3C42
[HoldingRegister<UInt32>(15428, writable: true)] private UInt32 _powerOff; // 0x3C44
//[HoldingRegister<UInt32>(15430, writable: true)] private UInt32 _faultClearing; // 0x3C46
//[HoldingRegister<UInt32>(15518, writable: true)] private UInt32 _meterReverseManualDetection; // 0x3C9E
//[HoldingRegister<UInt32>(15520, writable: true)] private UInt32 _batteryVoltageRating; // 0x3CA0
//[HoldingRegister<UInt32>(15524, writable: true)] private UInt32 _battery1Activation; // 0x3CA4
//[HoldingRegister<UInt32>(15526, writable: true)] private UInt32 _battery2Activation; // 0x3CA6
[HoldingRegister<UInt32>(15430, writable: true)] private UInt32 _faultClearing; // 0x3C46
[HoldingRegister<UInt32>(15518, writable: true)] private UInt32 _meterReverseManualDetection; // 0x3C9E
//[HoldingRegister<UInt32>(15520, writable: true)] private UInt32 _batteryVoltageRating; // 0x3CA0 This causing the inverter to switch off, please consider testing it first before uncommenting
[HoldingRegister<UInt32>(15522, writable: true)] private UInt32 _battery1Activation; // 0x3CA2
[HoldingRegister<UInt32>(15524, writable: true)] private UInt32 _battery2Activation; // 0x3CA4
// ───────────────────────────────────────────────
@ -337,8 +516,6 @@ public partial class SinexcelRecord
// Time-based scheduling (period 1) configuration
// ───────────────────────────────────────────────
//[HoldingRegister<UInt32>(49328, writable: true)] private UInt32 _Timed_Charge_and_Discharge_Off; // 0xC0B4, bit flags SunSat
//[HoldingRegister<UInt32>(49330, writable: true)] private UInt32 _Other_time_period_mode; // 0xC0B4, bit flags SunSat
[HoldingRegister<UInt32>(49332, writable: true)] private UInt32 _repetitiveWeeks; // 0xC0B4, bit flags SunSat
[HoldingRegister<UInt32>(49334, writable: true)] private UInt32 _effectiveStartDate; // 0xC0B6, UNIX timestamp (UTC+offset)
[HoldingRegister<UInt32>(49336, writable: true)] private UInt32 _effectiveEndDate; // 0xC0B8, UNIX timestamp (UTC+offset)

View File

@ -461,6 +461,7 @@ function Installation(props: singleInstallationProps) {
<Log
errorLoadingS3Data={errorLoadingS3Data}
id={props.current_installation.id}
status={props.current_installation.status}
></Log>
}
/>

View File

@ -27,6 +27,7 @@ import Checkbox from '@mui/material/Checkbox';
interface LogProps {
errorLoadingS3Data: boolean;
id: number;
status?: number;
}
function Log(props: LogProps) {
@ -47,7 +48,7 @@ function Log(props: LogProps) {
const tokencontext = useContext(TokenContext);
const { removeToken } = tokencontext;
const [diagnoses, setDiagnoses] = useState<{ description: string; response: DiagnosticResponse }[]>([]);
const [diagnoses, setDiagnoses] = useState<{ description: string; lastSeen: string; response: DiagnosticResponse }[]>([]);
const [diagnosisLoading, setDiagnosisLoading] = useState(false);
const [expandedDiagnoses, setExpandedDiagnoses] = useState<Set<number>>(new Set());
@ -78,11 +79,25 @@ function Log(props: LogProps) {
}, [updateCount]);
// fetch AI diagnosis for the latest 3 unique errors/warnings
// only when installation status is red (2) or orange (1)
useEffect(() => {
// combine errors and warnings, filter non-errors, sort by date+time descending, deduplicate
// skip diagnosis if status is not alarm (2) or warning (1)
if (props.status !== 1 && props.status !== 2) {
setDiagnoses([]);
return;
}
// filter to last 24 hours only
const now = new Date();
const cutoff = new Date(now.getTime() - 24 * 60 * 60 * 1000);
const ignore = new Set(['NoAlarm', '0', '']);
const all = [...errors, ...warnings]
.filter(item => !ignore.has(item.description.trim()))
.filter(item => {
if (ignore.has(item.description.trim())) return false;
const itemDate = new Date(item.date + 'T' + item.time);
return itemDate >= cutoff;
})
.sort((a, b) => (b.date + ' ' + b.time).localeCompare(a.date + ' ' + a.time));
const seen = new Set<string>();
@ -112,16 +127,16 @@ function Log(props: LogProps) {
.get(`/DiagnoseError?installationId=${props.id}&errorDescription=${encodeURIComponent(target.description)}`)
.then((res: AxiosResponse<DiagnosticResponse>) => {
if (res.status === 204 || !res.data || !res.data.explanation) return null;
return { description: target.description, response: res.data };
return { description: target.description, lastSeen: target.date + ' ' + target.time, response: res.data };
})
.catch(() => null)
)
).then(results => {
setDiagnoses(results.filter(r => r !== null) as { description: string; response: DiagnosticResponse }[]);
setDiagnoses(results.filter(r => r !== null) as { description: string; lastSeen: string; response: DiagnosticResponse }[]);
}).finally(() => {
setDiagnosisLoading(false);
});
}, [errors, warnings]);
}, [errors, warnings, props.status]);
const handleErrorButtonPressed = () => {
setErrorButtonPressed(!errorButtonPressed);
@ -476,10 +491,10 @@ function Log(props: LogProps) {
<Box sx={{ padding: '16px' }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: '8px', mb: 1 }}>
<Typography variant="subtitle2" fontWeight="bold" color="primary">
<FormattedMessage id="ai_diagnosis" defaultMessage="AI Diagnosis" />
{diag.description}
</Typography>
<Typography variant="caption" color="text.secondary">
{diag.description}
Last seen: {diag.lastSeen}
</Typography>
</Box>

View File

@ -452,6 +452,8 @@ export interface JSONRecordData {
Battery2Soh:number;
PvPower:number;
ConsumptionPower:number;
WorkingMode?:string;
OperatingMode?:string;
};
AcDcGrowatt: {

View File

@ -346,6 +346,7 @@ function SalidomoInstallation(props: singleInstallationProps) {
<Log
errorLoadingS3Data={errorLoadingS3Data}
id={props.current_installation.id}
status={props.current_installation.status}
></Log>
}
/>

View File

@ -99,6 +99,9 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
<TableCell>
<FormattedMessage id="location" defaultMessage="Location" />
</TableCell>
<TableCell>
<FormattedMessage id="installationSN" defaultMessage="Installation SN" />
</TableCell>
<TableCell>
<FormattedMessage id="region" defaultMessage="Region" />
</TableCell>
@ -156,6 +159,19 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
</Typography>
</TableCell>
<TableCell>
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
{installation.serialNumber}
</Typography>
</TableCell>
<TableCell>
<Typography
variant="body2"

View File

@ -339,7 +339,11 @@ function SodioHomeInstallation(props: singleInstallationProps) {
fontSize: '14px'
}}
>
{values.Config.OperatingPriority}
{props.current_installation.device === 4
? values.InverterRecord?.WorkingMode
: props.current_installation.device === 3
? values.InverterRecord?.OperatingMode
: values.Config.OperatingPriority}
</Typography>
</div>
)}
@ -474,6 +478,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
<Log
errorLoadingS3Data={errorLoadingS3Data}
id={props.current_installation.id}
status={props.current_installation.status}
></Log>
}
/>