Compare commits
No commits in common. "5e45e51357288424f030a975374f9ae2161bf524" and "612554bc05812f02cd5c2a41a2587c7bc54de041" have entirely different histories.
5e45e51357
...
612554bc05
|
|
@ -1,5 +1,6 @@
|
||||||
|
|
||||||
using InnovEnergy.App.GrowattCommunication.ESS;
|
using InnovEnergy.App.GrowattCommunication.ESS;
|
||||||
|
using InnovEnergy.App.GrowattCommunication.SystemConfig;
|
||||||
|
|
||||||
namespace InnovEnergy.App.GrowattCommunication.DataTypes;
|
namespace InnovEnergy.App.GrowattCommunication.DataTypes;
|
||||||
|
|
||||||
|
|
@ -10,7 +11,5 @@ public class Configuration
|
||||||
public Double MaximumDischargingCurrent { get; set; }
|
public Double MaximumDischargingCurrent { get; set; }
|
||||||
public Double MaximumChargingCurrent { get; set; }
|
public Double MaximumChargingCurrent { get; set; }
|
||||||
public EssMode OperatingPriority { get; set; }
|
public EssMode OperatingPriority { get; set; }
|
||||||
public required Int16 BatteriesCount { get; set; }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ public static class MiddlewareAgent
|
||||||
if (config != null)
|
if (config != null)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"Received a configuration message: GridSetPoint is " + config.GridSetPoint +
|
Console.WriteLine($"Received a configuration message: GridSetPoint is " + config.GridSetPoint +
|
||||||
", MinimumSoC is " + config.MinimumSoC + " and operating priorty is " +config.OperatingPriority + "Number of batteries is " + config.BatteriesCount );
|
", MinimumSoC is " + config.MinimumSoC, " and operating priorty is " +config.OperatingPriority );
|
||||||
|
|
||||||
// Send the reply to the sender's endpoint
|
// Send the reply to the sender's endpoint
|
||||||
_udpListener.Send(replyData, replyData.Length, serverEndpoint);
|
_udpListener.Send(replyData, replyData.Length, serverEndpoint);
|
||||||
|
|
|
||||||
|
|
@ -7,9 +7,9 @@ namespace InnovEnergy.App.GrowattCommunication.MiddlewareClasses;
|
||||||
|
|
||||||
public static class RabbitMqManager
|
public static class RabbitMqManager
|
||||||
{
|
{
|
||||||
private static ConnectionFactory? _factory ;
|
public static ConnectionFactory? Factory ;
|
||||||
private static IConnection ? _connection;
|
public static IConnection ? Connection;
|
||||||
private static IModel? _channel;
|
public static IModel? Channel;
|
||||||
|
|
||||||
public static Boolean SubscribeToQueue(StatusMessage currentSalimaxState, String? s3Bucket,String VpnServerIp)
|
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 { HostName = VpnServerIp };
|
||||||
|
|
||||||
_factory = new ConnectionFactory
|
Factory = new ConnectionFactory
|
||||||
{
|
{
|
||||||
HostName = VpnServerIp,
|
HostName = VpnServerIp,
|
||||||
Port = 5672,
|
Port = 5672,
|
||||||
|
|
@ -27,9 +27,9 @@ public static class RabbitMqManager
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
_connection = _factory.CreateConnection();
|
Connection = Factory.CreateConnection();
|
||||||
_channel = _connection.CreateModel();
|
Channel = Connection.CreateModel();
|
||||||
_channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
|
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");
|
Console.WriteLine("The controller sends its status to the middleware for the first time");
|
||||||
if (s3Bucket != null) InformMiddleware(currentSalimaxState);
|
if (s3Bucket != null) InformMiddleware(currentSalimaxState);
|
||||||
|
|
@ -49,7 +49,7 @@ public static class RabbitMqManager
|
||||||
var message = JsonSerializer.Serialize(status);
|
var message = JsonSerializer.Serialize(status);
|
||||||
var body = Encoding.UTF8.GetBytes(message);
|
var body = Encoding.UTF8.GetBytes(message);
|
||||||
|
|
||||||
_channel.BasicPublish(exchange: string.Empty,
|
Channel.BasicPublish(exchange: string.Empty,
|
||||||
routingKey: "statusQueue",
|
routingKey: "statusQueue",
|
||||||
basicProperties: null,
|
basicProperties: null,
|
||||||
body: body);
|
body: body);
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ using static System.Text.Json.JsonSerializer;
|
||||||
|
|
||||||
namespace InnovEnergy.App.GrowattCommunication.SystemConfig;
|
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
|
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");
|
private static String DefaultConfigFilePath => Path.Combine(Environment.CurrentDirectory, "config.json");
|
||||||
|
|
@ -19,8 +18,6 @@ public class Config //TODO: let IE choose from config files (Json) and connect t
|
||||||
public required Double MaximumDischargingCurrent { get; set; }
|
public required Double MaximumDischargingCurrent { get; set; }
|
||||||
public required Double MaximumChargingCurrent { get; set; }
|
public required Double MaximumChargingCurrent { get; set; }
|
||||||
public required EssMode OperatingPriority { 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; }
|
public required S3Config? S3 { get; set; }
|
||||||
|
|
||||||
|
|
@ -34,8 +31,6 @@ public class Config //TODO: let IE choose from config files (Json) and connect t
|
||||||
MaximumChargingCurrent = 180,
|
MaximumChargingCurrent = 180,
|
||||||
MaximumDischargingCurrent = 180,
|
MaximumDischargingCurrent = 180,
|
||||||
OperatingPriority = EssMode.LoadPriority,
|
OperatingPriority = EssMode.LoadPriority,
|
||||||
BatteriesCount = 0,
|
|
||||||
ModbusProtcolNumber = 1.2,
|
|
||||||
S3 = new()
|
S3 = new()
|
||||||
{
|
{
|
||||||
Bucket = "1-3e5b3069-214a-43ee-8d85-57d72000c19d",
|
Bucket = "1-3e5b3069-214a-43ee-8d85-57d72000c19d",
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,48 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
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; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
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; }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
namespace InnovEnergy.App.SinexcelCommunication.DataTypes;
|
|
||||||
|
|
||||||
public enum SodistoreAlarmState
|
|
||||||
{
|
|
||||||
Green,
|
|
||||||
Orange,
|
|
||||||
Red
|
|
||||||
}
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
namespace InnovEnergy.App.SinexcelCommunication;
|
|
||||||
|
|
||||||
public enum DeviceState
|
|
||||||
{
|
|
||||||
Disabled,
|
|
||||||
Measured,
|
|
||||||
Computed
|
|
||||||
}
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
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; }
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
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}");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,566 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
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);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,79 +0,0 @@
|
||||||
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}";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
#!/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
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,68 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
namespace InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
|
||||||
|
|
||||||
public enum InputFrequency
|
|
||||||
{
|
|
||||||
Hz50 = 0, // 0: 50 Hz (default)
|
|
||||||
Hz60 = 1 // 1: 60 Hz
|
|
||||||
}
|
|
||||||
|
|
@ -1,52 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
<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>
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,610 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,313 +0,0 @@
|
||||||
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