New project sinexcel
This commit is contained in:
parent
0aae239551
commit
711f054a06
|
|
@ -0,0 +1,33 @@
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
using InnovEnergy.Lib.Utils;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication.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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication.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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication.DataTypes;
|
||||||
|
|
||||||
|
public class AlarmOrWarning
|
||||||
|
{
|
||||||
|
public String? Date { get; set; }
|
||||||
|
public String? Time { get; set; }
|
||||||
|
public String? Description { get; set; }
|
||||||
|
public String? CreatedBy { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication.DataTypes;
|
||||||
|
|
||||||
|
public class Configuration
|
||||||
|
{
|
||||||
|
public Double MinimumSoC { get; set; }
|
||||||
|
public Double GridSetPoint { get; set; }
|
||||||
|
// public Double MaximumDischargingCurrent { get; set; }
|
||||||
|
// public Double MaximumChargingCurrent { get; set; }
|
||||||
|
// public EssMode OperatingPriority { get; set; }
|
||||||
|
// public required Int16 BatteriesCount { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication.DataTypes;
|
||||||
|
|
||||||
|
public enum SodistoreAlarmState
|
||||||
|
{
|
||||||
|
Green,
|
||||||
|
Orange,
|
||||||
|
Red
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication.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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication;
|
||||||
|
|
||||||
|
public enum DeviceState
|
||||||
|
{
|
||||||
|
Disabled,
|
||||||
|
Measured,
|
||||||
|
Computed
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
using InnovEnergy.App.SinexcelCommunication.SystemConfig;
|
||||||
|
using InnovEnergy.Lib.Devices.Sinexcel_12K_TL;
|
||||||
|
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication.ESS;
|
||||||
|
|
||||||
|
public record StatusRecord
|
||||||
|
{
|
||||||
|
public required SinexcelRecord SinexcelRecord { get; set; }
|
||||||
|
public required Config Config { get; set; }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.NetworkInformation;
|
||||||
|
using System.Net.Sockets;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using InnovEnergy.App.SinexcelCommunication.DataTypes;
|
||||||
|
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication.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: GridSetPoint is " + config.GridSetPoint +
|
||||||
|
", MinimumSoC is " + config.MinimumSoC + " and operating priorty is " );
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using InnovEnergy.App.SinexcelCommunication.DataTypes;
|
||||||
|
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;
|
||||||
|
|
||||||
|
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}");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,566 @@
|
||||||
|
using System.IO.Compression;
|
||||||
|
using System.IO.Ports;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
using Flurl.Http;
|
||||||
|
|
||||||
|
using System.Reactive.Linq;
|
||||||
|
using System.Reactive.Threading.Tasks;
|
||||||
|
|
||||||
|
using InnovEnergy.Lib.Devices.Sinexcel_12K_TL;
|
||||||
|
using InnovEnergy.App.SinexcelCommunication.DataLogging;
|
||||||
|
using InnovEnergy.App.SinexcelCommunication.ESS;
|
||||||
|
using InnovEnergy.App.SinexcelCommunication.MiddlewareClasses;
|
||||||
|
using InnovEnergy.App.SinexcelCommunication.SystemConfig;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||||
|
using InnovEnergy.App.SinexcelCommunication.DataTypes;
|
||||||
|
|
||||||
|
using InnovEnergy.Lib.Units;
|
||||||
|
using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Formatting = Newtonsoft.Json.Formatting;
|
||||||
|
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||||
|
using static InnovEnergy.App.SinexcelCommunication.MiddlewareClasses.MiddlewareAgent;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||||
|
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication;
|
||||||
|
|
||||||
|
[SuppressMessage("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic access otherwise can break functionality when trimming application code")]
|
||||||
|
internal static class Program
|
||||||
|
{
|
||||||
|
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(4);
|
||||||
|
private const UInt16 NbrOfFileToConcatenate = 15; // add this to config file
|
||||||
|
private static UInt16 _fileCounter = 0;
|
||||||
|
private static Channel _sinexcelChannel;
|
||||||
|
|
||||||
|
private const String SwVersionNumber =" V1.00.131025 beta";
|
||||||
|
private const String VpnServerIp = "10.2.0.11";
|
||||||
|
private static Boolean _subscribedToQueue = false;
|
||||||
|
private static Boolean _subscribeToQueueForTheFirstTime = false;
|
||||||
|
private static SodistoreAlarmState _prevSodiohomeAlarmState = SodistoreAlarmState.Green;
|
||||||
|
private static SodistoreAlarmState _sodiohomeAlarmState = SodistoreAlarmState.Green;
|
||||||
|
|
||||||
|
// move all this to config file
|
||||||
|
private const String Port = "/dev/ttyUSB0";
|
||||||
|
private const Byte SlaveId = 1;
|
||||||
|
private const Parity Parity = 0; //none
|
||||||
|
private const Int32 StopBits = 1;
|
||||||
|
private const Int32 BaudRate = 115200;
|
||||||
|
private const Int32 DataBits = 8;
|
||||||
|
|
||||||
|
public static async Task Main(String[] args)
|
||||||
|
{
|
||||||
|
_sinexcelChannel = new SerialPortChannel(Port, BaudRate, Parity, DataBits, StopBits);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await Run();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
e.WriteLine();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ReSharper disable once FunctionNeverReturns
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task Run()
|
||||||
|
{
|
||||||
|
Watchdog.NotifyReady();
|
||||||
|
|
||||||
|
Console.WriteLine("Starting Sinexcel Communication");
|
||||||
|
|
||||||
|
var sinexcelDevice = new SinexcelDevice(_sinexcelChannel, SlaveId);
|
||||||
|
// var sinexcelDevice = new sinexcelDevices(new List<sinexcelDevice> { growattDeviceT415K });
|
||||||
|
|
||||||
|
StatusRecord? ReadStatus()
|
||||||
|
{
|
||||||
|
var config = Config.Load();
|
||||||
|
var sinexcelRecord = sinexcelDevice.Read();
|
||||||
|
|
||||||
|
return new StatusRecord
|
||||||
|
{
|
||||||
|
SinexcelRecord = sinexcelRecord,
|
||||||
|
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 Battery Data *********************************************");
|
||||||
|
Console.WriteLine(startTime.ToString("HH:mm:ss.fff"));
|
||||||
|
|
||||||
|
// the order matter of the next three lines
|
||||||
|
var statusrecord = ReadStatus();
|
||||||
|
if (statusrecord == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
statusrecord.SinexcelRecord.GridAPhaseVoltage.WriteLine(" = Grid A PhaseVoltage");
|
||||||
|
statusrecord.SinexcelRecord.GridBPhaseVoltage.WriteLine(" = Grid B PhaseVoltage");
|
||||||
|
statusrecord.SinexcelRecord.GridCPhaseVoltage.WriteLine(" = Grid C PhaseVoltage");
|
||||||
|
|
||||||
|
statusrecord.SinexcelRecord.GridAPhaseCurrent.WriteLine(" = Grid A PhaseCurrent");
|
||||||
|
statusrecord.SinexcelRecord.GridBPhaseCurrent.WriteLine(" = Grid B PhaseCurrent");
|
||||||
|
statusrecord.SinexcelRecord.GridCPhaseCurrent.WriteLine(" = Grid C PhaseCurrent");
|
||||||
|
|
||||||
|
|
||||||
|
statusrecord.SinexcelRecord.GridAPhaseActivePower.WriteLine(" = A Grid Active Power");
|
||||||
|
statusrecord.SinexcelRecord.GridBPhaseActivePower.WriteLine(" = B Grid Active Power");
|
||||||
|
statusrecord.SinexcelRecord.GridCPhaseActivePower.WriteLine(" = C Grid Active Power");
|
||||||
|
|
||||||
|
statusrecord.SinexcelRecord.GridVoltageFrequency.WriteLine(" = Frequency");
|
||||||
|
|
||||||
|
statusrecord.SinexcelRecord.ElectricMeterCPhaseActivePower.WriteLine(" Meter Phase C Power");
|
||||||
|
statusrecord.SinexcelRecord.ElectricMeterBPhaseActivePower.WriteLine(" Meter Phase B Power");
|
||||||
|
statusrecord.SinexcelRecord.ElectricMeterAPhaseActivePower.WriteLine(" Meter Phase A Power");
|
||||||
|
|
||||||
|
statusrecord.SinexcelRecord.ElectricMeterAPhaseCurrent.WriteLine(" Meter Phase C Current ");
|
||||||
|
statusrecord.SinexcelRecord.ElectricMeterBPhaseCurrent.WriteLine(" Meter Phase B Current ");
|
||||||
|
statusrecord.SinexcelRecord.ElectricMeterBPhaseCurrent.WriteLine(" Meter Phase A Current ");
|
||||||
|
statusrecord.SinexcelRecord.WorkingMode.WriteLine(" workingmode");
|
||||||
|
|
||||||
|
statusrecord.SinexcelRecord.BatteryVoltage.WriteLine(" BatteryVoltage");
|
||||||
|
statusrecord.SinexcelRecord.BatteryVoltage1.WriteLine(" BatteryVoltage1");
|
||||||
|
statusrecord.SinexcelRecord.BatteryVoltage2.WriteLine(" BatteryVoltage2");
|
||||||
|
|
||||||
|
statusrecord.SinexcelRecord.BatteryPower1.WriteLine(" BatteryPower1");
|
||||||
|
statusrecord.SinexcelRecord.BatteryPower2.WriteLine(" BatteryPower2");
|
||||||
|
statusrecord.SinexcelRecord.TotalBatteryPower.WriteLine(" TotalBatteryPower");
|
||||||
|
|
||||||
|
statusrecord.SinexcelRecord.EnableBattery1.WriteLine(" EnableBattery1");
|
||||||
|
statusrecord.SinexcelRecord.EnableBattery2.WriteLine(" EnableBattery2");
|
||||||
|
|
||||||
|
statusrecord.SinexcelRecord.MaxChargingCurrentBattery1.WriteLine(" MaxChargingCurrentBattery1");
|
||||||
|
statusrecord.SinexcelRecord.MaxChargingCurrentBattery2.WriteLine(" MaxChargingCurrentBattery2");
|
||||||
|
|
||||||
|
statusrecord.SinexcelRecord.MaxDischargingCurrentBattery1.WriteLine(" MaxChargingCurrentBattery1");
|
||||||
|
statusrecord.SinexcelRecord.MaxDischargingCurrentBattery2.WriteLine(" MaxChargingCurrentBattery2");
|
||||||
|
|
||||||
|
//EssModeControl(statusrecord);
|
||||||
|
// statusrecord.ApplyDefaultSettings();
|
||||||
|
Console.WriteLine( " ************************************ We are writing ************************************");
|
||||||
|
statusrecord?.Config.Save(); // save the config file
|
||||||
|
//sinexcelDevice.Write(statusrecord?.SinexcelRecord);
|
||||||
|
var stopTime = DateTime.Now;
|
||||||
|
Console.WriteLine(stopTime.ToString("HH:mm:ss.fff"));
|
||||||
|
return statusrecord;
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
// Handle exception and print the error
|
||||||
|
Console.WriteLine(e );
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Int32 GetInstallationId(String s3Bucket)
|
||||||
|
{
|
||||||
|
var part = s3Bucket.Split('-').FirstOrDefault();
|
||||||
|
return int.TryParse(part, out var id) ? id : 0; // is 0 a default safe value? check with Marios
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SendSalimaxStateAlarm(StatusMessage currentSalimaxState, StatusRecord? record)
|
||||||
|
{
|
||||||
|
var s3Bucket = Config.Load().S3?.Bucket;
|
||||||
|
var subscribedNow = false;
|
||||||
|
|
||||||
|
//When the controller boots, it tries to subscribe to the queue
|
||||||
|
if (_subscribeToQueueForTheFirstTime == false)
|
||||||
|
{
|
||||||
|
subscribedNow = true;
|
||||||
|
_subscribeToQueueForTheFirstTime = true;
|
||||||
|
_prevSodiohomeAlarmState = currentSalimaxState.Status;
|
||||||
|
_subscribedToQueue = RabbitMqManager.SubscribeToQueue(currentSalimaxState, s3Bucket, VpnServerIp);
|
||||||
|
}
|
||||||
|
|
||||||
|
//If already subscribed to the queue and the status has been changed, update the queue
|
||||||
|
if (!subscribedNow && _subscribedToQueue && currentSalimaxState.Status != _prevSodiohomeAlarmState)
|
||||||
|
{
|
||||||
|
_prevSodiohomeAlarmState = currentSalimaxState.Status;
|
||||||
|
if (s3Bucket != null)
|
||||||
|
RabbitMqManager.InformMiddleware(currentSalimaxState);
|
||||||
|
}
|
||||||
|
|
||||||
|
//If there is an available message from the RabbitMQ Broker, apply the configuration file
|
||||||
|
Configuration? config = SetConfigurationFile();
|
||||||
|
if (config != null)
|
||||||
|
{
|
||||||
|
record.ApplyConfigFile(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ApplyConfigFile(this StatusRecord? status, Configuration? config)
|
||||||
|
{
|
||||||
|
if (config == null) return;
|
||||||
|
if (status == null) return;
|
||||||
|
|
||||||
|
status.Config.MinSoc = config.MinimumSoC;
|
||||||
|
status.Config.GridSetPoint = config.GridSetPoint * 1000; // converted from kW to W
|
||||||
|
//status.Config.MaximumChargingCurrent = config.MaximumChargingCurrent;
|
||||||
|
//status.Config.MaximumDischargingCurrent = config.MaximumDischargingCurrent;
|
||||||
|
//status.Config.OperatingPriority = config.OperatingPriority;
|
||||||
|
//status.Config.BatteriesCount = config.BatteriesCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.SinexcelRecord.GridAPhaseVoltage, "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);
|
||||||
|
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 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 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];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 SavingLocalCsvFile(Int64 timestamp, String csv)
|
||||||
|
{
|
||||||
|
const String directoryPath = "/home/inesco/SodiStoreHome/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> 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 = 2,
|
||||||
|
Status = _sodiohomeAlarmState,
|
||||||
|
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 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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
using InnovEnergy.Lib.Utils.Net;
|
||||||
|
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication;
|
||||||
|
|
||||||
|
public class SalimaxDevice : Ip4Address
|
||||||
|
{
|
||||||
|
public required DeviceState DeviceState { get; init; }
|
||||||
|
|
||||||
|
public override String ToString() => $"{base.ToString()} ({DeviceState})";
|
||||||
|
|
||||||
|
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), DeviceState);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<RootNamespace>InnovEnergy.App.SinexcelCommunication</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
|
||||||
|
<Import Project="../InnovEnergy.App.props" />
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="../../Lib/Devices/BatteryDeligreen/BatteryDeligreen.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Lib\Devices\Amax5070\Amax5070.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Lib\Protocols\Modbus\Modbus.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Lib\Units\Units.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Sinexcel 12K TL\Sinexcel 12K TL.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>
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json;
|
||||||
|
using InnovEnergy.Lib.Utils;
|
||||||
|
using static System.Text.Json.JsonSerializer;
|
||||||
|
|
||||||
|
|
||||||
|
namespace InnovEnergy.App.SinexcelCommunication.SystemConfig;
|
||||||
|
|
||||||
|
[SuppressMessage("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic access otherwise can break functionality when trimming application code")]
|
||||||
|
public class Config
|
||||||
|
{
|
||||||
|
|
||||||
|
private static String DefaultConfigFilePath => Path.Combine(Environment.CurrentDirectory, "config.json");
|
||||||
|
private static DateTime DefaultDatetime => new(2025, 01, 01, 09, 00, 00);
|
||||||
|
|
||||||
|
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
|
||||||
|
|
||||||
|
public required Double MinSoc { get; set; }
|
||||||
|
public required Double GridSetPoint { get; set; }
|
||||||
|
|
||||||
|
public required S3Config? S3 { get; set; }
|
||||||
|
|
||||||
|
|
||||||
|
private static String? LastSavedData { get; set; }
|
||||||
|
|
||||||
|
public static Config Default => new()
|
||||||
|
{
|
||||||
|
MinSoc = 20,
|
||||||
|
GridSetPoint = 0,
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.SinexcelCommunication.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}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
#!/bin/bash
|
||||||
|
dotnet_version='net6.0'
|
||||||
|
salimax_ip="$1"
|
||||||
|
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"
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo -e "\n============================ Build ============================\n"
|
||||||
|
|
||||||
|
dotnet publish \
|
||||||
|
./SinexcelCommunication.csproj \
|
||||||
|
-p:PublishTrimmed=false \
|
||||||
|
-c Release \
|
||||||
|
-r linux-arm64
|
||||||
|
|
||||||
|
echo -e "\n============================ Deploy ============================\n"
|
||||||
|
|
||||||
|
rsync -v \
|
||||||
|
--exclude '*.pdb' \
|
||||||
|
./bin/Release/$dotnet_version/linux-arm64/publish/* \
|
||||||
|
$username@"$salimax_ip":~/SodiStoreHome
|
||||||
|
|
||||||
|
if [[ "$is_release" == "--release" ]]; then
|
||||||
|
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."
|
||||||
|
fi
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||||
|
|
||||||
|
public enum AccreditedCountry
|
||||||
|
{
|
||||||
|
// 0x30C4 – Accredited Countries
|
||||||
|
Germany = 0,
|
||||||
|
UnitedKingdom = 1,
|
||||||
|
EuropeanStandard = 2,
|
||||||
|
SouthAfrica = 3,
|
||||||
|
Poland = 4,
|
||||||
|
Austria = 5,
|
||||||
|
Italy = 6,
|
||||||
|
Belgium = 7,
|
||||||
|
Spain = 8,
|
||||||
|
Israel = 9,
|
||||||
|
ThaiPEA = 10,
|
||||||
|
AustraliaA = 11,
|
||||||
|
AustraliaB = 12,
|
||||||
|
AustraliaC = 13,
|
||||||
|
NewZealand = 14,
|
||||||
|
Vietnam = 15,
|
||||||
|
ThailandMEA = 16,
|
||||||
|
China = 17,
|
||||||
|
NorthAmerica = 18
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||||
|
|
||||||
|
// 0x3030 – Battery Access Method
|
||||||
|
public enum SinexcelBatteryAccessMethod
|
||||||
|
{
|
||||||
|
Independent = 0, // 0: Independent access
|
||||||
|
Parallel = 1 // 1: Parallel access
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0x3032 – Meter Access Enable
|
||||||
|
public enum SinexcelMeterAccessEnable
|
||||||
|
{
|
||||||
|
Disable = 0,
|
||||||
|
Enable = 1 // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0x3034 / 0x3036 – Battery Enable
|
||||||
|
public enum SinexcelBatteryEnable
|
||||||
|
{
|
||||||
|
Disable = 0,
|
||||||
|
Enable = 1 // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0x3038 / 0x303A – PV Enable
|
||||||
|
public enum SinexcelPvEnable
|
||||||
|
{
|
||||||
|
Disable = 0,
|
||||||
|
Enable = 1 // Default
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0x303C – Battery Type
|
||||||
|
public enum SinexcelBatteryType
|
||||||
|
{
|
||||||
|
Lithium = 0, // Default
|
||||||
|
Supercapacitor = 1,
|
||||||
|
LeadBattery = 2,
|
||||||
|
SodiumIon = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0x304A – Active Charge / Discharge
|
||||||
|
public enum SinexcelActiveChargeDischarge
|
||||||
|
{
|
||||||
|
Disable = 0,
|
||||||
|
ActiveCharge = 1,
|
||||||
|
ActiveDischarge = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0x305C – Enable Island Protection
|
||||||
|
public enum SinexcelIslandProtection
|
||||||
|
{
|
||||||
|
Disable = 0,
|
||||||
|
Enable = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0x305E – PV Access Mode
|
||||||
|
public enum SinexcelPvAccessMode
|
||||||
|
{
|
||||||
|
IndependentAccess = 0,
|
||||||
|
SemiParallel = 1,
|
||||||
|
FullParallel = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0x3CA2 – Battery Rating
|
||||||
|
public enum SinexcelBatteryRating
|
||||||
|
{
|
||||||
|
HighVoltage = 0, // 0: High voltage battery
|
||||||
|
LowVoltage = 1 // 1: Low voltage battery
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||||
|
|
||||||
|
public enum GridSwitchMethod
|
||||||
|
{
|
||||||
|
Automatic = 0, // 0: Automatic mode (default)
|
||||||
|
ManualOffGrid = 1, // 1: Manual off-grid mode
|
||||||
|
ManualOnGrid = 2 // 2: Manual on-grid mode
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||||
|
|
||||||
|
public enum InputFrequency
|
||||||
|
{
|
||||||
|
Hz50 = 0, // 0: 50 Hz (default)
|
||||||
|
Hz60 = 1 // 1: 60 Hz
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||||
|
|
||||||
|
public enum SinexcelMachineType
|
||||||
|
{
|
||||||
|
ThreePhase = 0, // 0: Three phase
|
||||||
|
SinglePhase = 1, // 1: Single phase
|
||||||
|
SplitPhase = 2, // 2: Split phase
|
||||||
|
OffGrid = 3, // 3: Off-grid
|
||||||
|
}
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// 0x3010 – Enable On-Grid Unbalance Compensation
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public enum SinexcelUnbalanceCompensation
|
||||||
|
{
|
||||||
|
Disable = 0, // 0: Disabled
|
||||||
|
Enable = 1 // 1: Enabled (default)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// 0x3012 – Temperature Drop Control
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public enum SinexcelTemperatureDrop
|
||||||
|
{
|
||||||
|
Disable = 0, // 0: Disabled
|
||||||
|
Enable = 1 // 1: Enabled (default)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// 0x3014 – HVRT (High Voltage Ride-Through)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public enum SinexcelHvrt
|
||||||
|
{
|
||||||
|
Disable = 0, // 0: Disabled (default)
|
||||||
|
Enable = 1 // 1: Enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// 0x3016 – LVRT (Low Voltage Ride-Through)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public enum SinexcelLvrt
|
||||||
|
{
|
||||||
|
Disable = 0, // 0: Disabled (default)
|
||||||
|
Enable = 1 // 1: Enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0x30BE – Single or Parallel Machine
|
||||||
|
public enum SinexcelMachineMode
|
||||||
|
{
|
||||||
|
Single = 0, // Default
|
||||||
|
Parallel = 1
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||||
|
|
||||||
|
public enum SinexcelModel
|
||||||
|
{
|
||||||
|
T_TH = 0, // Isuna 3000T–20000T
|
||||||
|
S_SL = 1, // Isuna 3000S–6000S
|
||||||
|
U_PH = 2,
|
||||||
|
D_TH = 3,
|
||||||
|
D_TL = 4,
|
||||||
|
D_SH = 5,
|
||||||
|
D_SL = 6,
|
||||||
|
O_SOL = 7,
|
||||||
|
O_SOL_P = 8,
|
||||||
|
L_TH = 9,
|
||||||
|
O_SOU_G2 = 10,
|
||||||
|
O_SOL_G2 = 11,
|
||||||
|
O_SOL_G2B = 12, // Duplicate in documentation (0x200D: 11 & 12 both O-SOL-G2)
|
||||||
|
O_TOH = 13,
|
||||||
|
D_SL_S = 14,
|
||||||
|
D_TL_S = 15
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||||
|
|
||||||
|
public enum ThreePhaseWireSystem
|
||||||
|
{
|
||||||
|
ThreePhaseFourWire = 0, // 0: Three-phase four-wire system (default)
|
||||||
|
ThreePhaseThreeWire = 1 // 1: Three-phase three-wire system
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||||
|
|
||||||
|
public enum WorkingMode
|
||||||
|
{
|
||||||
|
SpontaneousSelfUse = 0, // 0: Spontaneous self-use mode (default)
|
||||||
|
TimeChargeDischarge = 1, // 1: Time charge & discharge mode
|
||||||
|
TimeOfUsePowerPrice = 2, // 2: Time-of-use power price mode (future)
|
||||||
|
DisasterStandby = 3, // 3: Disaster standby mode
|
||||||
|
ManualControl = 4, // 4: Manual control mode (future)
|
||||||
|
PvPriorityCharging = 5, // 5: PV Priority Charging
|
||||||
|
PrioritySellElectricity = 6 // 6: Priority mode for selling electricity
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum MeterStatus
|
||||||
|
{
|
||||||
|
Online = 1,
|
||||||
|
Offline = 0
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
|
<RootNamespace>InnovEnergy.Lib.Devices.Sinexcel_12K_TL;</RootNamespace>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<LangVersion>preview</LangVersion>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Lib\Protocols\Modbus\Modbus.csproj" />
|
||||||
|
<ProjectReference Include="..\Lib\Units\Units.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
using System.IO.Ports;
|
||||||
|
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.Sinexcel_12K_TL;
|
||||||
|
|
||||||
|
public class SinexcelDevice : ModbusDevice<SinexcelRecord>
|
||||||
|
{
|
||||||
|
private const Parity Parity = 0; //none
|
||||||
|
private const Int32 StopBits = 1;
|
||||||
|
private const Int32 BaudRate = 115200;
|
||||||
|
private const Int32 DataBits = 8;
|
||||||
|
|
||||||
|
public Byte SlaveId { get; }
|
||||||
|
|
||||||
|
public SinexcelDevice(String tty, Byte slaveId, SshHost host) : this
|
||||||
|
(
|
||||||
|
channel: new RemoteSerialChannel(host, tty, BaudRate, Parity, DataBits, StopBits),
|
||||||
|
slaveId
|
||||||
|
)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public SinexcelDevice(String tty, Byte slaveId, String? host = null) : this
|
||||||
|
(
|
||||||
|
channel: host switch
|
||||||
|
{
|
||||||
|
null => new SerialPortChannel ( tty, BaudRate, Parity, DataBits, StopBits),
|
||||||
|
_ => new RemoteSerialChannel(host, tty, BaudRate, Parity, DataBits, StopBits)
|
||||||
|
},
|
||||||
|
slaveId
|
||||||
|
)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public SinexcelDevice(Channel channel, Byte slaveId) : this
|
||||||
|
(
|
||||||
|
client: new ModbusRtuClient(channel, slaveId)
|
||||||
|
)
|
||||||
|
{}
|
||||||
|
|
||||||
|
public SinexcelDevice(ModbusClient client): base(client)
|
||||||
|
{
|
||||||
|
SlaveId = client.SlaveId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,610 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||||
|
using InnovEnergy.Lib.Units;
|
||||||
|
using InnovEnergy.Lib.Units.Power;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL;
|
||||||
|
|
||||||
|
public partial class SinexcelRecord
|
||||||
|
{
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Public API — Decoded Float Values
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Grid Measurements
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public Voltage GridAPhaseVoltage => ConvertBitPatternToFloat(_gridAPhaseVoltage);
|
||||||
|
public Voltage GridBPhaseVoltage => UIntToFloat(_grid_B_Phase_Voltage);
|
||||||
|
public Voltage GridCPhaseVoltage => UIntToFloat(_grid_C_Phase_Voltage);
|
||||||
|
|
||||||
|
public Voltage GridAbWireVoltage => UIntToFloat(_grid_AB_Wire_Voltage);
|
||||||
|
public Voltage GridBcWireVoltage => UIntToFloat(_grid_BC_Wire_Voltage);
|
||||||
|
public Voltage GridCaWireVoltage => UIntToFloat(_grid_CA_Wire_Voltage);
|
||||||
|
|
||||||
|
public Frequency GridVoltageFrequency => UIntToFloat(_grid_Voltage_Frequency);
|
||||||
|
|
||||||
|
public Current GridAPhaseCurrent => UIntToFloat(_grid_A_Phase_Current);
|
||||||
|
public Current GridBPhaseCurrent => UIntToFloat(_grid_B_Phase_Current);
|
||||||
|
public Current GridCPhaseCurrent => UIntToFloat(_grid_C_Phase_Current);
|
||||||
|
public Current GridNWireCurrent => UIntToFloat(_grid_N_Wire_Current);
|
||||||
|
|
||||||
|
public ApparentPower GridAPhaseApparentPower => UIntToFloat(_gridAPhaseApparentPower);
|
||||||
|
public ApparentPower GridBPhaseApparentPower => UIntToFloat(_gridBPhaseApparentPower);
|
||||||
|
public ApparentPower GridCPhaseApparentPower => UIntToFloat(_gridCPhaseApparentPower);
|
||||||
|
|
||||||
|
public ActivePower GridAPhaseActivePower => UIntToFloat(_gridAPhaseActivePower);
|
||||||
|
public ActivePower GridBPhaseActivePower => UIntToFloat(_gridBPhaseActivePower);
|
||||||
|
public ActivePower GridCPhaseActivePower => UIntToFloat(_gridCPhaseActivePower);
|
||||||
|
|
||||||
|
public ReactivePower GridAPhaseReactivePower => UIntToFloat(_gridAPhaseReactivePower);
|
||||||
|
public ReactivePower GridBPhaseReactivePower => UIntToFloat(_gridBPhaseReactivePower);
|
||||||
|
public ReactivePower GridCPhaseReactivePower => UIntToFloat(_gridCPhaseReactivePower);
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Load Measurements
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public Voltage LoadAPhaseVoltage => UIntToFloat(_loadAPhaseVoltage);
|
||||||
|
public Voltage LoadBPhaseVoltage => UIntToFloat(_loadBPhaseVoltage);
|
||||||
|
public Voltage LoadCPhaseVoltage => UIntToFloat(_loadCPhaseVoltage);
|
||||||
|
|
||||||
|
public Voltage LoadAbWireVoltage => UIntToFloat(_loadABWireVoltage);
|
||||||
|
public Voltage LoadBcWireVoltage => UIntToFloat(_loadBCWireVoltage);
|
||||||
|
public Voltage LoadCaWireVoltage => UIntToFloat(_loadCAWireVoltage);
|
||||||
|
|
||||||
|
public Frequency LoadVoltageFrequency => UIntToFloat(_loadVoltageFrequency);
|
||||||
|
|
||||||
|
public Current LoadAPhaseCurrent => UIntToFloat(_loadAPhaseCurrent);
|
||||||
|
public Current LoadBPhaseCurrent => UIntToFloat(_loadBPhaseCurrent);
|
||||||
|
public Current LoadCPhaseCurrent => UIntToFloat(_loadCPhaseCurrent);
|
||||||
|
public Current LoadNWireCurrent => UIntToFloat(_loadNWireCurrent);
|
||||||
|
|
||||||
|
public ApparentPower LoadAPhaseApparentPower => UIntToFloat(_loadAPhaseApparentPower);
|
||||||
|
public ApparentPower LoadBPhaseApparentPower => UIntToFloat(_loadBPhaseApparentPower);
|
||||||
|
public ApparentPower LoadCPhaseApparentPower => UIntToFloat(_loadCPhaseApparentPower);
|
||||||
|
|
||||||
|
public ActivePower LoadAPhaseActivePower => UIntToFloat(_loadAPhaseActivePower);
|
||||||
|
public ActivePower LoadBPhaseActivePower => UIntToFloat(_loadBPhaseActivePower);
|
||||||
|
public ActivePower LoadCPhaseActivePower => UIntToFloat(_loadCPhaseActivePower);
|
||||||
|
|
||||||
|
public ReactivePower LoadAPhaseReactivePower => UIntToFloat(_loadAPhaseReactivePower);
|
||||||
|
public ReactivePower LoadBPhaseReactivePower => UIntToFloat(_loadBPhaseReactivePower);
|
||||||
|
public ReactivePower LoadCPhaseReactivePower => UIntToFloat(_loadCPhaseReactivePower);
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Inverter Measurements
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public Voltage InverterAPhaseVoltage => UIntToFloat(_inverterAPhaseVoltage);
|
||||||
|
public Voltage InverterBPhaseVoltage => UIntToFloat(_inverterBPhaseVoltage);
|
||||||
|
public Voltage InverterCPhaseVoltage => UIntToFloat(_inverterCPhaseVoltage);
|
||||||
|
|
||||||
|
public Voltage InverterAbWireVoltage => UIntToFloat(_inverterABWireVoltage);
|
||||||
|
public Voltage InverterBcWireVoltage => UIntToFloat(_inverterBCWireVoltage);
|
||||||
|
public Voltage InverterCaWireVoltage => UIntToFloat(_inverterCAWireVoltage);
|
||||||
|
|
||||||
|
public ActivePower InverterAPhaseActivePower => UIntToFloat(_inverterAPhaseActivePower);
|
||||||
|
public ActivePower InverterBPhaseActivePower => UIntToFloat(_inverterBPhaseActivePower);
|
||||||
|
public ActivePower InverterCPhaseActivePower => UIntToFloat(_inverterCPhaseActivePower);
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// DC/AC Temperature Sensors (°C)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public Temperature DcacTemperature1 => UIntToFloat(_dcacTemperature1);
|
||||||
|
public Temperature DcacTemperature2 => UIntToFloat(_dcacTemperature2);
|
||||||
|
public Temperature DcacTemperature3 => UIntToFloat(_dcacTemperature3);
|
||||||
|
public Temperature DcacTemperature4 => UIntToFloat(_dcacTemperature4);
|
||||||
|
public Temperature DcacTemperature5 => UIntToFloat(_dcacTemperature5);
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Date / Time Information
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public Single Year => UIntToFloat(_year);
|
||||||
|
public Single Month => UIntToFloat(_month);
|
||||||
|
public Single Day => UIntToFloat(_day);
|
||||||
|
public Single Hour => UIntToFloat(_hour);
|
||||||
|
public Single Minute => UIntToFloat(_minute);
|
||||||
|
public Single Second => UIntToFloat(_second);
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Diesel Generator Measurements
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public Voltage DieselGenAPhaseVoltage => ConvertBitPatternToFloat(_dieselGenAPhaseVoltage);
|
||||||
|
public Voltage DieselGenBPhaseVoltage => ConvertBitPatternToFloat(_dieselGenBPhaseVoltage);
|
||||||
|
public Voltage DieselGenCPhaseVoltage => ConvertBitPatternToFloat(_dieselGenCPhaseVoltage);
|
||||||
|
public Voltage DieselGenABWireVoltage => ConvertBitPatternToFloat(_dieselGenABWireVoltage);
|
||||||
|
public Voltage DieselGenBCWireVoltage => ConvertBitPatternToFloat(_dieselGenBCWireVoltage);
|
||||||
|
public Voltage DieselGenCAWireVoltage => ConvertBitPatternToFloat(_dieselGenCAWireVoltage);
|
||||||
|
|
||||||
|
public Frequency DieselGenVoltageFrequency => ConvertBitPatternToFloat(_dieselGenVoltageFrequency);
|
||||||
|
|
||||||
|
public Current DieselGenAPhaseCurrent => ConvertBitPatternToFloat(_dieselGenAPhaseCurrent);
|
||||||
|
public Current DieselGenBPhaseCurrent => ConvertBitPatternToFloat(_dieselGenBPhaseCurrent);
|
||||||
|
public Current DieselGenCPhaseCurrent => ConvertBitPatternToFloat(_dieselGenCPhaseCurrent);
|
||||||
|
public Current DieselGenNWireCurrent => ConvertBitPatternToFloat(_dieselGenNWireCurrent);
|
||||||
|
|
||||||
|
public ApparentPower DieselGenAPhaseApparentPower => ConvertBitPatternToFloat(_dieselGenAPhaseApparentPower);
|
||||||
|
public ApparentPower DieselGenBPhaseApparentPower => ConvertBitPatternToFloat(_dieselGenBPhaseApparentPower);
|
||||||
|
public ApparentPower DieselGenCPhaseApparentPower => ConvertBitPatternToFloat(_dieselGenCPhaseApparentPower);
|
||||||
|
|
||||||
|
public ActivePower DieselGenAPhaseActivePower => ConvertBitPatternToFloat(_dieselGenAPhaseActivePower);
|
||||||
|
public ActivePower DieselGenBPhaseActivePower => ConvertBitPatternToFloat(_dieselGenBPhaseActivePower);
|
||||||
|
public ActivePower DieselGenCPhaseActivePower => ConvertBitPatternToFloat(_dieselGenCPhaseActivePower);
|
||||||
|
|
||||||
|
public ReactivePower DieselGenAPhaseReactivePower => ConvertBitPatternToFloat(_dieselGenAPhaseReactivePower);
|
||||||
|
public ReactivePower DieselGenBPhaseReactivePower => ConvertBitPatternToFloat(_dieselGenBPhaseReactivePower);
|
||||||
|
public ReactivePower DieselGenCPhaseReactivePower => ConvertBitPatternToFloat(_dieselGenCPhaseReactivePower);
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Photovoltaic and Battery Measurements
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public Voltage PvVoltage1 => ConvertBitPatternToFloat(_pvVoltage1);
|
||||||
|
public Current PvCurrent1 => ConvertBitPatternToFloat(_pvCurrent1);
|
||||||
|
public ActivePower PvPower1 => ConvertBitPatternToFloat(_pvPower1);
|
||||||
|
public Voltage PvVoltage2 => ConvertBitPatternToFloat(_pvVoltage2);
|
||||||
|
public Current PvCurrent2 => ConvertBitPatternToFloat(_pvCurrent2);
|
||||||
|
public ActivePower PvPower2 => ConvertBitPatternToFloat(_pvPower2);
|
||||||
|
|
||||||
|
public Voltage BatteryVoltage1 => ConvertBitPatternToFloat(_batteryVoltage1);
|
||||||
|
public Current BatteryCurrent1 => ConvertBitPatternToFloat(_batteryCurrent1);
|
||||||
|
public ActivePower BatteryPower1 => ConvertBitPatternToFloat(_batteryPower1);
|
||||||
|
public Percent BatterySoc1 => ConvertBitPatternToFloat(_batterySoc1);
|
||||||
|
public float BatteryFullLoadDuration1 => ConvertBitPatternToFloat(_batteryFullLoadDuration1);
|
||||||
|
|
||||||
|
public Voltage BatteryVoltage2 => ConvertBitPatternToFloat(_batteryVoltage2);
|
||||||
|
public Current BatteryCurrent2 => ConvertBitPatternToFloat(_batteryCurrent2);
|
||||||
|
public ActivePower BatteryPower2 => ConvertBitPatternToFloat(_batteryPower2);
|
||||||
|
public Percent BatterySoc2 => ConvertBitPatternToFloat(_batterySoc2);
|
||||||
|
public float BatteryFullLoadDuration2 => ConvertBitPatternToFloat(_batteryFullLoadDuration2);
|
||||||
|
|
||||||
|
public Temperature DcdcTemperature1 => ConvertBitPatternToFloat(_dcdcTemperature1);
|
||||||
|
public Temperature DcdcTemperature2 => ConvertBitPatternToFloat(_dcdcTemperature2);
|
||||||
|
public Temperature DcdcTemperature3 => ConvertBitPatternToFloat(_dcdcTemperature3);
|
||||||
|
public Temperature DcdcTemperature4 => ConvertBitPatternToFloat(_dcdcTemperature4);
|
||||||
|
public Temperature DcdcTemperature5 => ConvertBitPatternToFloat(_dcdcTemperature5);
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Energy and Power Summary
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public Energy ElectricityPurchased => ConvertBitPatternToFloat(_electricityPurchased);
|
||||||
|
public Energy ElectricityFed => ConvertBitPatternToFloat(_electricityFed);
|
||||||
|
public Energy SelfGeneratedElectricity => ConvertBitPatternToFloat(_selfGeneratedElectricity);
|
||||||
|
public Energy BatteryCharge => ConvertBitPatternToFloat(_batteryCharge);
|
||||||
|
public Energy BatteryDischarge => ConvertBitPatternToFloat(_batteryDischarge);
|
||||||
|
public Energy LoadPowerConsumption => ConvertBitPatternToFloat(_loadPowerConsumption);
|
||||||
|
public Energy DailySelfGeneratedElectricity => ConvertBitPatternToFloat(_dailySelfGeneratedElectricity);
|
||||||
|
public Energy DailyElectricityPurchased => ConvertBitPatternToFloat(_dailyElectricityPurchased);
|
||||||
|
public Energy DailyElectricityFed => ConvertBitPatternToFloat(_dailyElectricityFed);
|
||||||
|
public Energy DailyBatteryCharge => ConvertBitPatternToFloat(_dailyBatteryCharge);
|
||||||
|
public Energy DailyBatteryDischarge => ConvertBitPatternToFloat(_dailyBatteryDischarge);
|
||||||
|
public Energy DailyLoadPowerConsumption => ConvertBitPatternToFloat(_dailyLoadPowerConsumption);
|
||||||
|
public ActivePower TotalPhotovoltaicPower => ConvertBitPatternToFloat(_totalPhotovoltaicPower);
|
||||||
|
public ActivePower TotalBatteryPower => ConvertBitPatternToFloat(_totalBatteryPower);
|
||||||
|
public ActivePower TotalLoadPower => ConvertBitPatternToFloat(_totalLoadPower);
|
||||||
|
public ActivePower TotalGridPower => ConvertBitPatternToFloat(_totalGridPower);
|
||||||
|
public ActivePower ImportantLoadTotalPower => ConvertBitPatternToFloat(_importantLoadTotalPower);
|
||||||
|
public ActivePower GeneralLoadTotalPower => ConvertBitPatternToFloat(_generalLoadTotalPower);
|
||||||
|
public Voltage Pv3Voltage => ConvertBitPatternToFloat(_pv3Voltage);
|
||||||
|
public Current Pv3Current => ConvertBitPatternToFloat(_pv3Current);
|
||||||
|
public ActivePower Pv3Power => ConvertBitPatternToFloat(_pv3Power);
|
||||||
|
public Voltage Pv4Voltage => ConvertBitPatternToFloat(_pv4Voltage);
|
||||||
|
public Current Pv4Current => ConvertBitPatternToFloat(_pv4Current);
|
||||||
|
public ActivePower Pv4Power => ConvertBitPatternToFloat(_pv4Power);
|
||||||
|
public ActivePower GeneratorTotalPower => ConvertBitPatternToFloat(_generatorTotalPower);
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Manufacturer Information & Software Versions
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public UInt16 ProtocolVersionNo => _protocolVersionNo; // 0x2000
|
||||||
|
public UInt16 DcacSoftwareVersionNo => _dcacSoftwareVersionNo; // 0x2001
|
||||||
|
public UInt16 DcdcSoftwareVersionNo => _dcdcSoftwareVersionNo; // 0x2002
|
||||||
|
public UInt16 ArmSoftwareVersionNo => _armSoftwareVersionNo; // 0x2003
|
||||||
|
public UInt16 CpldVersion => _cpldVersion; // 0x2004
|
||||||
|
public UInt16 AfciSoftwareVersionNo => _afciSoftwareVersionNo; // 0x2005
|
||||||
|
public SinexcelMachineType MachineType => (SinexcelMachineType)_machineType; // 0x2006
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Device Info & Model
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
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 InputFrequency InputFrequencyClass
|
||||||
|
{
|
||||||
|
get => (InputFrequency)_inputFrequencyClass;
|
||||||
|
set => _inputFrequencyClass = (UInt32)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorkingMode WorkingMode
|
||||||
|
{
|
||||||
|
get => (WorkingMode)ConvertBitPatternToFloat(_workingMode);
|
||||||
|
set => _workingMode = (UInt32)value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public GridSwitchMethod GridSwitchMethod
|
||||||
|
{
|
||||||
|
get => (GridSwitchMethod)_methodSwitchMode;
|
||||||
|
set => _methodSwitchMode = (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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fan 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// 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 MaxChargingCurrentBattery1
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_maxChargingCurrentBattery1));
|
||||||
|
set => _maxChargingCurrentBattery1 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MaxDischargingCurrentBattery1
|
||||||
|
{
|
||||||
|
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 MinSocBattery1
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
set => _activeChargeDischargePower = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// 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 MaxChargingCurrentBattery2
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((int)_maxChargingCurrentBattery2));
|
||||||
|
set => _maxChargingCurrentBattery2 = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float MaxDischargingCurrentBattery2
|
||||||
|
{
|
||||||
|
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 float PowerOn
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_powerOn));
|
||||||
|
set => _powerOn = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float PowerOff
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Meter & Battery Control
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public float MeterReverseManualDetection
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_meterReverseManualDetection));
|
||||||
|
set => _meterReverseManualDetection = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Voltage BatteryVoltage
|
||||||
|
{
|
||||||
|
get => (Voltage)ConvertBitPatternToFloat(_batteryVoltage);
|
||||||
|
set => _batteryVoltage = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SinexcelBatteryRating BatteryRating
|
||||||
|
{
|
||||||
|
get => (SinexcelBatteryRating)ConvertBitPatternToFloat(_batteryRating);
|
||||||
|
set => _batteryRating = (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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Electric Meter Operating State
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public MeterStatus ElectricMeterOperatingState
|
||||||
|
{
|
||||||
|
get => (MeterStatus)BitConverter.Int32BitsToSingle(unchecked((Int32)_electricMeterOperatingState)); // 0 = Offline, 1 = Online
|
||||||
|
}
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Electric Meter Voltages (V)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public Voltage ElectricMeterAPhaseVoltage
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_electricMeterAPhaseVoltage));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Voltage ElectricMeterBPhaseVoltage
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_electricMeterBPhaseVoltage));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Voltage ElectricMeterCPhaseVoltage
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_electricMeterCPhaseVoltage));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Electric Meter Currents (A)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public Current ElectricMeterAPhaseCurrent
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_electricMeterAPhaseCurrent));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Current ElectricMeterBPhaseCurrent
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_electricMeterBPhaseCurrent));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Current ElectricMeterCPhaseCurrent
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_electricMeterCPhaseCurrent));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Electric Meter Active Power (kW)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
public ActivePower ElectricMeterAPhaseActivePower
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_electricMeterAPhaseActivePower));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActivePower ElectricMeterBPhaseActivePower
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_electricMeterBPhaseActivePower));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ActivePower ElectricMeterCPhaseActivePower
|
||||||
|
{
|
||||||
|
get => BitConverter.Int32BitsToSingle(unchecked((Int32)_electricMeterCPhaseActivePower));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Conversion methodes
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
private static Single IntToFloat(Int32 intValue)
|
||||||
|
{
|
||||||
|
byte[] intBytes = BitConverter.GetBytes(intValue);
|
||||||
|
float f = BitConverter.ToSingle(intBytes, 0);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Single UIntToFloat(UInt32 uintValue)
|
||||||
|
{
|
||||||
|
byte[] uintBytes = BitConverter.GetBytes(uintValue);
|
||||||
|
float f = BitConverter.ToSingle(uintBytes, 0);
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
public static float ConvertBitPatternToFloat(uint rawValue)
|
||||||
|
{
|
||||||
|
byte[] bytes = BitConverter.GetBytes(rawValue);
|
||||||
|
return BitConverter.ToSingle(bytes, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,313 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using InnovEnergy.Lib.Protocols.Modbus.Reflection.Attributes;
|
||||||
|
using InnovEnergy.Lib.Units;
|
||||||
|
using InnovEnergy.Lib.Units.Power;
|
||||||
|
// ReSharper disable InconsistentNaming
|
||||||
|
#pragma warning disable CS0169 // Field is never used
|
||||||
|
#pragma warning disable CS0649 // Field is never assigned to, and will always have its default value
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL;
|
||||||
|
|
||||||
|
[BigEndian]
|
||||||
|
public partial class SinexcelRecord
|
||||||
|
{
|
||||||
|
/****************************** Input registers ****************************/
|
||||||
|
// Voltages
|
||||||
|
[HoldingRegister<UInt32>(4096)] public UInt32 _gridAPhaseVoltage; // 0x1000
|
||||||
|
[HoldingRegister<UInt32>(4098)] private UInt32 _grid_B_Phase_Voltage; // 0x1002
|
||||||
|
[HoldingRegister<UInt32>(4100)] private UInt32 _grid_C_Phase_Voltage; // 0x1004
|
||||||
|
|
||||||
|
// Line-to-line Voltages (V)
|
||||||
|
[HoldingRegister<UInt32>(4102)] private UInt32 _grid_AB_Wire_Voltage; // 0x1006
|
||||||
|
[HoldingRegister<UInt32>(4104)] private UInt32 _grid_BC_Wire_Voltage; // 0x1008
|
||||||
|
[HoldingRegister<UInt32>(4106)] private UInt32 _grid_CA_Wire_Voltage; // 0x100A
|
||||||
|
|
||||||
|
// Frequency (Hz)
|
||||||
|
[HoldingRegister<UInt32>(4114)] private UInt32 _grid_Voltage_Frequency; // 0x1012
|
||||||
|
|
||||||
|
// Currents (A)
|
||||||
|
[HoldingRegister<UInt32>(4116)] private UInt32 _grid_A_Phase_Current; // 0x1014
|
||||||
|
[HoldingRegister<UInt32>(4118)] private UInt32 _grid_B_Phase_Current; // 0x1016
|
||||||
|
[HoldingRegister<UInt32>(4120)] private UInt32 _grid_C_Phase_Current; // 0x1018
|
||||||
|
[HoldingRegister<UInt32>(4122)] private UInt32 _grid_N_Wire_Current; // 0x101A*/
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Power Measurements
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
// Apparent Power (kVA)
|
||||||
|
[HoldingRegister<UInt32>(4136)] private UInt32 _gridAPhaseApparentPower; // 0x1028
|
||||||
|
[HoldingRegister<UInt32>(4138)] private UInt32 _gridBPhaseApparentPower; // 0x102A
|
||||||
|
[HoldingRegister<UInt32>(4140)] private UInt32 _gridCPhaseApparentPower; // 0x102C
|
||||||
|
|
||||||
|
// Active Power (kW)
|
||||||
|
[HoldingRegister<UInt32>(4142)] private UInt32 _gridAPhaseActivePower; // 0x102E
|
||||||
|
[HoldingRegister<UInt32>(4144)] private UInt32 _gridBPhaseActivePower; // 0x1030
|
||||||
|
[HoldingRegister<UInt32>(4146)] private UInt32 _gridCPhaseActivePower; // 0x1032
|
||||||
|
|
||||||
|
// Reactive Power (kVar)
|
||||||
|
[HoldingRegister<UInt32>(4148)] private UInt32 _gridAPhaseReactivePower; // 0x1034
|
||||||
|
[HoldingRegister<UInt32>(4150)] private UInt32 _gridBPhaseReactivePower; // 0x1036
|
||||||
|
[HoldingRegister<UInt32>(4152)] private UInt32 _gridCPhaseReactivePower; // 0x1038
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Load Voltages (V)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(4166)] private UInt32 _loadAPhaseVoltage; // 0x1046
|
||||||
|
[HoldingRegister<UInt32>(4168)] private UInt32 _loadBPhaseVoltage; // 0x1048
|
||||||
|
[HoldingRegister<UInt32>(4170)] private UInt32 _loadCPhaseVoltage; // 0x104A
|
||||||
|
[HoldingRegister<UInt32>(4172)] private UInt32 _loadABWireVoltage; // 0x104C
|
||||||
|
[HoldingRegister<UInt32>(4174)] private UInt32 _loadBCWireVoltage; // 0x104E
|
||||||
|
[HoldingRegister<UInt32>(4176)] private UInt32 _loadCAWireVoltage; // 0x1050
|
||||||
|
[HoldingRegister<UInt32>(4184)] private UInt32 _loadVoltageFrequency; // 0x1058
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Load Currents (A)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(4186)] private UInt32 _loadAPhaseCurrent; // 0x105A
|
||||||
|
[HoldingRegister<UInt32>(4188)] private UInt32 _loadBPhaseCurrent; // 0x105C
|
||||||
|
[HoldingRegister<UInt32>(4190)] private UInt32 _loadCPhaseCurrent; // 0x105E
|
||||||
|
[HoldingRegister<UInt32>(4192)] private UInt32 _loadNWireCurrent; // 0x1060
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Load Apparent Power (kVA)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(4206)] private UInt32 _loadAPhaseApparentPower; // 0x106E
|
||||||
|
[HoldingRegister<UInt32>(4208)] private UInt32 _loadBPhaseApparentPower; // 0x1070
|
||||||
|
[HoldingRegister<UInt32>(4210)] private UInt32 _loadCPhaseApparentPower; // 0x1072
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Load Active Power (kW)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(4212)] private UInt32 _loadAPhaseActivePower; // 0x1074
|
||||||
|
[HoldingRegister<UInt32>(4214)] private UInt32 _loadBPhaseActivePower; // 0x1076
|
||||||
|
[HoldingRegister<UInt32>(4216)] private UInt32 _loadCPhaseActivePower; // 0x1078
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Load Reactive Power (kVar)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(4218)] private UInt32 _loadAPhaseReactivePower; // 0x107A
|
||||||
|
[HoldingRegister<UInt32>(4220)] private UInt32 _loadBPhaseReactivePower; // 0x107C
|
||||||
|
[HoldingRegister<UInt32>(4222)] private UInt32 _loadCPhaseReactivePower; // 0x107E
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Inverter Voltages (V)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(4242)] private UInt32 _inverterAPhaseVoltage; // 0x1092
|
||||||
|
[HoldingRegister<UInt32>(4244)] private UInt32 _inverterBPhaseVoltage; // 0x1094
|
||||||
|
[HoldingRegister<UInt32>(4246)] private UInt32 _inverterCPhaseVoltage; // 0x1096
|
||||||
|
[HoldingRegister<UInt32>(4248)] private UInt32 _inverterABWireVoltage; // 0x1098
|
||||||
|
[HoldingRegister<UInt32>(4250)] private UInt32 _inverterBCWireVoltage; // 0x109A
|
||||||
|
[HoldingRegister<UInt32>(4252)] private UInt32 _inverterCAWireVoltage; // 0x109C
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Inverter Active Power (kW)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(4288)] private UInt32 _inverterAPhaseActivePower; // 0x10C0
|
||||||
|
[HoldingRegister<UInt32>(4290)] private UInt32 _inverterBPhaseActivePower; // 0x10C2
|
||||||
|
[HoldingRegister<UInt32>(4292)] private UInt32 _inverterCPhaseActivePower; // 0x10C4
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// DC/AC Temperatures (°C)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(4318)] private UInt32 _dcacTemperature1; // 0x10DE
|
||||||
|
[HoldingRegister<UInt32>(4320)] private UInt32 _dcacTemperature2; // 0x10E0
|
||||||
|
[HoldingRegister<UInt32>(4322)] private UInt32 _dcacTemperature3; // 0x10E2
|
||||||
|
[HoldingRegister<UInt32>(4324)] private UInt32 _dcacTemperature4; // 0x10E4
|
||||||
|
[HoldingRegister<UInt32>(4326)] private UInt32 _dcacTemperature5; // 0x10E6
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Date / Time Information
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(4338)] private UInt32 _year; // 0x10F2
|
||||||
|
[HoldingRegister<UInt32>(4340)] private UInt32 _month; // 0x10F4
|
||||||
|
[HoldingRegister<UInt32>(4342)] private UInt32 _day; // 0x10F6
|
||||||
|
[HoldingRegister<UInt32>(4344)] private UInt32 _hour; // 0x10F8
|
||||||
|
[HoldingRegister<UInt32>(4346)] private UInt32 _minute; // 0x10FA
|
||||||
|
[HoldingRegister<UInt32>(4348)] private UInt32 _second; // 0x10FC
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Diesel Generator Measurements
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(4362)] private UInt32 _dieselGenAPhaseVoltage; // 0x110A
|
||||||
|
[HoldingRegister<UInt32>(4364)] private UInt32 _dieselGenBPhaseVoltage; // 0x110C
|
||||||
|
[HoldingRegister<UInt32>(4366)] private UInt32 _dieselGenCPhaseVoltage; // 0x110E
|
||||||
|
[HoldingRegister<UInt32>(4368)] private UInt32 _dieselGenABWireVoltage; // 0x1110
|
||||||
|
[HoldingRegister<UInt32>(4370)] private UInt32 _dieselGenBCWireVoltage; // 0x1112
|
||||||
|
[HoldingRegister<UInt32>(4372)] private UInt32 _dieselGenCAWireVoltage; // 0x1114
|
||||||
|
[HoldingRegister<UInt32>(4380)] private UInt32 _dieselGenVoltageFrequency; // 0x111C
|
||||||
|
[HoldingRegister<UInt32>(4382)] private UInt32 _dieselGenAPhaseCurrent; // 0x111E
|
||||||
|
[HoldingRegister<UInt32>(4384)] private UInt32 _dieselGenBPhaseCurrent; // 0x1120
|
||||||
|
[HoldingRegister<UInt32>(4386)] private UInt32 _dieselGenCPhaseCurrent; // 0x1122
|
||||||
|
[HoldingRegister<UInt32>(4388)] private UInt32 _dieselGenNWireCurrent; // 0x1124
|
||||||
|
[HoldingRegister<UInt32>(4402)] private UInt32 _dieselGenAPhaseApparentPower; // 0x1132
|
||||||
|
[HoldingRegister<UInt32>(4404)] private UInt32 _dieselGenBPhaseApparentPower; // 0x1134
|
||||||
|
[HoldingRegister<UInt32>(4406)] private UInt32 _dieselGenCPhaseApparentPower; // 0x1136
|
||||||
|
[HoldingRegister<UInt32>(4408)] private UInt32 _dieselGenAPhaseActivePower; // 0x1138
|
||||||
|
[HoldingRegister<UInt32>(4410)] private UInt32 _dieselGenBPhaseActivePower; // 0x113A
|
||||||
|
[HoldingRegister<UInt32>(4412)] private UInt32 _dieselGenCPhaseActivePower; // 0x113C
|
||||||
|
[HoldingRegister<UInt32>(4414)] private UInt32 _dieselGenAPhaseReactivePower; // 0x113E
|
||||||
|
[HoldingRegister<UInt32>(4416)] private UInt32 _dieselGenBPhaseReactivePower; // 0x1140
|
||||||
|
[HoldingRegister<UInt32>(4418)] private UInt32 _dieselGenCPhaseReactivePower; // 0x1142
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Photovoltaic and Battery Measurements
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(4608)] private UInt32 _pvVoltage1; // 0x1200
|
||||||
|
[HoldingRegister<UInt32>(4610)] private UInt32 _pvCurrent1; // 0x1202
|
||||||
|
[HoldingRegister<UInt32>(4612)] private UInt32 _pvPower1; // 0x1204
|
||||||
|
[HoldingRegister<UInt32>(4614)] private UInt32 _pvVoltage2; // 0x1206
|
||||||
|
[HoldingRegister<UInt32>(4616)] private UInt32 _pvCurrent2; // 0x1208
|
||||||
|
[HoldingRegister<UInt32>(4618)] private UInt32 _pvPower2; // 0x120A
|
||||||
|
|
||||||
|
[HoldingRegister<UInt32>(4620)] private UInt32 _batteryVoltage1; // 0x120C
|
||||||
|
[HoldingRegister<UInt32>(4622)] private UInt32 _batteryCurrent1; // 0x120E
|
||||||
|
[HoldingRegister<UInt32>(4624)] private UInt32 _batteryPower1; // 0x1210
|
||||||
|
[HoldingRegister<UInt32>(4626)] private UInt32 _batterySoc1; // 0x1212
|
||||||
|
[HoldingRegister<UInt32>(4628)] private UInt32 _batteryFullLoadDuration1; // 0x1214
|
||||||
|
|
||||||
|
[HoldingRegister<UInt32>(4630)] private UInt32 _batteryVoltage2; // 0x1216
|
||||||
|
[HoldingRegister<UInt32>(4632)] private UInt32 _batteryCurrent2; // 0x1218
|
||||||
|
[HoldingRegister<UInt32>(4634)] private UInt32 _batteryPower2; // 0x121A
|
||||||
|
[HoldingRegister<UInt32>(4636)] private UInt32 _batterySoc2; // 0x121C
|
||||||
|
[HoldingRegister<UInt32>(4638)] private UInt32 _batteryFullLoadDuration2; // 0x121E
|
||||||
|
|
||||||
|
[HoldingRegister<UInt32>(4640)] private UInt32 _dcdcTemperature1; // 0x1220
|
||||||
|
[HoldingRegister<UInt32>(4642)] private UInt32 _dcdcTemperature2; // 0x1222
|
||||||
|
[HoldingRegister<UInt32>(4644)] private UInt32 _dcdcTemperature3; // 0x1224
|
||||||
|
[HoldingRegister<UInt32>(4646)] private UInt32 _dcdcTemperature4; // 0x1226
|
||||||
|
[HoldingRegister<UInt32>(4648)] private UInt32 _dcdcTemperature5; // 0x1228
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Energy and Power Summary
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(4672)] private UInt32 _electricityPurchased; // 0x1240
|
||||||
|
[HoldingRegister<UInt32>(4674)] private UInt32 _electricityFed; // 0x1242
|
||||||
|
[HoldingRegister<UInt32>(4700)] private UInt32 _selfGeneratedElectricity; // 0x125C
|
||||||
|
[HoldingRegister<UInt32>(4702)] private UInt32 _batteryCharge; // 0x125E
|
||||||
|
[HoldingRegister<UInt32>(4704)] private UInt32 _batteryDischarge; // 0x1260
|
||||||
|
[HoldingRegister<UInt32>(4706)] private UInt32 _loadPowerConsumption; // 0x1262
|
||||||
|
[HoldingRegister<UInt32>(4708)] private UInt32 _dailySelfGeneratedElectricity; // 0x1264
|
||||||
|
[HoldingRegister<UInt32>(4710)] private UInt32 _dailyElectricityPurchased; // 0x1266
|
||||||
|
[HoldingRegister<UInt32>(4712)] private UInt32 _dailyElectricityFed; // 0x1268
|
||||||
|
[HoldingRegister<UInt32>(4714)] private UInt32 _dailyBatteryCharge; // 0x126A
|
||||||
|
[HoldingRegister<UInt32>(4716)] private UInt32 _dailyBatteryDischarge; // 0x126C
|
||||||
|
[HoldingRegister<UInt32>(4718)] private UInt32 _dailyLoadPowerConsumption; // 0x126E
|
||||||
|
[HoldingRegister<UInt32>(4720)] private UInt32 _totalPhotovoltaicPower; // 0x1270
|
||||||
|
[HoldingRegister<UInt32>(4722)] private UInt32 _totalBatteryPower; // 0x1272
|
||||||
|
[HoldingRegister<UInt32>(4724)] private UInt32 _totalLoadPower; // 0x1274
|
||||||
|
[HoldingRegister<UInt32>(4726)] private UInt32 _totalGridPower; // 0x1276
|
||||||
|
[HoldingRegister<UInt32>(4728)] private UInt32 _importantLoadTotalPower; // 0x1278
|
||||||
|
[HoldingRegister<UInt32>(4730)] private UInt32 _generalLoadTotalPower; // 0x127A
|
||||||
|
[HoldingRegister<UInt32>(4732)] private UInt32 _pv3Voltage; // 0x127C
|
||||||
|
[HoldingRegister<UInt32>(4734)] private UInt32 _pv3Current; // 0x127E
|
||||||
|
[HoldingRegister<UInt32>(4736)] private UInt32 _pv3Power; // 0x1280
|
||||||
|
[HoldingRegister<UInt32>(4738)] private UInt32 _pv4Voltage; // 0x1282
|
||||||
|
[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
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt16>(8192)] private UInt16 _protocolVersionNo; // 0x2000
|
||||||
|
[HoldingRegister<UInt16>(8193)] private UInt16 _dcacSoftwareVersionNo; // 0x2001
|
||||||
|
[HoldingRegister<UInt16>(8194)] private UInt16 _dcdcSoftwareVersionNo; // 0x2002
|
||||||
|
[HoldingRegister<UInt16>(8195)] private UInt16 _armSoftwareVersionNo; // 0x2003
|
||||||
|
[HoldingRegister<UInt16>(8196)] private UInt16 _cpldVersion; // 0x2004
|
||||||
|
[HoldingRegister<UInt16>(8197)] private UInt16 _afciSoftwareVersionNo; // 0x2005
|
||||||
|
[HoldingRegister<UInt16>(8198)] private UInt16 _machineType; // 0x2006
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Device Info & Model
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt16>(8199)] private UInt16 _remain; // 0x2007
|
||||||
|
[HoldingRegister<UInt16>(8200)] private UInt16 _ratedPower; // 0x2008 (value / 100 = kVA)
|
||||||
|
[HoldingRegister<UInt16>(8205)] private UInt16 _model; // 0x200D
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// System configuration / operation registers
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[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>(12300, writable: true)] private UInt32 _workingMode; // 0x300C
|
||||||
|
[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>(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
|
||||||
|
[HoldingRegister<UInt32>(12312, writable: true)] private UInt32 _fanGear; // 0x3018
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// 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>(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
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// 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 _batteryVoltage; // 0x3CA0
|
||||||
|
[HoldingRegister<UInt32>(15522, writable: true)] private UInt32 _batteryRating; // 0x3CA2
|
||||||
|
[HoldingRegister<UInt32>(15524, writable: true)] private UInt32 _battery1Activation; // 0x3CA4
|
||||||
|
[HoldingRegister<UInt32>(15526, writable: true)] private UInt32 _battery2Activation; // 0x3CA6
|
||||||
|
|
||||||
|
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
// Electric Meter Measurements (Read-Only)
|
||||||
|
// ───────────────────────────────────────────────
|
||||||
|
[HoldingRegister<UInt32>(40960)] private UInt32 _electricMeterOperatingState; // 0xA000
|
||||||
|
|
||||||
|
[HoldingRegister<UInt32>(41216)] private UInt32 _electricMeterAPhaseVoltage; // 0xA100
|
||||||
|
[HoldingRegister<UInt32>(41218)] private UInt32 _electricMeterBPhaseVoltage; // 0xA102
|
||||||
|
[HoldingRegister<UInt32>(41220)] private UInt32 _electricMeterCPhaseVoltage; // 0xA104
|
||||||
|
|
||||||
|
[HoldingRegister<UInt32>(41222)] private UInt32 _electricMeterAPhaseCurrent; // 0xA106
|
||||||
|
[HoldingRegister<UInt32>(41224)] private UInt32 _electricMeterBPhaseCurrent; // 0xA108
|
||||||
|
[HoldingRegister<UInt32>(41226)] private UInt32 _electricMeterCPhaseCurrent; // 0xA10A
|
||||||
|
|
||||||
|
[HoldingRegister<UInt32>(41234)] private UInt32 _electricMeterAPhaseActivePower; // 0xA112
|
||||||
|
[HoldingRegister<UInt32>(41236)] private UInt32 _electricMeterBPhaseActivePower; // 0xA114
|
||||||
|
[HoldingRegister<UInt32>(41238)] private UInt32 _electricMeterCPhaseActivePower; // 0xA116
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue