Compare commits
4 Commits
612554bc05
...
5e45e51357
| Author | SHA1 | Date |
|---|---|---|
|
|
5e45e51357 | |
|
|
b1b307a12f | |
|
|
84b8446ec3 | |
|
|
711f054a06 |
|
|
@ -1,6 +1,5 @@
|
|||
|
||||
using InnovEnergy.App.GrowattCommunication.ESS;
|
||||
using InnovEnergy.App.GrowattCommunication.SystemConfig;
|
||||
|
||||
namespace InnovEnergy.App.GrowattCommunication.DataTypes;
|
||||
|
||||
|
|
@ -11,5 +10,7 @@ public class Configuration
|
|||
public Double MaximumDischargingCurrent { get; set; }
|
||||
public Double MaximumChargingCurrent { get; set; }
|
||||
public EssMode OperatingPriority { get; set; }
|
||||
public required Int16 BatteriesCount { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ public static class MiddlewareAgent
|
|||
if (config != null)
|
||||
{
|
||||
Console.WriteLine($"Received a configuration message: GridSetPoint is " + config.GridSetPoint +
|
||||
", MinimumSoC is " + config.MinimumSoC, " and operating priorty is " +config.OperatingPriority );
|
||||
", MinimumSoC is " + config.MinimumSoC + " and operating priorty is " +config.OperatingPriority + "Number of batteries is " + config.BatteriesCount );
|
||||
|
||||
// Send the reply to the sender's endpoint
|
||||
_udpListener.Send(replyData, replyData.Length, serverEndpoint);
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@ namespace InnovEnergy.App.GrowattCommunication.MiddlewareClasses;
|
|||
|
||||
public static class RabbitMqManager
|
||||
{
|
||||
public static ConnectionFactory? Factory ;
|
||||
public static IConnection ? Connection;
|
||||
public static IModel? Channel;
|
||||
private static ConnectionFactory? _factory ;
|
||||
private static IConnection ? _connection;
|
||||
private static IModel? _channel;
|
||||
|
||||
public static Boolean SubscribeToQueue(StatusMessage currentSalimaxState, String? s3Bucket,String VpnServerIp)
|
||||
{
|
||||
|
|
@ -17,7 +17,7 @@ public static class RabbitMqManager
|
|||
{
|
||||
//_factory = new ConnectionFactory { HostName = VpnServerIp };
|
||||
|
||||
Factory = new ConnectionFactory
|
||||
_factory = new ConnectionFactory
|
||||
{
|
||||
HostName = VpnServerIp,
|
||||
Port = 5672,
|
||||
|
|
@ -27,9 +27,9 @@ public static class RabbitMqManager
|
|||
|
||||
};
|
||||
|
||||
Connection = Factory.CreateConnection();
|
||||
Channel = Connection.CreateModel();
|
||||
Channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
|
||||
_connection = _factory.CreateConnection();
|
||||
_channel = _connection.CreateModel();
|
||||
_channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
|
||||
|
||||
Console.WriteLine("The controller sends its status to the middleware for the first time");
|
||||
if (s3Bucket != null) InformMiddleware(currentSalimaxState);
|
||||
|
|
@ -49,7 +49,7 @@ public static class RabbitMqManager
|
|||
var message = JsonSerializer.Serialize(status);
|
||||
var body = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
Channel.BasicPublish(exchange: string.Empty,
|
||||
_channel.BasicPublish(exchange: string.Empty,
|
||||
routingKey: "statusQueue",
|
||||
basicProperties: null,
|
||||
body: body);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using static System.Text.Json.JsonSerializer;
|
|||
|
||||
namespace InnovEnergy.App.GrowattCommunication.SystemConfig;
|
||||
|
||||
[SuppressMessage("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic access otherwise can break functionality when trimming application code")]
|
||||
public class Config //TODO: let IE choose from config files (Json) and connect to GUI
|
||||
{
|
||||
private static String DefaultConfigFilePath => Path.Combine(Environment.CurrentDirectory, "config.json");
|
||||
|
|
@ -18,6 +19,8 @@ public class Config //TODO: let IE choose from config files (Json) and connect t
|
|||
public required Double MaximumDischargingCurrent { get; set; }
|
||||
public required Double MaximumChargingCurrent { get; set; }
|
||||
public required EssMode OperatingPriority { get; set; }
|
||||
public required Int16 BatteriesCount { get; set; }
|
||||
public required Double ModbusProtcolNumber { get; set; }
|
||||
|
||||
public required S3Config? S3 { get; set; }
|
||||
|
||||
|
|
@ -31,6 +34,8 @@ public class Config //TODO: let IE choose from config files (Json) and connect t
|
|||
MaximumChargingCurrent = 180,
|
||||
MaximumDischargingCurrent = 180,
|
||||
OperatingPriority = EssMode.LoadPriority,
|
||||
BatteriesCount = 0,
|
||||
ModbusProtcolNumber = 1.2,
|
||||
S3 = new()
|
||||
{
|
||||
Bucket = "1-3e5b3069-214a-43ee-8d85-57d72000c19d",
|
||||
|
|
|
|||
|
|
@ -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