Update Sinexcel record
This commit is contained in:
parent
0db9406b9c
commit
a0c73a8a1b
|
|
@ -0,0 +1,160 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using InnovEnergy.App.SinexcelCommunication.ESS;
|
||||
|
||||
namespace InnovEnergy.App.SinexcelCommunication.AggregationService;
|
||||
|
||||
public class HourlyAccumulator
|
||||
{
|
||||
public DateTime HourStart { get; set; }
|
||||
|
||||
public double StartSelfGeneratedElectricity { get; set; }
|
||||
public double StartElectricityPurchased { get; set; }
|
||||
public double StartElectricityFed { get; set; }
|
||||
public double StartBatteryChargeEnergy { get; set; }
|
||||
public double StartBatteryDischargeEnergy { get; set; }
|
||||
public double StartLoadPowerConsumption { get; set; }
|
||||
|
||||
public double LastSelfGeneratedElectricity { get; set; }
|
||||
public double LastElectricityPurchased { get; set; }
|
||||
public double LastElectricityFed { get; set; }
|
||||
public double LastBatteryChargeEnergy { get; set; }
|
||||
public double LastBatteryDischargeEnergy { get; set; }
|
||||
public double LastLoadPowerConsumption { get; set; }
|
||||
}
|
||||
|
||||
public static class EnergyAggregation
|
||||
{
|
||||
private static HourlyAccumulator? _currentHourAccumulator;
|
||||
private static DateTime? _lastDailySaveDate;
|
||||
|
||||
|
||||
public static HourlyEnergyData? ProcessHourlyData(StatusRecord statusRecord, DateTime timestamp)
|
||||
{
|
||||
var r = statusRecord.InverterRecord;
|
||||
var hourStart = new DateTime(timestamp.Year, timestamp.Month, timestamp.Day, timestamp.Hour, 0, 0);
|
||||
|
||||
// First call
|
||||
if (_currentHourAccumulator == null)
|
||||
{
|
||||
_currentHourAccumulator = new HourlyAccumulator
|
||||
{
|
||||
HourStart = hourStart,
|
||||
|
||||
StartSelfGeneratedElectricity = r.SelfGeneratedElectricity,
|
||||
StartElectricityPurchased = r.ElectricityPurchased,
|
||||
StartElectricityFed = r.ElectricityFed,
|
||||
StartBatteryChargeEnergy = r.BatteryChargeEnergy,
|
||||
StartBatteryDischargeEnergy = r.BatteryDischargeEnergy,
|
||||
StartLoadPowerConsumption = r.LoadPowerConsumption,
|
||||
|
||||
LastSelfGeneratedElectricity = r.SelfGeneratedElectricity,
|
||||
LastElectricityPurchased = r.ElectricityPurchased,
|
||||
LastElectricityFed = r.ElectricityFed,
|
||||
LastBatteryChargeEnergy = r.BatteryChargeEnergy,
|
||||
LastBatteryDischargeEnergy = r.BatteryDischargeEnergy,
|
||||
LastLoadPowerConsumption = r.LoadPowerConsumption
|
||||
};
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Still same hour → just update last values
|
||||
if (_currentHourAccumulator.HourStart == hourStart)
|
||||
{
|
||||
_currentHourAccumulator.LastSelfGeneratedElectricity = r.SelfGeneratedElectricity;
|
||||
_currentHourAccumulator.LastElectricityPurchased = r.ElectricityPurchased;
|
||||
_currentHourAccumulator.LastElectricityFed = r.ElectricityFed;
|
||||
_currentHourAccumulator.LastBatteryChargeEnergy = r.BatteryChargeEnergy;
|
||||
_currentHourAccumulator.LastBatteryDischargeEnergy = r.BatteryDischargeEnergy;
|
||||
_currentHourAccumulator.LastLoadPowerConsumption = r.LoadPowerConsumption;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Hour changed → finalize previous hour
|
||||
var completedHour = new HourlyEnergyData
|
||||
{
|
||||
Timestamp = _currentHourAccumulator.HourStart,
|
||||
|
||||
SelfGeneratedElectricity = SafeDiff(
|
||||
_currentHourAccumulator.LastSelfGeneratedElectricity,
|
||||
_currentHourAccumulator.StartSelfGeneratedElectricity),
|
||||
|
||||
ElectricityPurchased = SafeDiff(
|
||||
_currentHourAccumulator.LastElectricityPurchased,
|
||||
_currentHourAccumulator.StartElectricityPurchased),
|
||||
|
||||
ElectricityFed = SafeDiff(
|
||||
_currentHourAccumulator.LastElectricityFed,
|
||||
_currentHourAccumulator.StartElectricityFed),
|
||||
|
||||
BatteryChargeEnergy = SafeDiff(
|
||||
_currentHourAccumulator.LastBatteryChargeEnergy,
|
||||
_currentHourAccumulator.StartBatteryChargeEnergy),
|
||||
|
||||
BatteryDischargeEnergy = SafeDiff(
|
||||
_currentHourAccumulator.LastBatteryDischargeEnergy,
|
||||
_currentHourAccumulator.StartBatteryDischargeEnergy),
|
||||
|
||||
LoadPowerConsumption = SafeDiff(
|
||||
_currentHourAccumulator.LastLoadPowerConsumption,
|
||||
_currentHourAccumulator.StartLoadPowerConsumption)
|
||||
};
|
||||
|
||||
// Start new hour with current sample
|
||||
_currentHourAccumulator = new HourlyAccumulator
|
||||
{
|
||||
HourStart = hourStart,
|
||||
|
||||
StartSelfGeneratedElectricity = r.SelfGeneratedElectricity,
|
||||
StartElectricityPurchased = r.ElectricityPurchased,
|
||||
StartElectricityFed = r.ElectricityFed,
|
||||
StartBatteryChargeEnergy = r.BatteryChargeEnergy,
|
||||
StartBatteryDischargeEnergy = r.BatteryDischargeEnergy,
|
||||
StartLoadPowerConsumption = r.LoadPowerConsumption,
|
||||
|
||||
LastSelfGeneratedElectricity = r.SelfGeneratedElectricity,
|
||||
LastElectricityPurchased = r.ElectricityPurchased,
|
||||
LastElectricityFed = r.ElectricityFed,
|
||||
LastBatteryChargeEnergy = r.BatteryChargeEnergy,
|
||||
LastBatteryDischargeEnergy = r.BatteryDischargeEnergy,
|
||||
LastLoadPowerConsumption = r.LoadPowerConsumption
|
||||
};
|
||||
|
||||
return completedHour;
|
||||
}
|
||||
|
||||
public static DailyEnergyData? TryCreateDailyData(StatusRecord statusRecord, DateTime timestamp)
|
||||
{
|
||||
if (timestamp is { Hour: 23, Minute: 59 })
|
||||
{
|
||||
if (_lastDailySaveDate != timestamp.Date)
|
||||
{
|
||||
_lastDailySaveDate = timestamp.Date;
|
||||
|
||||
var r = statusRecord.InverterRecord;
|
||||
|
||||
return new DailyEnergyData
|
||||
{
|
||||
Timestamp = timestamp,
|
||||
|
||||
DailySelfGeneratedElectricity = r.DailySelfGeneratedElectricity,
|
||||
DailyElectricityPurchased = r.DailyElectricityPurchased,
|
||||
DailyElectricityFed = r.DailyElectricityFed,
|
||||
BatteryDailyChargeEnergy = r.BatteryDailyChargeEnergy,
|
||||
BatteryDailyDischargeEnergy = r.BatteryDailyDischargeEnergy,
|
||||
DailyLoadPowerConsumption = r.DailyLoadPowerConsumption
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static double SafeDiff(double endValue, double startValue)
|
||||
{
|
||||
var diff = endValue - startValue;
|
||||
return diff < 0 ? 0 : diff;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ public class Configuration
|
|||
public Double MinimumSoC { get; set; }
|
||||
public Double MaximumDischargingCurrent { get; set; }
|
||||
public Double MaximumChargingCurrent { get; set; }
|
||||
public WorkingMode OperatingPriority { get; set; }
|
||||
public OperatingPriority OperatingPriority { get; set; }
|
||||
public Int16 BatteriesCount { get; set; }
|
||||
public Int16 ClusterNumber { get; set; }
|
||||
public Int16 PvNumber { get; set; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace InnovEnergy.App.SinexcelCommunication.DataTypes;
|
||||
|
||||
public enum OperatingPriority
|
||||
{
|
||||
ModeNotSynched = -1,
|
||||
LoadPriority = 0,
|
||||
BatteryPriority = 1,
|
||||
GridPriority = 2,
|
||||
}
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
using InnovEnergy.App.SinexcelCommunication.DataTypes;
|
||||
using InnovEnergy.Lib.Devices.Sinexcel_12K_TL;
|
||||
using InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||
using InnovEnergy.Lib.Units;
|
||||
using InnovEnergy.Lib.Units.Power;
|
||||
|
||||
namespace InnovEnergy.App.SinexcelCommunication.ESS;
|
||||
|
||||
public class InverterRecords
|
||||
{
|
||||
|
||||
public required Energy SelfGeneratedElectricity { get; init; }
|
||||
public required Energy ElectricityPurchased { get; init; }
|
||||
public required Energy ElectricityFed { get; init; }
|
||||
public required Energy BatteryChargeEnergy { get; init; }
|
||||
public required Energy BatteryDischargeEnergy { get; init; }
|
||||
public required Energy LoadPowerConsumption { get; init; }
|
||||
|
||||
public required Energy DailySelfGeneratedElectricity { get; init; }
|
||||
public required Energy DailyElectricityPurchased { get; init; }
|
||||
public required Energy DailyElectricityFed { get; init; }
|
||||
public required Energy BatteryDailyChargeEnergy { get; init; }
|
||||
public required Energy BatteryDailyDischargeEnergy { get; init; }
|
||||
public required Energy DailyLoadPowerConsumption { get; init; }
|
||||
|
||||
// public required ActivePower TotalConsumptionPower { get; init; }
|
||||
public required ActivePower TotalPhotovoltaicPower { get; init; }
|
||||
public required ActivePower TotalBatteryPower { get; init; }
|
||||
public required ActivePower TotalLoadPower { get; init; }
|
||||
public required ActivePower TotalGridPower { get; init; }
|
||||
|
||||
public required OperatingPriority OperatingPriority { get; init; }
|
||||
public required Voltage AvgBatteryVoltage { get; init; }
|
||||
public required Current TotalBatteryCurrent { get; init; }
|
||||
public required Percent AvgBatterySoc { get; init; }
|
||||
public required Percent AvgBatterySoh { get; init; }
|
||||
public required Temperature AvgBatteryTemp { get; init; }
|
||||
public required Percent MinSoc { get; init; }
|
||||
|
||||
public required Current MaxChargeCurrent { get; init; }
|
||||
public required Current MaxDischargingCurrent { get; init; }
|
||||
public required ActivePower GridPower { get; init; }
|
||||
public required Frequency GridFrequency { get; init; }
|
||||
public required ActivePower InverterPower { get; init; }
|
||||
public required EnablePowerLimitation EnableGridExport { get; init; }
|
||||
public required ActivePower GridExportPower { get; init; }
|
||||
|
||||
|
||||
public required IReadOnlyList<SinexcelRecord> Devices { get; init; }
|
||||
|
||||
public static InverterRecords? FromInverters(IReadOnlyList<SinexcelRecord>? records)
|
||||
{
|
||||
if (records is null || records.Count == 0)
|
||||
return null;
|
||||
|
||||
|
||||
return new InverterRecords
|
||||
{
|
||||
Devices = records,
|
||||
|
||||
DailySelfGeneratedElectricity = records.Sum(r => r.DailySelfGeneratedElectricity),
|
||||
DailyElectricityPurchased = records.Sum(r => r.DailyElectricityPurchased),
|
||||
DailyElectricityFed = records.Sum(r => r.DailyElectricityFed),
|
||||
BatteryDailyChargeEnergy = records.Sum(r => r.BatteryDailyChargeEnergy),
|
||||
BatteryDailyDischargeEnergy = records.Sum(r => r.BatteryDailyDischargeEnergy),
|
||||
DailyLoadPowerConsumption = records.Sum(r => r.DailyLoadPowerConsumption),
|
||||
|
||||
SelfGeneratedElectricity = records.Sum(r => r.SelfGeneratedElectricity),
|
||||
ElectricityPurchased = records.Sum(r => r.TotalEnergyToUser),
|
||||
ElectricityFed = records.Sum(r => r.TotalEnergyToGrid),
|
||||
BatteryChargeEnergy = records.Sum(r => r.BatteryCharge),
|
||||
BatteryDischargeEnergy = records.Sum(r => r.BatteryDischarge),
|
||||
LoadPowerConsumption = records.Sum(r => r.LoadPowerConsumption),
|
||||
|
||||
// TotalConsumptionPower = records.Sum(r => r.ConsumptionPower), // consumption same as load
|
||||
TotalPhotovoltaicPower = records.Sum(r => r.TotalPhotovoltaicPower),
|
||||
TotalBatteryPower = records.Sum(r => r.TotalBatteryPower),
|
||||
TotalLoadPower = records.Sum(r => r.TotalLoadPower),
|
||||
TotalGridPower = records.Sum(b => b.TotalGridPower),
|
||||
OperatingPriority = records.Select(r => r.WorkingMode).Distinct().Count() == 1 ? (OperatingPriority)records.First().WorkingMode: OperatingPriority.ModeNotSynched,
|
||||
AvgBatteryVoltage = records.SelectMany(r => new[] { r.Battery1Voltage.Value, r.Battery2Voltage.Value }).Where(v => v > 0).DefaultIfEmpty(0).Average(),
|
||||
TotalBatteryCurrent = records.SelectMany(r => new [] { r.Battery1Current.Value, r.Battery2Current.Value}).Sum(),
|
||||
AvgBatterySoc = records.SelectMany(r => new[] { r.Battery1Soc.Value, r.Battery2Soc.Value }).Average(),
|
||||
AvgBatterySoh = records.SelectMany(r => new[] { r.Battery1Soh.Value, r.Battery2Soh.Value }).Average(),
|
||||
MinSoc = records.SelectMany(r => new[] { r.Battery1BackupSoc, r.Battery2BackupSoc}).Min(),
|
||||
AvgBatteryTemp = records.SelectMany(r => new[] { r.Battery1Temperature.Value, r.Battery2Temperature.Value }).Average(),
|
||||
MaxChargeCurrent = records.SelectMany(r => new[] { r.Battery1MaxChargingCurrent, r.Battery2MaxChargingCurrent }).Min(),
|
||||
MaxDischargingCurrent = records.SelectMany(r => new[] { r.Battery1MaxDischargingCurrent, r.Battery2MaxDischargingCurrent }).Max(),
|
||||
GridPower = records.Sum(r => r.TotalGridPower),
|
||||
GridFrequency = records.Average(r => r.GridVoltageFrequency),
|
||||
InverterPower = records.Sum( r => r.InverterActivePower ),
|
||||
EnableGridExport = records.Select(r => r.EnableGridExport).Distinct().Count() == 1 ? records.First().EnableGridExport: EnablePowerLimitation.Prohibited,
|
||||
GridExportPower = records.Sum(r => r.PowerGridExportLimit)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ namespace InnovEnergy.App.SinexcelCommunication.ESS;
|
|||
|
||||
public record StatusRecord
|
||||
{
|
||||
public required SinexcelRecord InverterRecord { get; set; }
|
||||
public required InverterRecords? InverterRecord { get; set; }
|
||||
|
||||
public required Config Config { get; set; }
|
||||
}
|
||||
|
|
@ -13,20 +13,37 @@ public static class MiddlewareAgent
|
|||
private static IPAddress? _controllerIpAddress;
|
||||
private static EndPoint? _endPoint;
|
||||
|
||||
public static void InitializeCommunicationToMiddleware()
|
||||
public static bool InitializeCommunicationToMiddleware()
|
||||
{
|
||||
_controllerIpAddress = FindVpnIp();
|
||||
if (Equals(IPAddress.None, _controllerIpAddress))
|
||||
try
|
||||
{
|
||||
Console.WriteLine("There is no VPN interface, exiting...");
|
||||
_controllerIpAddress = FindVpnIp();
|
||||
if (Equals(IPAddress.None, _controllerIpAddress))
|
||||
{
|
||||
Console.WriteLine("There is no VPN interface.");
|
||||
_udpListener = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
const int udpPort = 9000;
|
||||
_endPoint = new IPEndPoint(_controllerIpAddress, udpPort);
|
||||
|
||||
_udpListener?.Close();
|
||||
_udpListener?.Dispose();
|
||||
|
||||
_udpListener = new UdpClient();
|
||||
_udpListener.Client.Blocking = false;
|
||||
_udpListener.Client.Bind(_endPoint);
|
||||
|
||||
Console.WriteLine($"UDP listener bound to {_endPoint}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to initialize middleware communication: {ex}");
|
||||
_udpListener = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
const Int32 udpPort = 9000;
|
||||
_endPoint = new IPEndPoint(_controllerIpAddress, udpPort);
|
||||
|
||||
_udpListener = new UdpClient();
|
||||
_udpListener.Client.Blocking = false;
|
||||
_udpListener.Client.Bind(_endPoint);
|
||||
}
|
||||
|
||||
private static IPAddress FindVpnIp()
|
||||
|
|
@ -50,42 +67,96 @@ public static class MiddlewareAgent
|
|||
|
||||
return IPAddress.None;
|
||||
}
|
||||
|
||||
public static Configuration? SetConfigurationFile()
|
||||
{
|
||||
if (_udpListener.Available > 0)
|
||||
try
|
||||
{
|
||||
// Ensure listener is initialized
|
||||
if (_udpListener == null)
|
||||
{
|
||||
Console.WriteLine("UDP listener not initialized, trying to initialize...");
|
||||
InitializeCommunicationToMiddleware();
|
||||
|
||||
if (_udpListener == null)
|
||||
{
|
||||
Console.WriteLine("Failed to initialize UDP listener.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if data is available
|
||||
if (_udpListener.Available <= 0)
|
||||
return null;
|
||||
|
||||
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 message = Encoding.UTF8.GetString(udpMessage);
|
||||
|
||||
Console.WriteLine($"Received raw UDP message from {serverEndpoint}: {message}");
|
||||
|
||||
var config = JsonSerializer.Deserialize<Configuration>(message);
|
||||
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
Console.WriteLine($"Received a configuration message: " +
|
||||
"MinimumSoC is " + config.MinimumSoC + " and operating priorty is " +config.OperatingPriority + "Number of batteries is " + config.BatteriesCount
|
||||
+ "Maximum Charging current is "+ config.MaximumChargingCurrent + "/n" + "Maximum Discharging current is " + config.MaximumDischargingCurrent
|
||||
+ "StartTimeChargeandDischargeDayandTime is" + config.StartTimeChargeandDischargeDayandTime + "StopTimeChargeandDischargeDayandTime is" + config.StopTimeChargeandDischargeDayandTime
|
||||
+ "TimeChargeandDischargePowert is " + config.TimeChargeandDischargePower + " Control permission is" + config.ControlPermission);
|
||||
|
||||
// Send the reply to the sender's endpoint
|
||||
Console.WriteLine(
|
||||
$"Received a configuration message:\n" +
|
||||
$"MinimumSoC: {config.MinimumSoC}\n" +
|
||||
$"OperatingPriority: {config.OperatingPriority}\n" +
|
||||
$"Number of batteries: {config.BatteriesCount}\n" +
|
||||
$"Maximum Charging current: {config.MaximumChargingCurrent}\n" +
|
||||
$"Maximum Discharging current: {config.MaximumDischargingCurrent}\n" +
|
||||
$"StartTimeChargeandDischargeDayandTime: {config.StartTimeChargeandDischargeDayandTime}\n" +
|
||||
$"StopTimeChargeandDischargeDayandTime: {config.StopTimeChargeandDischargeDayandTime}\n" +
|
||||
$"TimeChargeandDischargePower: {config.TimeChargeandDischargePower}\n" +
|
||||
$"ControlPermission: {config.ControlPermission}"
|
||||
);
|
||||
|
||||
// Send ACK
|
||||
var replyMessage = "ACK";
|
||||
var replyData = Encoding.UTF8.GetBytes(replyMessage);
|
||||
|
||||
_udpListener.Send(replyData, replyData.Length, serverEndpoint);
|
||||
Console.WriteLine($"Replied to {serverEndpoint}: {replyMessage}");
|
||||
|
||||
return config;
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Received UDP message but failed to deserialize Configuration.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (_endPoint != null && !_endPoint.Equals(_udpListener.Client.LocalEndPoint as IPEndPoint))
|
||||
catch (SocketException ex)
|
||||
{
|
||||
Console.WriteLine("UDP address has changed, rebinding...");
|
||||
Console.WriteLine($"Socket error in SetConfigurationFile: {ex}");
|
||||
|
||||
// Recover by reinitializing
|
||||
try
|
||||
{
|
||||
_udpListener?.Close();
|
||||
_udpListener?.Dispose();
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
|
||||
_udpListener = null;
|
||||
InitializeCommunicationToMiddleware();
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (JsonException ex)
|
||||
{
|
||||
Console.WriteLine($"JSON deserialization error: {ex}");
|
||||
return null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Unexpected error in SetConfigurationFile: {ex}");
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
using System.IO.Compression;
|
||||
using System.Diagnostics;
|
||||
using System.IO.Compression;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
|
@ -24,6 +25,7 @@ using Formatting = Newtonsoft.Json.Formatting;
|
|||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
using static InnovEnergy.App.SinexcelCommunication.MiddlewareClasses.MiddlewareAgent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using InnovEnergy.App.SinexcelCommunication.AggregationService;
|
||||
using InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Protocol;
|
||||
using static InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType.WorkingMode;
|
||||
|
|
@ -35,12 +37,12 @@ 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(5);
|
||||
private const UInt16 NbrOfFileToConcatenate = 15; // add this to config file
|
||||
private static UInt16 _fileCounter = 0;
|
||||
private static Channel _sinexcelChannel1;
|
||||
private static Channel _sinexcelChannel2;
|
||||
|
||||
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(10);
|
||||
private const UInt16 NbrOfFileToConcatenate = 15; // add this to config file
|
||||
private static UInt16 _fileCounter = 0;
|
||||
private static List<Channel> _sinexcelChannel;
|
||||
private static DateTime? _lastUploadedAggregatedDate;
|
||||
private static DailyEnergyData? _pendingDailyData;
|
||||
private static readonly String SwVersionNumber = " V1.00." + DateTime.Today;
|
||||
private const String VpnServerIp = "10.2.0.11";
|
||||
private static Boolean _subscribedToQueue = false;
|
||||
|
|
@ -57,19 +59,29 @@ internal static class Program
|
|||
private const Int32 HeartbeatIntervalSeconds = 60;
|
||||
|
||||
|
||||
// move all this to config file
|
||||
private const String Port1 = "/dev/ttyUSB0";
|
||||
private const String Port2 = "/dev/ttyUSB1";
|
||||
|
||||
private const Byte SlaveId = 1;
|
||||
private const Parity Parity = 0; //none
|
||||
private const Int32 StopBits = 1;
|
||||
private const Int32 BaudRate = 115200;
|
||||
private const Int32 DataBits = 8;
|
||||
|
||||
|
||||
public static async Task Main(String[] args)
|
||||
{
|
||||
_sinexcelChannel1 = new SerialPortChannel(Port1, BaudRate, Parity, DataBits, StopBits);
|
||||
_sinexcelChannel2 = new SerialPortChannel(Port2, BaudRate, Parity, DataBits, StopBits);
|
||||
var config = Config.Load();
|
||||
var d = config.Devices;
|
||||
var serial = d.Serial;
|
||||
|
||||
|
||||
Channel CreateChannel(SodiDevice device) => device.DeviceState == DeviceState.Disabled
|
||||
? new NullChannel()
|
||||
: new SerialPortChannel(device.Port,serial.BaudRate,serial.Parity,serial.DataBits,serial.StopBits);
|
||||
|
||||
_sinexcelChannel = new List<Channel>()
|
||||
{
|
||||
CreateChannel(d.Inverter1),
|
||||
CreateChannel(d.Inverter2),
|
||||
CreateChannel(d.Inverter3),
|
||||
CreateChannel(d.Inverter4)
|
||||
};
|
||||
|
||||
|
||||
InitializeCommunicationToMiddleware();
|
||||
while (true)
|
||||
|
|
@ -91,21 +103,24 @@ internal static class Program
|
|||
Watchdog.NotifyReady();
|
||||
|
||||
Console.WriteLine("Starting Sinexcel Communication, SW Version : " + SwVersionNumber);
|
||||
|
||||
var sinexcelDevice1 = new SinexcelDevice(_sinexcelChannel1, SlaveId);
|
||||
var sinexcelDevice2 = new SinexcelDevice(_sinexcelChannel2, SlaveId);
|
||||
|
||||
var devices = _sinexcelChannel
|
||||
.Where(ch => ch is not NullChannel)
|
||||
.Select(ch => new SinexcelDevice(ch, SlaveId))
|
||||
.ToList();
|
||||
|
||||
StatusRecord? ReadStatus()
|
||||
{
|
||||
var config = Config.Load();
|
||||
var sinexcelRecord1 = sinexcelDevice1.Read();
|
||||
var sinexcelRecord2 = sinexcelDevice2.Read();
|
||||
var config = Config.Load();
|
||||
var listOfInverterRecord = devices
|
||||
.Select(device => device.Read())
|
||||
.ToList();
|
||||
|
||||
InverterRecords? inverterRecords = InverterRecords.FromInverters(listOfInverterRecord);
|
||||
|
||||
return new StatusRecord
|
||||
{
|
||||
InverterRecord1 = sinexcelRecord1,
|
||||
InverterRecord2 = sinexcelRecord2,
|
||||
InverterRecord = inverterRecords,
|
||||
Config = config // load from disk every iteration, so config can be changed while running
|
||||
};
|
||||
}
|
||||
|
|
@ -128,103 +143,46 @@ internal static class Program
|
|||
try
|
||||
{
|
||||
Watchdog.NotifyAlive();
|
||||
|
||||
var startTime = DateTime.Now;
|
||||
Console.WriteLine("***************************** Reading Battery Data *********************************************");
|
||||
Console.WriteLine(startTime.ToString("HH:mm:ss.fff ")+ "Start Reading");
|
||||
|
||||
// the order matter of the next three lines
|
||||
var statusrecord = ReadStatus();
|
||||
if (statusrecord == null)
|
||||
return null;
|
||||
_ = CreateAggregatedData(statusrecord);
|
||||
|
||||
Console.WriteLine(" ************************************************ Inverter 1 ************************************************ ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.SystemDateTime + " SystemDateTime ");
|
||||
|
||||
Console.WriteLine( statusrecord.InverterRecord1.TotalPhotovoltaicPower + " TotalPhotovoltaicPower ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.TotalBatteryPower + " TotalBatteryPower ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.TotalLoadPower + " TotalLoadPower ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.TotalGridPower + " TotalGridPower ");
|
||||
var invDevices = statusrecord.InverterRecord?.Devices;
|
||||
|
||||
|
||||
Console.WriteLine( statusrecord.InverterRecord1.Battery1Power + " Battery1Power ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.Battery1Soc + " Battery1Soc ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.Battery1BackupSoc + " Battery1BackupSoc ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.Battery1MinSoc + " Battery1MinSoc ");
|
||||
|
||||
Console.WriteLine( statusrecord.InverterRecord1.Battery2Power + " Battery2Power ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.Battery2Soc + " Battery2Soc ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.Battery2BackupSoc + " Battery2BackupSoc ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.Battery2MinSoc + " Battery2MinSoc ");
|
||||
|
||||
Console.WriteLine( statusrecord.InverterRecord1.EnableGridExport + " EnableGridExport ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.PowerGridExportLimit + " PowerGridExportLimit ");
|
||||
if (invDevices != null)
|
||||
{
|
||||
var index = 1;
|
||||
foreach (var inverter in invDevices)
|
||||
PrintInverterData(inverter, index++);
|
||||
}
|
||||
|
||||
Console.WriteLine( statusrecord.InverterRecord1.PowerOn + " PowerOn ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.PowerOff + " PowerOff ");
|
||||
|
||||
|
||||
Console.WriteLine( statusrecord.InverterRecord1.WorkingMode + " WorkingMode ");
|
||||
|
||||
Console.WriteLine( statusrecord.InverterRecord1.GridSwitchMethod + " GridSwitchMethod ");
|
||||
|
||||
Console.WriteLine( statusrecord.InverterRecord1.ThreePhaseWireSystem + " ThreePhaseWireSystem ");
|
||||
|
||||
Console.WriteLine(" ************************************************ Inverter 2 ************************************************ ");
|
||||
|
||||
Console.WriteLine( statusrecord.InverterRecord2.SystemDateTime + " SystemDateTime ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.TotalPhotovoltaicPower + " TotalPhotovoltaicPower ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.TotalBatteryPower + " TotalBatteryPower ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.TotalLoadPower + " TotalLoadPower ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.TotalGridPower + " TotalGridPower ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.Battery1Power + " Battery1Power ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.Battery1Soc + " Battery1Soc ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.Battery1BackupSoc + " Battery1BackupSoc ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.Battery1MinSoc + " Battery1MinSoc ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.Battery2Power + " Battery2Power ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.Battery2Soc + " Battery2Soc ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.Battery2BackupSoc + " Battery2BackupSoc ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.Battery2MinSoc + " Battery2MinSoc ");
|
||||
|
||||
Console.WriteLine( statusrecord.InverterRecord2.EnableGridExport + " EnableGridExport ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.PowerGridExportLimit + " PowerGridExportLimit ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.PowerOn + " PowerOn ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.PowerOff + " PowerOff ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.WorkingMode + " WorkingMode ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.GridSwitchMethod + " GridSwitchMethod ");
|
||||
Console.WriteLine( statusrecord.InverterRecord2.ThreePhaseWireSystem + " ThreePhaseWireSystem ");
|
||||
/*
|
||||
Console.WriteLine( statusrecord.InverterRecord1.RepetitiveWeeks + " RepetitiveWeeks ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.EffectiveStartDate + " EffectiveStartDate ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.EffectiveEndDate + " EffectiveEndDate ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.ChargingPowerPeriod1 + " ChargingPowerPeriod1 ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.DishargingPowerPeriod1 + " dischargingPowerPeriod1 ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.ChargeStartTimePeriod1 + " ChargeStartTimePeriod1 ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.ChargeEndTimePeriod1 + " ChargeEndTimePeriod1 ");
|
||||
|
||||
Console.WriteLine( statusrecord.InverterRecord1.DischargeStartTimePeriod1 + " DischargeStartTimePeriod1 ");
|
||||
Console.WriteLine( statusrecord.InverterRecord1.DischargeEndTimePeriod1 + " DischargeEndTimePeriod1 ");*/
|
||||
|
||||
SendSalimaxStateAlarm(GetSodiHomeStateAlarm(statusrecord),statusrecord);
|
||||
statusrecord.ControlConstants();
|
||||
SendSalimaxStateAlarm(GetSodiHomeStateAlarm(statusrecord),statusrecord);
|
||||
statusrecord.ControlConstants();
|
||||
|
||||
Console.WriteLine( " ************************************ We are writing ************************************");
|
||||
var startWritingTime = DateTime.Now;
|
||||
Console.WriteLine(startWritingTime.ToString("HH:mm:ss.fff ") +"start Writing");
|
||||
statusrecord?.Config.Save(); // save the config file
|
||||
|
||||
if (statusrecord is { Config.ControlPermission: true })
|
||||
if (statusrecord is { Config.ControlPermission: true, InverterRecord.Devices: not null })
|
||||
{
|
||||
Console.WriteLine(" We have the Right to Write");
|
||||
sinexcelDevice1.Write(statusrecord.InverterRecord1);
|
||||
sinexcelDevice2.Write(statusrecord.InverterRecord2);
|
||||
Console.WriteLine("We have the Right to Write");
|
||||
|
||||
foreach (var pair in devices.Zip(statusrecord.InverterRecord.Devices))
|
||||
pair.First.Write(pair.Second);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine(" Nooooooo We cant' have the Right to Write");
|
||||
Console.WriteLine("Nooooooo We can't have the Right to Write");
|
||||
}
|
||||
|
||||
var stop = DateTime.Now;
|
||||
Console.WriteLine("***************************** Writing finished *********************************************");
|
||||
Console.WriteLine(stop.ToString("HH:mm:ss.fff ")+ "Cycle end");
|
||||
return statusrecord;
|
||||
}
|
||||
catch (CrcException e)
|
||||
|
|
@ -239,60 +197,216 @@ internal static class Program
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this is synchronous because :
|
||||
//
|
||||
// it only appends one line once per hour and once per day
|
||||
//
|
||||
// it should not meaningfully affect a 10-second loop
|
||||
|
||||
private static async Task CreateAggregatedData(StatusRecord statusRecord)
|
||||
{
|
||||
|
||||
DateTime now = DateTime.Now;
|
||||
string baseFolder = AppContext.BaseDirectory;
|
||||
|
||||
// 1) Finalize previous hour if hour changed
|
||||
var hourlyData = EnergyAggregation.ProcessHourlyData(statusRecord, now);
|
||||
/*if (hourlyData != null)
|
||||
{
|
||||
AggregatedDataFileWriter.AppendHourlyData(hourlyData, baseFolder);
|
||||
}*/
|
||||
if (hourlyData != null)
|
||||
{
|
||||
AggregatedDataFileWriter.AppendHourlyData(hourlyData, baseFolder);
|
||||
|
||||
if (_pendingDailyData != null && hourlyData.Timestamp.Hour == 23)
|
||||
{
|
||||
AggregatedDataFileWriter.AppendDailyData(_pendingDailyData, baseFolder);
|
||||
_pendingDailyData = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 2) Save daily line near end of day
|
||||
var dailyData = EnergyAggregation.TryCreateDailyData(statusRecord, now);
|
||||
if (dailyData != null)
|
||||
{
|
||||
_pendingDailyData = dailyData;
|
||||
//AggregatedDataFileWriter.AppendDailyData(dailyData, baseFolder);
|
||||
}
|
||||
|
||||
// 3) After midnight, upload yesterday's completed file once
|
||||
var yesterday = now.Date.AddDays(-1);
|
||||
|
||||
if (now.Hour == 0 && now.Minute == 0)
|
||||
{
|
||||
if (_lastUploadedAggregatedDate != yesterday)
|
||||
{
|
||||
Console.WriteLine(" We are inside the lastuploaded Aggregate");
|
||||
string filePath = Path.Combine(
|
||||
baseFolder,
|
||||
"AggregatedData",
|
||||
yesterday.ToString("ddMMyyyy") + ".json");
|
||||
|
||||
bool uploaded = await PushAggregatedFileToS3(filePath, statusRecord);
|
||||
|
||||
if (uploaded)
|
||||
{
|
||||
_lastUploadedAggregatedDate = yesterday;
|
||||
Console.WriteLine($"Uploaded aggregated file for {yesterday:ddMMyyyy}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"Uploaded failed for {yesterday:ddMMyyyy}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<Boolean> PushAggregatedFileToS3( String localFilePath, StatusRecord statusRecord)
|
||||
{
|
||||
var s3Config = statusRecord.Config.S3;
|
||||
if (s3Config is null)
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
if (!File.Exists(localFilePath))
|
||||
{
|
||||
Console.WriteLine($"File not found: {localFilePath}");
|
||||
return false;
|
||||
}
|
||||
|
||||
var jsonString = await File.ReadAllTextAsync(localFilePath);
|
||||
|
||||
// Example S3 object name: 09032026.json
|
||||
var s3Path = Path.GetFileName(localFilePath);
|
||||
var request = s3Config.CreatePutRequest(s3Path);
|
||||
var base64String = Convert.ToBase64String(Encoding.UTF8.GetBytes(jsonString));
|
||||
|
||||
using var content = new StringContent(base64String, Encoding.UTF8, "application/base64");
|
||||
|
||||
Console.WriteLine("Sending Content-Type: application/base64; charset=utf-8");
|
||||
Console.WriteLine($"S3 Path: {s3Path}");
|
||||
|
||||
var response = await request.PutAsync(content);
|
||||
|
||||
if (response.StatusCode != 200)
|
||||
{
|
||||
Console.WriteLine("ERROR: PUT");
|
||||
var error = await response.GetStringAsync();
|
||||
Console.WriteLine(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Uploaded successfully: {s3Path}");
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"PushAggregatedFileToS3 failed: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void ControlConstants(this StatusRecord? statusrecord)
|
||||
{
|
||||
if (statusrecord == null) return;
|
||||
if (statusrecord?.InverterRecord?.Devices == null) return;
|
||||
|
||||
// Compute once (same for all inverters)
|
||||
var config = statusrecord.Config;
|
||||
|
||||
var isChargePeriod = IsNowInsideDateAndTime(
|
||||
config.StartTimeChargeandDischargeDayandTime,
|
||||
config.StopTimeChargeandDischargeDayandTime
|
||||
);
|
||||
|
||||
statusrecord.InverterRecord1.Battery1BackupSoc = (Single)statusrecord.Config.MinSoc ;
|
||||
statusrecord.InverterRecord1.Battery2BackupSoc = (Single)statusrecord.Config.MinSoc ;
|
||||
statusrecord.InverterRecord1.RepetitiveWeeks = SinexcelWeekDays.All;
|
||||
|
||||
|
||||
var isChargePeriod = IsNowInsideDateAndTime(statusrecord.Config.StartTimeChargeandDischargeDayandTime, statusrecord.Config.StopTimeChargeandDischargeDayandTime);
|
||||
|
||||
|
||||
Console.WriteLine("Are we inside the charge/Discharge time " + isChargePeriod);
|
||||
|
||||
if (statusrecord.Config.OperatingPriority != TimeChargeDischarge)
|
||||
foreach (var inverter in statusrecord.InverterRecord.Devices)
|
||||
{
|
||||
statusrecord.InverterRecord1.WorkingMode = statusrecord.Config.OperatingPriority;
|
||||
}
|
||||
else if (statusrecord.Config.OperatingPriority == TimeChargeDischarge && isChargePeriod)
|
||||
{
|
||||
statusrecord.InverterRecord1.WorkingMode = statusrecord.Config.OperatingPriority;
|
||||
// constants for every inverter
|
||||
inverter.Battery1BackupSoc = (float)config.MinSoc;
|
||||
inverter.Battery2BackupSoc = (float)config.MinSoc;
|
||||
inverter.RepetitiveWeeks = SinexcelWeekDays.All;
|
||||
|
||||
if (statusrecord.Config.TimeChargeandDischargePower > 0)
|
||||
var operatingMode = config.OperatingPriority switch
|
||||
{
|
||||
statusrecord.InverterRecord1.EffectiveStartDate = statusrecord.Config.StartTimeChargeandDischargeDayandTime.Date;
|
||||
statusrecord.InverterRecord1.EffectiveEndDate = statusrecord.Config.StopTimeChargeandDischargeDayandTime.Date;
|
||||
statusrecord.InverterRecord1.ChargingPowerPeriod1 = Math.Abs(statusrecord.Config.TimeChargeandDischargePower);
|
||||
statusrecord.InverterRecord1.ChargeStartTimePeriod1 = statusrecord.Config.StartTimeChargeandDischargeDayandTime.TimeOfDay;
|
||||
statusrecord.InverterRecord1.ChargeEndTimePeriod1 = statusrecord.Config.StopTimeChargeandDischargeDayandTime.TimeOfDay;
|
||||
|
||||
statusrecord.InverterRecord1.DischargeStartTimePeriod1 = TimeSpan.Zero;
|
||||
statusrecord.InverterRecord1.DischargeEndTimePeriod1 = TimeSpan.Zero;
|
||||
OperatingPriority.LoadPriority => SpontaneousSelfUse,
|
||||
OperatingPriority.BatteryPriority => TimeChargeDischarge,
|
||||
OperatingPriority.GridPriority => PrioritySellElectricity,
|
||||
_ => SpontaneousSelfUse
|
||||
};
|
||||
|
||||
if (operatingMode!= TimeChargeDischarge)
|
||||
{
|
||||
inverter.WorkingMode = operatingMode;
|
||||
}
|
||||
else if (isChargePeriod)
|
||||
{
|
||||
inverter.WorkingMode = operatingMode;
|
||||
inverter.EffectiveStartDate = config.StartTimeChargeandDischargeDayandTime.Date;
|
||||
inverter.EffectiveEndDate = config.StopTimeChargeandDischargeDayandTime.Date;
|
||||
|
||||
var power = config.TimeChargeandDischargePower;
|
||||
|
||||
if (power > 0)
|
||||
{
|
||||
inverter.ChargingPowerPeriod1 = Math.Abs(power);
|
||||
inverter.ChargeStartTimePeriod1 = config.StartTimeChargeandDischargeDayandTime.TimeOfDay;
|
||||
inverter.ChargeEndTimePeriod1 = config.StopTimeChargeandDischargeDayandTime.TimeOfDay;
|
||||
|
||||
inverter.DischargeStartTimePeriod1 = TimeSpan.Zero;
|
||||
inverter.DischargeEndTimePeriod1 = TimeSpan.Zero;
|
||||
}
|
||||
else
|
||||
{
|
||||
inverter.DishargingPowerPeriod1 = Math.Abs(power);
|
||||
inverter.DischargeStartTimePeriod1 = config.StartTimeChargeandDischargeDayandTime.TimeOfDay;
|
||||
inverter.DischargeEndTimePeriod1 = config.StopTimeChargeandDischargeDayandTime.TimeOfDay;
|
||||
|
||||
inverter.ChargeStartTimePeriod1 = TimeSpan.Zero;
|
||||
inverter.ChargeEndTimePeriod1 = TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
statusrecord.InverterRecord1.EffectiveStartDate = statusrecord.Config.StartTimeChargeandDischargeDayandTime.Date;
|
||||
statusrecord.InverterRecord1.EffectiveEndDate = statusrecord.Config.StopTimeChargeandDischargeDayandTime.Date;
|
||||
statusrecord.InverterRecord1.DishargingPowerPeriod1 = Math.Abs(statusrecord.Config.TimeChargeandDischargePower);
|
||||
statusrecord.InverterRecord1.DischargeStartTimePeriod1 = statusrecord.Config.StartTimeChargeandDischargeDayandTime.TimeOfDay;
|
||||
statusrecord.InverterRecord1.DischargeEndTimePeriod1 = statusrecord.Config.StopTimeChargeandDischargeDayandTime.TimeOfDay;
|
||||
|
||||
statusrecord.InverterRecord1.ChargeStartTimePeriod1 = TimeSpan.Zero;
|
||||
statusrecord.InverterRecord1.ChargeEndTimePeriod1 = TimeSpan.Zero;
|
||||
inverter.WorkingMode = SpontaneousSelfUse;
|
||||
}
|
||||
|
||||
|
||||
inverter.PowerOn = 1;
|
||||
inverter.PowerOff = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
statusrecord.InverterRecord1.WorkingMode = SpontaneousSelfUse;
|
||||
}
|
||||
statusrecord.InverterRecord1.PowerOn = 1;
|
||||
statusrecord.InverterRecord1.PowerOff = 0;
|
||||
//statusrecord.InverterRecord.FaultClearing = 1;
|
||||
}
|
||||
|
||||
static void PrintInverterData(SinexcelRecord r, int index)
|
||||
{
|
||||
Console.WriteLine($" ************************************************ Inverter {index} ************************************************ ");
|
||||
|
||||
//Console.WriteLine($"{r.SystemDateTime} SystemDateTime");
|
||||
Console.WriteLine($"{r.TotalPhotovoltaicPower} TotalPhotovoltaicPower");
|
||||
Console.WriteLine($"{r.TotalBatteryPower} TotalBatteryPower");
|
||||
Console.WriteLine($"{r.TotalLoadPower} TotalLoadPower");
|
||||
Console.WriteLine($"{r.TotalGridPower} TotalGridPower");
|
||||
|
||||
Console.WriteLine($"{r.Battery1Power} Battery1Power");
|
||||
Console.WriteLine($"{r.Battery1Soc} Battery1Soc");
|
||||
Console.WriteLine($"{r.Battery1BackupSoc} Battery1BackupSoc");
|
||||
Console.WriteLine($"{r.Battery1MinSoc} Battery1MinSoc");
|
||||
|
||||
Console.WriteLine($"{r.Battery2Power} Battery2Power");
|
||||
Console.WriteLine($"{r.Battery2Soc} Battery2Soc");
|
||||
Console.WriteLine($"{r.Battery2BackupSoc} Battery2BackupSoc");
|
||||
Console.WriteLine($"{r.Battery2MinSoc} Battery2MinSoc");
|
||||
|
||||
Console.WriteLine($"{r.EnableGridExport} EnableGridExport");
|
||||
Console.WriteLine($"{r.PowerGridExportLimit} PowerGridExportLimit");
|
||||
|
||||
Console.WriteLine($"{r.PowerOn} PowerOn");
|
||||
Console.WriteLine($"{r.PowerOff} PowerOff");
|
||||
Console.WriteLine($"{r.WorkingMode} WorkingMode");
|
||||
Console.WriteLine($"{r.GridSwitchMethod} GridSwitchMethod");
|
||||
Console.WriteLine($"{r.ThreePhaseWireSystem} ThreePhaseWireSystem");
|
||||
|
||||
Console.WriteLine();
|
||||
}
|
||||
|
||||
private static bool IsNowInsideDateAndTime(DateTime effectiveStart, DateTime effectiveEnd)
|
||||
|
|
@ -309,7 +423,7 @@ internal static class Program
|
|||
|
||||
private static StatusMessage GetSodiHomeStateAlarm(StatusRecord? record)
|
||||
{
|
||||
var s3Bucket = Config.Load().S3?.Bucket;
|
||||
var s3Bucket = record?.Config.S3?.Bucket; // this should not load the config file, only use the one from status record TO change this in other project
|
||||
|
||||
var alarmList = new List<AlarmOrWarning>();
|
||||
var warningList = new List<AlarmOrWarning>();
|
||||
|
|
@ -424,13 +538,7 @@ internal static class Program
|
|||
var stateChanged = currentSalimaxState.Status != _prevSodiohomeAlarmState;
|
||||
var contentChanged = HasErrorsOrWarningsChanged(currentSalimaxState);
|
||||
var needsHeartbeat = (DateTime.Now - _lastHeartbeatTime).TotalSeconds >= HeartbeatIntervalSeconds;
|
||||
Console.WriteLine($"subscribedNow={subscribedNow}");
|
||||
Console.WriteLine($"_subscribedToQueue={_subscribedToQueue}");
|
||||
Console.WriteLine($"stateChanged={stateChanged}");
|
||||
Console.WriteLine($"contentChanged={contentChanged}");
|
||||
Console.WriteLine($"needsHeartbeat={needsHeartbeat}");
|
||||
Console.WriteLine($"s3Bucket null? {s3Bucket == null}");
|
||||
|
||||
|
||||
if (s3Bucket == null)
|
||||
{
|
||||
Console.WriteLine("⚠ S3 bucket not configured. Skipping middleware send.");
|
||||
|
|
@ -526,47 +634,48 @@ internal static class Program
|
|||
|
||||
}
|
||||
|
||||
private static async Task<Boolean> SaveModbusTcpFile(StatusRecord status)
|
||||
private static async Task<Boolean> SaveModbusTcpFile(StatusRecord status)
|
||||
{
|
||||
var modbusData = new Dictionary<String, UInt16>();
|
||||
|
||||
// SYSTEM DATA
|
||||
var result1 = ConvertToModbusRegisters((status.Config.ModbusProtcolNumber * 10), "UInt16", 30001); // this to be updated to modbusTCP version
|
||||
var result2 = ConvertToModbusRegisters(status.InverterRecord1.SystemDateTime.ToUnixTime(), "UInt32", 30002);
|
||||
// SYSTEM DATA
|
||||
var result3 = ConvertToModbusRegisters(status.InverterRecord1.WorkingMode, "UInt16", 30004);
|
||||
|
||||
// BATTERY SUMMARY (assuming single battery [0]) // this to be improved
|
||||
try
|
||||
{
|
||||
// SYSTEM DATA
|
||||
var result1 = ConvertToModbusRegisters((status.Config.ModbusProtcolNumber * 10), "UInt16", 30001); // this to be updated to modbusTCP version
|
||||
var result2 = ConvertToModbusRegisters(DateTimeOffset.Now.ToUnixTimeSeconds(), "UInt32", 30002);
|
||||
// SYSTEM DATA
|
||||
var result3 = ConvertToModbusRegisters(status.InverterRecord.OperatingPriority, "UInt16", 30004);
|
||||
|
||||
|
||||
var result4 = ConvertToModbusRegisters((status.Config.BatteriesCount), "UInt16", 31000);
|
||||
var result8 = ConvertToModbusRegisters((status.InverterRecord1.Battery1Voltage.Value * 10), "UInt16", 31001);
|
||||
var result12 = ConvertToModbusRegisters((status.InverterRecord1.Battery2Voltage.Value * 10), "Int16", 31002);
|
||||
var result13 = ConvertToModbusRegisters((status.InverterRecord1.Battery1Current.Value * 10), "Int32", 31003);
|
||||
var result16 = ConvertToModbusRegisters((status.InverterRecord1.Battery2Current.Value * 10), "Int32", 31005);
|
||||
var result9 = ConvertToModbusRegisters((status.InverterRecord1.Battery1Soc.Value * 100), "UInt16", 31007);
|
||||
var result14 = ConvertToModbusRegisters((status.InverterRecord1.Battery2Soc.Value * 100), "UInt16", 31008);
|
||||
var result5 = ConvertToModbusRegisters((status.InverterRecord1.TotalBatteryPower.Value * 10), "Int32", 31009);
|
||||
var result8 = ConvertToModbusRegisters((0), "UInt16", 31001); // this is ignored as dosen't exist in Sinexcel
|
||||
var result12 = ConvertToModbusRegisters((status.InverterRecord.AvgBatteryVoltage.Value * 10), "Int16", 31002);
|
||||
var result13 = ConvertToModbusRegisters((status.InverterRecord.TotalBatteryCurrent.Value * 10), "Int32", 31003);
|
||||
var result16 = ConvertToModbusRegisters((status.InverterRecord.AvgBatterySoc.Value * 100), "UInt16", 31005);
|
||||
var result9 = ConvertToModbusRegisters((status.InverterRecord.TotalBatteryPower.Value * 10), "Int32", 31006);
|
||||
var result14 = ConvertToModbusRegisters((status.InverterRecord.MinSoc.Value * 100), "UInt16", 31008);
|
||||
var result55 = ConvertToModbusRegisters(100 * 100, "UInt16", 31009); //this is ignored as dosen't exist in Sinexcel
|
||||
var result5 = ConvertToModbusRegisters((status.InverterRecord.AvgBatterySoh.Value * 100), "UInt16", 31009);
|
||||
|
||||
var result7 = ConvertToModbusRegisters((status.InverterRecord1.Battery1BackupSoc * 100), "UInt16", 31011);
|
||||
var result20 = ConvertToModbusRegisters((status.InverterRecord1.Battery2BackupSoc * 100), "UInt16", 31012);
|
||||
var result15 = ConvertToModbusRegisters((status.InverterRecord1.Battery1Soh.Value * 100), "UInt16", 31013);
|
||||
var result26 = ConvertToModbusRegisters((status.InverterRecord1.Battery2Soh.Value * 100), "UInt16", 31014);
|
||||
var result21 = ConvertToModbusRegisters((status.InverterRecord1.Battery1MaxChargingCurrent * 10), "UInt16", 31016);
|
||||
var result22 = ConvertToModbusRegisters((status.InverterRecord1.Battery1MaxDischargingCurrent * 10), "UInt16", 31017);
|
||||
|
||||
var result7 = ConvertToModbusRegisters((status.InverterRecord.AvgBatteryTemp.Value * 100), "Int16", 31011);
|
||||
var result20 = ConvertToModbusRegisters((status.InverterRecord.MaxChargeCurrent.Value * 10), "UInt16", 31012);
|
||||
var result15 = ConvertToModbusRegisters((status.InverterRecord.MaxDischargingCurrent.Value * 10), "UInt16", 31013);
|
||||
var result26 = ConvertToModbusRegisters(60 * 10, "UInt16", 31014); //this is ignored as dosen't exist in Sinexcel
|
||||
|
||||
var result18 = ConvertToModbusRegisters((status.InverterRecord1.PvTotalPower * 10), "UInt32", 32000);
|
||||
var result19 = ConvertToModbusRegisters((status.InverterRecord1.GridPower * 10), "Int32", 33000);
|
||||
var result23 = ConvertToModbusRegisters((status.InverterRecord1.GridVoltageFrequency * 10), "UInt16", 33002);
|
||||
var result18 = ConvertToModbusRegisters((status.InverterRecord.TotalPhotovoltaicPower.Value * 10), "UInt32", 32000);
|
||||
var result19 = ConvertToModbusRegisters((status.InverterRecord.TotalGridPower.Value * 10), "Int32", 33000);
|
||||
var result23 = ConvertToModbusRegisters((status.InverterRecord.GridFrequency.Value * 10), "UInt16", 33002);
|
||||
|
||||
var result24 = ConvertToModbusRegisters((status.InverterRecord1.WorkingMode), "UInt16", 34000);
|
||||
var result25 = ConvertToModbusRegisters((status.InverterRecord1.InverterActivePower * 10), "Int32", 34001);
|
||||
var result29 = ConvertToModbusRegisters((status.InverterRecord1.EnableGridExport ), "UInt16", 34003);
|
||||
var result27 = ConvertToModbusRegisters((status.InverterRecord1.PowerGridExportLimit ), "Int16", 34004);
|
||||
|
||||
var result24 = ConvertToModbusRegisters((status.InverterRecord.OperatingPriority), "UInt16", 34000);
|
||||
var result25 = ConvertToModbusRegisters((status.InverterRecord.InverterPower.Value * 10), "Int32", 34001);
|
||||
var result29 = ConvertToModbusRegisters((status.InverterRecord.EnableGridExport ), "UInt16", 35002);
|
||||
var result27 = ConvertToModbusRegisters((status.InverterRecord.GridExportPower.Value ), "Int16", 35003);
|
||||
// Merge all results into one dictionary
|
||||
var allResults = new[]
|
||||
{
|
||||
result1, result2, result3, result4, result5, result23, result24, result25, result29, result27, result26, result7, result8, result9, result16, result20, result12, result13, result14, result15, result18, result19, result21, result22
|
||||
result1, result2, result3, result4, result5, result23, result24, result25, result29, result27, result26, result7, result8, result9, result16, result20, result12, result13, result14, result15, result18, result19
|
||||
, result55
|
||||
};
|
||||
|
||||
foreach (var result in allResults)
|
||||
|
|
@ -580,11 +689,16 @@ internal static class Program
|
|||
var json = JsonSerializer.Serialize(modbusData, new JsonSerializerOptions { WriteIndented = true });
|
||||
await File.WriteAllTextAsync("/home/inesco/SodiStoreHome/ModbusTCP/modbus_tcp_data.json", json);
|
||||
|
||||
//Console.WriteLine("JSON file written successfully.");
|
||||
//Console.WriteLine(json);
|
||||
var stopTime = DateTime.Now;
|
||||
Console.WriteLine(stopTime.ToString("HH:mm:ss.fff" )+ " Finish the loop");
|
||||
// Console.WriteLine("JSON file written successfully.");
|
||||
// Console.WriteLine(json);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static Dictionary<string, ushort> ConvertToModbusRegisters(object value, string outputType, int startingAddress)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using InnovEnergy.App.SinexcelCommunication.DataTypes;
|
||||
using InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using static System.Text.Json.JsonSerializer;
|
||||
|
|
@ -10,17 +11,21 @@ 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 DeviceConfig Devices { get; set; }
|
||||
//public required Boolean DynamicPricingEnabled { get; set; }
|
||||
//public required DynamicPricingMode DynamicPricingMode { get; set; }
|
||||
//public required Decimal CheapPrice { get; set; }
|
||||
//public required Decimal HighPrice { get; set; }
|
||||
public required Double MinSoc { get; set; }
|
||||
public required Double GridSetPoint { get; set; }
|
||||
public required Double MaximumDischargingCurrent { get; set; }
|
||||
public required Double MaximumChargingCurrent { get; set; }
|
||||
public required WorkingMode OperatingPriority { get; set; }
|
||||
public required OperatingPriority OperatingPriority { get; set; }
|
||||
public required Int16 BatteriesCount { get; set; }
|
||||
public required Int16 ClusterNumber { get; set; }
|
||||
public required Int16 PvNumber { get; set; }
|
||||
|
|
@ -34,15 +39,26 @@ public class Config
|
|||
|
||||
public required S3Config? S3 { get; set; }
|
||||
|
||||
private static String? LastSavedData { get; set; }
|
||||
private static String? LastSavedData { get; set; }
|
||||
private static DateTime? LoadedWriteTimeUtc { get; set; }
|
||||
|
||||
public static Config Default => new()
|
||||
{
|
||||
Devices = new ()
|
||||
{
|
||||
Serial = new() {BaudRate = 115200, Parity = 0, StopBits = 1, DataBits = 8},
|
||||
Inverter1 = new() {DeviceState = DeviceState.Measured, Port = "/dev/ttyUSB0", SlaveId = 1},
|
||||
Inverter2 = new() {DeviceState = DeviceState.Measured, Port = "/dev/ttyUSB1", SlaveId = 1},
|
||||
Inverter3 = new() {DeviceState = DeviceState.Measured, Port = "/dev/ttyUSB3", SlaveId = 1},
|
||||
Inverter4 = new() {DeviceState = DeviceState.Measured, Port = "/dev/ttyUSB4", SlaveId = 1},
|
||||
},
|
||||
//DynamicPricingEnabled = false,
|
||||
//DynamicPricingMode = DynamicPricingMode.Disabled,
|
||||
MinSoc = 20,
|
||||
GridSetPoint = 0,
|
||||
MaximumChargingCurrent = 180,
|
||||
MaximumDischargingCurrent = 180,
|
||||
OperatingPriority = WorkingMode.TimeChargeDischarge,
|
||||
OperatingPriority = OperatingPriority.LoadPriority,
|
||||
BatteriesCount = 0,
|
||||
ClusterNumber = 0,
|
||||
PvNumber = 0,
|
||||
|
|
@ -67,6 +83,10 @@ public class Config
|
|||
{
|
||||
var configFilePath = path ?? DefaultConfigFilePath;
|
||||
|
||||
var currentWriteTime = File.GetLastWriteTimeUtc(configFilePath);
|
||||
if (currentWriteTime != LoadedWriteTimeUtc)
|
||||
throw new IOException("Config file changed on disk since it was loaded; refusing to overwrite."); // to prevent an overwriting while an external changes happended in the meantime
|
||||
|
||||
try
|
||||
{
|
||||
var jsonString = Serialize(this, JsonOptions);
|
||||
|
|
@ -84,13 +104,15 @@ public class Config
|
|||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
public static Config Load(String? path = null)
|
||||
{
|
||||
var configFilePath = path ?? DefaultConfigFilePath;
|
||||
try
|
||||
{
|
||||
var jsonString = File.ReadAllText(configFilePath);
|
||||
// LoadedWriteTimeUtc = File.GetLastWriteTimeUtc(configFilePath);
|
||||
|
||||
return Deserialize<Config>(jsonString)!;
|
||||
}
|
||||
catch (Exception e)
|
||||
|
|
@ -98,6 +120,31 @@ public class Config
|
|||
$"Failed to read config file {configFilePath}, using default config\n{e}".WriteLine();
|
||||
return Default;
|
||||
}
|
||||
}*/
|
||||
|
||||
public static Config Load(string? path = null)
|
||||
{
|
||||
var configFilePath = path ?? DefaultConfigFilePath; // ✅ handle null first
|
||||
|
||||
try
|
||||
{
|
||||
// Now safe to call any File/Path API
|
||||
var json = File.ReadAllText(configFilePath);
|
||||
|
||||
var cfg = Deserialize<Config>(json, JsonOptions)
|
||||
?? throw new InvalidOperationException("Config deserialized to null.");
|
||||
|
||||
// Optional: store last write time / last saved json
|
||||
LoadedWriteTimeUtc = File.GetLastWriteTimeUtc(configFilePath);
|
||||
LastSavedData = Serialize(cfg, JsonOptions); // if you use the save-skip logic
|
||||
|
||||
return cfg;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
$"Failed to read config file {configFilePath}\n{e}".WriteLine();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Config> LoadAsync(String? path = null)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
namespace InnovEnergy.App.SinexcelCommunication.SystemConfig;
|
||||
|
||||
public record DeviceConfig
|
||||
{
|
||||
public required SerialLineConfig Serial { get; init; }
|
||||
|
||||
public required SodiDevice Inverter1 { get; init; }
|
||||
public required SodiDevice Inverter2 { get; init; }
|
||||
public required SodiDevice Inverter3 { get; init; }
|
||||
public required SodiDevice Inverter4 { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace InnovEnergy.App.SinexcelCommunication.SystemConfig;
|
||||
|
||||
public enum DeviceState
|
||||
{
|
||||
Disabled,
|
||||
Measured,
|
||||
Computed
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
using System.IO.Ports;
|
||||
|
||||
namespace InnovEnergy.App.SinexcelCommunication.SystemConfig;
|
||||
|
||||
public sealed class SerialLineConfig
|
||||
{
|
||||
public required Int32 BaudRate { get; init; } = 115200;
|
||||
public required Parity Parity { get; init; } = 0; //none
|
||||
public required Int32 DataBits { get; init; } = 8;
|
||||
public required Int32 StopBits { get; init; } = 1;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
namespace InnovEnergy.App.SinexcelCommunication.SystemConfig;
|
||||
|
||||
public class SodiDevice
|
||||
{
|
||||
public required DeviceState DeviceState { get; init; }
|
||||
public required String Port { get; init; }
|
||||
public required Byte SlaveId { get; init; }
|
||||
}
|
||||
|
|
@ -6,12 +6,14 @@ username='inesco'
|
|||
root_password='Sodistore0918425'
|
||||
|
||||
release_flag_file="./bin/Release/$dotnet_version/linux-arm64/publish/.release.flag"
|
||||
DOTNET="/snap/dotnet-sdk_60/current/dotnet"
|
||||
|
||||
|
||||
set -e
|
||||
|
||||
echo -e "\n============================ Build ============================\n"
|
||||
|
||||
dotnet publish \
|
||||
"$DOTNET" publish \
|
||||
./SinexcelCommunication.csproj \
|
||||
-p:PublishTrimmed=false \
|
||||
-c Release \
|
||||
|
|
|
|||
|
|
@ -49,4 +49,10 @@ public enum SinexcelMachineMode
|
|||
{
|
||||
Single = 0, // Default
|
||||
Parallel = 1
|
||||
}
|
||||
|
||||
public enum EnablePowerLimitation
|
||||
{
|
||||
Prohibited = 0, // Default
|
||||
Enable = 1
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
using System.ComponentModel;
|
||||
using InnovEnergy.Lib.Devices.Sinexcel_12K_TL.DataType;
|
||||
using InnovEnergy.Lib.Units;
|
||||
using InnovEnergy.Lib.Units.Power;
|
||||
|
|
@ -193,7 +194,7 @@ public partial class SinexcelRecord
|
|||
|
||||
|
||||
|
||||
private Int16 _factorFromKwtoW = 1000;
|
||||
private readonly Int16 _factorFromKwtoW = 1000;
|
||||
// ───────────────────────────────────────────────
|
||||
// Public API — Decoded Float Values
|
||||
// ───────────────────────────────────────────────
|
||||
|
|
@ -295,11 +296,12 @@ public partial class SinexcelRecord
|
|||
public UInt16 Minute => (UInt16) ConvertBitPatternToFloat(_minute);
|
||||
public UInt16 Second => (UInt16) ConvertBitPatternToFloat(_second);
|
||||
|
||||
public DateTime SystemDateTime => new(Year, Month, Day, Hour, Minute, Second);
|
||||
// public DateTime SystemDateTime => new(Year, Month, Day, Hour, Minute, Second);
|
||||
|
||||
// ───────────────────────────────────────────────
|
||||
// Diesel Generator Measurements
|
||||
// ───────────────────────────────────────────────
|
||||
/*
|
||||
public Voltage DieselGenAPhaseVoltage => ConvertBitPatternToFloat(_dieselGenAPhaseVoltage);
|
||||
public Voltage DieselGenBPhaseVoltage => ConvertBitPatternToFloat(_dieselGenBPhaseVoltage);
|
||||
public Voltage DieselGenCPhaseVoltage => ConvertBitPatternToFloat(_dieselGenCPhaseVoltage);
|
||||
|
|
@ -324,7 +326,7 @@ public partial class SinexcelRecord
|
|||
|
||||
public ReactivePower DieselGenAPhaseReactivePower => ConvertBitPatternToFloat(_dieselGenAPhaseReactivePower) * _factorFromKwtoW;
|
||||
public ReactivePower DieselGenBPhaseReactivePower => ConvertBitPatternToFloat(_dieselGenBPhaseReactivePower) * _factorFromKwtoW;
|
||||
public ReactivePower DieselGenCPhaseReactivePower => ConvertBitPatternToFloat(_dieselGenCPhaseReactivePower) * _factorFromKwtoW;
|
||||
public ReactivePower DieselGenCPhaseReactivePower => ConvertBitPatternToFloat(_dieselGenCPhaseReactivePower) * _factorFromKwtoW;*/
|
||||
|
||||
// ───────────────────────────────────────────────
|
||||
// Photovoltaic and Battery Measurements
|
||||
|
|
@ -645,14 +647,14 @@ public partial class SinexcelRecord
|
|||
set => _battery2BackupSOC = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
|
||||
}
|
||||
// to be tested
|
||||
public float EnableGridExport
|
||||
public EnablePowerLimitation EnableGridExport
|
||||
|
||||
{
|
||||
get => BitConverter.Int32BitsToSingle(unchecked((int)_enableGridExport));
|
||||
set => _enableGridExport = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
|
||||
get => (EnablePowerLimitation)ConvertBitPatternToFloat(_enableGridExport); //(Boolean)BitConverter.Int32BitsToSingle(unchecked((int)_enableGridExport));
|
||||
set => _enableGridExport = (UInt32)value;
|
||||
}
|
||||
|
||||
public float PowerGridExportLimit
|
||||
public ActivePower PowerGridExportLimit
|
||||
{
|
||||
get => BitConverter.Int32BitsToSingle(unchecked((int)_powerGridExportLimit));
|
||||
set => _powerGridExportLimit = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
|
||||
|
|
@ -813,15 +815,17 @@ public partial class SinexcelRecord
|
|||
public Percent Battery1SocSecondvalue => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab1Soc)); // 0xB106 %
|
||||
public Percent Battery1Soh => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab1Soh)); // 0xB108 %
|
||||
|
||||
|
||||
// Energy (kW·h)
|
||||
public float Battery2TotalChargingEnergy => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2TotalChargingEnergy)); // 0xB1FC
|
||||
public float Battery2TotalDischargedEnergy => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2TotalDischargedEnergy)); // 0xB1FE
|
||||
|
||||
// Pack Voltage / Current / Temperature
|
||||
public Voltage Battery2PackTotalVoltage => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2PackTotalVoltage)); // 0xB200 (0.01 V resolution)
|
||||
public Current Battery2PackTotalCurrent => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2PackTotalCurrent)); // 0xB202 (0.01 A resolution)
|
||||
public Percent Battery2Socsecondvalue => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2Soc)); // 0xB206 %
|
||||
public Percent Battery2Soh => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2Soh)); // 0xB208 %
|
||||
public Voltage Battery2PackTotalVoltage => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2PackTotalVoltage)); // 0xB200 (0.01 V resolution)
|
||||
public Current Battery2PackTotalCurrent => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2PackTotalCurrent)); // 0xB202 (0.01 A resolution)
|
||||
public Temperature Battery2Temperature => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2Temperature)); // 0xB104 (0.01 °C resolution per spec)
|
||||
public Percent Battery2Socsecondvalue => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2Soc)); // 0xB206 %
|
||||
public Percent Battery2Soh => BitConverter.Int32BitsToSingle(unchecked((Int32)_batteryCab2Soh)); // 0xB208 %
|
||||
|
||||
|
||||
// Repetitive-week mask (bit-mapped 0–6 = Sun–Sat)
|
||||
|
|
@ -930,5 +934,9 @@ public partial class SinexcelRecord
|
|||
byte[] bytes = BitConverter.GetBytes(rawValue);
|
||||
return BitConverter.ToSingle(bytes, 0);
|
||||
}
|
||||
|
||||
|
||||
private static UInt32 ConvertFloatToBitPattern(float value)
|
||||
{
|
||||
return BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -290,16 +290,17 @@ public partial class SinexcelRecord
|
|||
// ───────────────────────────────────────────────
|
||||
// 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
|
||||
[HoldingRegister<UInt32>(4338/*, writable: true*/)] private UInt32 _year; // 0x10F2
|
||||
[HoldingRegister<UInt32>(4340/*, writable: true*/)] private UInt32 _month; // 0x10F4
|
||||
[HoldingRegister<UInt32>(4342/*, writable: true*/)] private UInt32 _day; // 0x10F6
|
||||
[HoldingRegister<UInt32>(4344/*, writable: true*/)] private UInt32 _hour; // 0x10F8
|
||||
[HoldingRegister<UInt32>(4346/*, writable: true*/)] private UInt32 _minute; // 0x10FA
|
||||
[HoldingRegister<UInt32>(4348/*, writable: true*/)] 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
|
||||
|
|
@ -318,8 +319,8 @@ public partial class SinexcelRecord
|
|||
[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
|
||||
[HoldingRegister<UInt32>(4416)] private UInt32 _dieselGenBPhaseReactivePower; // 0x1140*
|
||||
[HoldingRegister<UInt32>(4418)] private UInt32 _dieselGenCPhaseReactivePower; // 0x1142*/
|
||||
|
||||
// ───────────────────────────────────────────────
|
||||
// Photovoltaic and Battery Measurements
|
||||
|
|
|
|||
Loading…
Reference in New Issue