diff --git a/csharp/App/Backend/Websockets/WebsockerManager.cs b/csharp/App/Backend/Websockets/WebsockerManager.cs
index 0161b0c5f..5568f66c7 100644
--- a/csharp/App/Backend/Websockets/WebsockerManager.cs
+++ b/csharp/App/Backend/Websockets/WebsockerManager.cs
@@ -55,22 +55,22 @@ public static class WebsocketManager
//Console.WriteLine("Installation ID is "+installationConnection.Key);
if (installationConnection.Value.Product == (int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) < TimeSpan.FromMinutes(60)){
Console.WriteLine("Installation ID is "+installationConnection.Key + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
-
-
}
if (installationConnection.Value.Product==(int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(60))
{
- //Console.WriteLine("Installation ID is "+installationConnection.Key + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
+
//Console.WriteLine("installationConnection.Value.Timestamp is "+installationConnection.Value.Timestamp);
//Console.WriteLine("timestamp now is is "+(DateTime.Now));
Installation installation = Db.Installations.FirstOrDefault(f => f.Product == (int)ProductType.Salidomo && f.Id == installationConnection.Key);
+ Console.WriteLine("Installation ID is "+installation.Name + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
installation.Status = (int)StatusType.Offline;
installation.Apply(Db.Update);
installationConnection.Value.Status = (int)StatusType.Offline;
if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);}
+ else{Console.WriteLine("NONE IS CONNECTED TO THAT INSTALLATION-------------------------------------------------------------");}
}
}
Console.WriteLine("FINISHED WITH UPDATING\n");
@@ -84,7 +84,7 @@ public static class WebsocketManager
{
var installation = Db.GetInstallationById(installationId);
var installationConnection = InstallationConnections[installationId];
- Console.WriteLine("Update all the connected websockets for installation " + installationId);
+ Console.WriteLine("Update all the connected websockets for installation " + installation.Name);
var jsonObject = new
{
diff --git a/csharp/App/SaliMax/SaliMax.csproj b/csharp/App/SaliMax/SaliMax.csproj
index c8e936464..a62f192ea 100644
--- a/csharp/App/SaliMax/SaliMax.csproj
+++ b/csharp/App/SaliMax/SaliMax.csproj
@@ -29,4 +29,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp/App/SaliMax/src/AggregationService/Aggregator.cs b/csharp/App/SaliMax/src/AggregationService/Aggregator.cs
index 37c7d3913..e430bb351 100644
--- a/csharp/App/SaliMax/src/AggregationService/Aggregator.cs
+++ b/csharp/App/SaliMax/src/AggregationService/Aggregator.cs
@@ -1,5 +1,7 @@
using InnovEnergy.App.SaliMax.Ess;
using InnovEnergy.Lib.Utils;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
using static System.Double;
namespace InnovEnergy.App.SaliMax.AggregationService;
@@ -14,7 +16,7 @@ public static class Aggregator
// Calculate the time until the next rounded hour
var timeUntilNextHour = nextRoundedHour - currentDateTime;
-
+
// Output the current and next rounded hour times
Console.WriteLine("------------------------------------------HourlyDataAggregationManager-------------------------------------------");
Console.WriteLine("Current Date and Time: " + currentDateTime);
@@ -22,7 +24,7 @@ public static class Aggregator
// Output the time until the next rounded hour
Console.WriteLine("Waiting for " + timeUntilNextHour.TotalMinutes + " minutes...");
Console.WriteLine("-----------------------------------------------------------------------------------------------------------------");
-
+
// Wait until the next rounded hour
await Task.Delay(timeUntilNextHour);
@@ -30,7 +32,7 @@ public static class Aggregator
{
try
{
- AggregatedData hourlyAggregatedData = CreateHourlyData("LogDirectory",DateTime.Now.AddHours(-1).ToUnixTime(),DateTime.Now.ToUnixTime());
+ AggregatedData hourlyAggregatedData = CreateHourlyData("JsonLogDirectory",DateTime.Now.AddHours(-1).ToUnixTime(),DateTime.Now.ToUnixTime());
hourlyAggregatedData.Save("HourlyData");
}
catch (Exception e)
@@ -83,14 +85,14 @@ public static class Aggregator
private static void DeleteHourlyData(String myDirectory, Int64 beforeTimestamp)
{
- var csvFiles = Directory.GetFiles(myDirectory, "*.csv");
+ var jsonFiles = Directory.GetFiles(myDirectory, "*.json");
Console.WriteLine("Delete data before"+beforeTimestamp);
- foreach (var csvFile in csvFiles)
+ foreach (var jsonFile in jsonFiles)
{
- if (IsFileWithinTimeRange(csvFile, 0, beforeTimestamp))
+ if (IsFileWithinTimeRange(jsonFile, 0, beforeTimestamp))
{
- File.Delete(csvFile);
- Console.WriteLine($"Deleted hourly data file: {csvFile}");
+ File.Delete(jsonFile);
+ Console.WriteLine($"Deleted hourly data file: {jsonFile}");
}
}
}
@@ -99,7 +101,7 @@ public static class Aggregator
private static AggregatedData CreateHourlyData(String myDirectory, Int64 afterTimestamp, Int64 beforeTimestamp)
{
// Get all CSV files in the specified directory
- var csvFiles = Directory.GetFiles(myDirectory, "*.csv");
+ var jsonFiles = Directory.GetFiles(myDirectory, "*.json");
var batterySoc = new List();
var pvPowerSum = new List();
var heatingPower = new List();
@@ -111,83 +113,63 @@ public static class Aggregator
Console.WriteLine("File timestamp should start after "+ afterTimestamp);
- foreach (var csvFile in csvFiles)
+ foreach (var jsonFile in jsonFiles)
{
- if (csvFile == "LogDirectory/log.csv")
+ if (jsonFile == "LogDirectory/log.json")
{
continue;
}
-
- if (IsFileWithinTimeRange(csvFile, afterTimestamp, beforeTimestamp))
+
+ if (IsFileWithinTimeRange(jsonFile, afterTimestamp, beforeTimestamp))
{
- using var reader = new StreamReader(csvFile);
-
- while (!reader.EndOfStream)
+ try
{
+ // Read and parse JSON
+
+ var jsonData = File.ReadAllText(jsonFile);
- var line = reader.ReadLine();
- var lines = line?.Split(';');
-
- // Assuming there are always three columns (variable name and its value)
- if (lines is { Length: 3 })
+ // Step 2: Find the first '{' character and trim everything before it
+ int startIndex = jsonData.IndexOf('{');
+ if (startIndex != -1)
{
- var variableName = lines[0].Trim();
+ jsonData = jsonData.Substring(startIndex); // Trim everything before '{'
+ }
- if (TryParse(lines[1].Trim(), out var value))
- {
- switch (variableName)
- {
- case "/Battery/Soc":
- batterySoc.Add(value);
- break;
-
- case "/PvOnDc/DcWh" :
- pvPowerSum.Add(value);
- break;
+ var jsonObject = JObject.Parse(jsonData);
- case "/Battery/Dc/Power":
-
- if (value < 0)
- {
- batteryDischargePower.Add(value);
- }
- else
- {
- batteryChargePower.Add(value);
- }
- break;
-
- case "/GridMeter/ActivePowerExportT3":
- // we are using different register to check which value from the grid meter we need to use
- // At the moment register 8002 amd 8012. in KWh
- gridPowerExport.Add(value);
- break;
- case "/GridMeter/ActivePowerImportT3":
- gridPowerImport.Add(value);
- break;
- case "/Battery/HeatingPower":
- heatingPower.Add(value);
- break;
- // Add more cases as needed
- default:
- // Code to execute when variableName doesn't match any condition
- break;
- }
-
- }
+ if (jsonObject["Battery"] != null && jsonObject["Battery"]["Soc"] != null)
+ {
+ batterySoc.Add((double)jsonObject["Battery"]["Soc"]);
+ }
+ if (jsonObject["PvOnDc"] != null && jsonObject["PvOnDc"]["DcWh"] != null)
+ {
+ pvPowerSum.Add((double)jsonObject["PvOnDc"]["DcWh"]);
+ }
+ if (jsonObject["Battery"] != null && jsonObject["Battery"]["Dc"]["Power"] != null)
+ {
+ double batteryPower = (double)jsonObject["Battery"]["Dc"]["Power"];
+ if (batteryPower < 0)
+ batteryDischargePower.Add(batteryPower);
else
- {
- //Handle cases where variableValue is not a valid number
- // Console.WriteLine(
- // $"Invalid numeric value for variable {variableName}:{lines[1].Trim()}");
- }
+ batteryChargePower.Add(batteryPower);
}
- else
+ if (jsonObject["GridMeter"] != null && jsonObject["GridMeter"]["ActivePowerExportT3"] != null)
{
- // Handle invalid column format
- //Console.WriteLine("Invalid format in column");
+ gridPowerExport.Add((double)jsonObject["GridMeter"]["ActivePowerExportT3"]);
}
+ if (jsonObject["GridMeter"] != null && jsonObject["GridMeter"]["ActivePowerImportT3"] != null)
+ {
+ gridPowerImport.Add((double)jsonObject["GridMeter"]["ActivePowerImportT3"]);
+ }
+ if (jsonObject["Battery"] != null && jsonObject["Battery"]["HeatingPower"] != null)
+ {
+ heatingPower.Add((double)jsonObject["Battery"]["HeatingPower"]);
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Failed to parse JSON file {jsonFile}: {e.Message}");
}
}
}
@@ -213,14 +195,14 @@ public static class Aggregator
AggregatedData aggregatedData = new AggregatedData
{
- MaxSoc = dMaxSoc,
- MinSoc = dMinSoc,
- DischargingBatteryPower = dischargingEnergy,
- ChargingBatteryPower = chargingEnergy,
- GridExportPower = dSumGridExportPower,
- GridImportPower = dSumGridImportPower,
- PvPower = dSumPvPower,
- HeatingPower = heatingPowerAvg
+ MaxSoc = Math.Round(dMaxSoc, 2),
+ MinSoc = Math.Round(dMinSoc, 2) ,
+ DischargingBatteryPower = Math.Round(dischargingEnergy, 2) ,
+ ChargingBatteryPower = Math.Round(chargingEnergy, 2) ,
+ GridExportPower = Math.Round(dSumGridExportPower, 2) ,
+ GridImportPower = Math.Round(dSumGridImportPower, 2) ,
+ PvPower = Math.Round(dSumPvPower, 2) ,
+ HeatingPower = Math.Round(heatingPowerAvg, 2)
};
// Print the stored CSV data for verification
@@ -245,7 +227,7 @@ public static class Aggregator
private static AggregatedData CreateDailyData(String myDirectory, Int64 afterTimestamp, Int64 beforeTimestamp)
{
// Get all CSV files in the specified directory
- var csvFiles = Directory.GetFiles(myDirectory, "*.csv");
+ var jsonFiles = Directory.GetFiles(myDirectory, "*.json");
var batterySoc = new List();
var pvPower = new List();
var gridPowerImport = new List();
@@ -258,79 +240,71 @@ public static class Aggregator
Console.WriteLine("File timestamp should start after "+ afterTimestamp);
- foreach (var csvFile in csvFiles)
+ foreach (var jsonFile in jsonFiles)
{
- if (csvFile == "LogDirectory/log.csv")
+ if (jsonFile == "JsonLogDirectory/log.json")
{
continue;
}
- if (IsFileWithinTimeRange(csvFile, afterTimestamp, beforeTimestamp))
+ if (IsFileWithinTimeRange(jsonFile, afterTimestamp, beforeTimestamp))
{
- using var reader = new StreamReader(csvFile);
-
- while (!reader.EndOfStream)
+
+ try
{
+ var jsonData = File.ReadAllText(jsonFile);
+ //Console.WriteLine("Parse file "+jsonFile);
- var line = reader.ReadLine();
- var lines = line?.Split(';');
+ // Parse JSON into a Dictionary
+ var jsonDict = JsonConvert.DeserializeObject>(jsonData);
- // Assuming there are always three columns (variable name and its value)
- if (lines is { Length: 3 })
+ // Process values
+ foreach (var (variableName, value) in jsonDict)
{
- var variableName = lines[0].Trim();
-
- if (TryParse(lines[1].Trim(), out var value))
+ switch (variableName)
{
- switch (variableName)
- {
- case "/MinSoc" or "/MaxSoc":
- batterySoc.Add(value);
- break;
-
- case "/PvPower":
- pvPower.Add(value);
- break;
+ case "MinSoc":
+ case "MaxSoc":
+ batterySoc.Add(value);
+ break;
- case "/DischargingBatteryPower" :
- batteryDischargePower.Add(value);
- break;
-
- case "/ChargingBatteryPower" :
- batteryChargePower.Add(value);
- break;
-
- case "/GridExportPower":
- gridPowerExport.Add(value);
- break;
-
- case "/GridImportPower":
- gridPowerImport.Add(value);
- break;
-
- case "/HeatingPower":
- heatingPowerAvg.Add(value);
- break;
- // Add more cases as needed
- default:
- // Code to execute when variableName doesn't match any condition
- break;
- }
+ case "PvPower":
+ pvPower.Add(value);
+ break;
- }
- else
- {
- //Handle cases where variableValue is not a valid number
- // Console.WriteLine(
- // $"Invalid numeric value for variable {variableName}:{lines[1].Trim()}");
+ case "DischargingBatteryPower":
+ batteryDischargePower.Add(value);
+ break;
+
+ case "ChargingBatteryPower":
+ batteryChargePower.Add(value);
+ break;
+
+ case "GridExportPower":
+ gridPowerExport.Add(value);
+ break;
+
+ case "GridImportPower":
+ gridPowerImport.Add(value);
+ break;
+
+ case "HeatingPower":
+ heatingPowerAvg.Add(value);
+ break;
+
+ default:
+ // Ignore unknown variables
+ break;
}
}
- else
- {
- // Handle invalid column format
- //Console.WriteLine("Invalid format in column");
- }
+
}
+ catch (Exception e)
+ {
+ Console.WriteLine($"Failed to parse JSON file {jsonFile}: {e.Message}");
+ }
+
+
}
}
diff --git a/csharp/App/SaliMax/src/AggregationService/HourlyData.cs b/csharp/App/SaliMax/src/AggregationService/HourlyData.cs
index e3c5a9dd2..a7e50f83c 100644
--- a/csharp/App/SaliMax/src/AggregationService/HourlyData.cs
+++ b/csharp/App/SaliMax/src/AggregationService/HourlyData.cs
@@ -6,6 +6,7 @@ using InnovEnergy.App.SaliMax.Devices;
using InnovEnergy.App.SaliMax.SystemConfig;
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Utils;
+using Newtonsoft.Json;
using static System.Text.Json.JsonSerializer;
namespace InnovEnergy.App.SaliMax.AggregationService;
@@ -30,7 +31,7 @@ public class AggregatedData
{
var date = DateTime.Now.ToUnixTime();
var defaultHDataPath = Environment.CurrentDirectory + "/" + directory + "/";
- var dataFilePath = defaultHDataPath + date + ".csv";
+ var dataFilePath = defaultHDataPath + date + ".json";
if (!Directory.Exists(defaultHDataPath))
{
@@ -41,8 +42,11 @@ public class AggregatedData
try
{
- var csvString = this.ToCsv();
- File.WriteAllText(dataFilePath, csvString);
+ // Convert the object to a JSON string
+ var jsonString = JsonConvert.SerializeObject(this, Formatting.None);
+
+ // Write JSON to file
+ File.WriteAllText(dataFilePath, jsonString);
}
catch (Exception e)
{
@@ -54,21 +58,21 @@ public class AggregatedData
public static void DeleteDailyData(String directory)
{
- var csvFiles = Directory.GetFiles(directory, "*.csv");
- foreach (var csvFile in csvFiles)
+ var jsonFiles = Directory.GetFiles(directory, "*.json");
+ foreach (var jsonFile in jsonFiles)
{
- File.Delete(csvFile);
- Console.WriteLine($"Deleted daily data file: {csvFile}");
+ File.Delete(jsonFile);
+ Console.WriteLine($"Deleted daily data file: {jsonFile}");
}
}
public async Task PushToS3()
{
- var csv = this.ToCsv();
+ var jsonString = JsonConvert.SerializeObject(this, Formatting.None);
if (_S3Config is null)
return false;
- var s3Path = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd") + ".csv";
+ var s3Path = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd") + ".json";
var request = _S3Config.CreatePutRequest(s3Path);
// Compress CSV data to a byte array
@@ -78,11 +82,11 @@ public class AggregatedData
//Create a zip directory and put the compressed file inside
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
- var entry = archive.CreateEntry("data.csv", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
+ var entry = archive.CreateEntry("data.json", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
using (var entryStream = entry.Open())
using (var writer = new StreamWriter(entryStream))
{
- writer.Write(csv);
+ writer.Write(jsonString);
}
}
@@ -98,9 +102,6 @@ public class AggregatedData
// Upload the compressed data (ZIP archive) to S3
var response = await request.PutAsync(stringContent);
- //
- // var request = _S3Config.CreatePutRequest(s3Path);
- // var response = await request.PutAsync(new StringContent(csv));
if (response.StatusCode != 200)
{
@@ -113,18 +114,5 @@ public class AggregatedData
return true;
}
- // public static HourlyData? Load(String dataFilePath)
- // {
- // try
- // {
- // var csvString = File.ReadAllText(dataFilePath);
- // return Deserialize(jsonString)!;
- // }
- // catch (Exception e)
- // {
- // $"Failed to read config file {dataFilePath}, using default config\n{e}".WriteLine();
- // return null;
- // }
- // }
}
\ No newline at end of file
diff --git a/csharp/App/SaliMax/src/LogFileConcatenator.cs b/csharp/App/SaliMax/src/LogFileConcatenator.cs
index b2873b251..60a7f5486 100644
--- a/csharp/App/SaliMax/src/LogFileConcatenator.cs
+++ b/csharp/App/SaliMax/src/LogFileConcatenator.cs
@@ -6,7 +6,7 @@ public class LogFileConcatenator
{
private readonly string _logDirectory;
- public LogFileConcatenator(String logDirectory = "LogDirectory/")
+ public LogFileConcatenator(String logDirectory = "JsonLogDirectory/")
{
_logDirectory = logDirectory;
}
@@ -14,7 +14,7 @@ public class LogFileConcatenator
public String ConcatenateFiles(int numberOfFiles)
{
var logFiles = Directory
- .GetFiles(_logDirectory, "log_*.csv")
+ .GetFiles(_logDirectory, "log_*.json")
.OrderByDescending(file => file)
.Take(numberOfFiles)
.OrderBy(file => file)
diff --git a/csharp/App/SaliMax/src/Logger.cs b/csharp/App/SaliMax/src/Logger.cs
index 7fdbd7b47..6728fbace 100644
--- a/csharp/App/SaliMax/src/Logger.cs
+++ b/csharp/App/SaliMax/src/Logger.cs
@@ -8,7 +8,7 @@ public static class Logger
//private const Int32 MaxFileSizeBytes = 2024 * 30; // TODO: move to settings
private const Int32 MaxLogFileCount = 5000; // TODO: move to settings
- private const String LogFilePath = "LogDirectory/log.csv"; // 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);
@@ -27,7 +27,7 @@ public static class Logger
public static T LogError(this T t) where T : notnull
{
- _logger.LogError(t.ToString()); // TODO: check warning
+ //_logger.LogError(t.ToString()); // TODO: check warning
return t;
}
diff --git a/csharp/App/SaliMax/src/Program.cs b/csharp/App/SaliMax/src/Program.cs
index 5122351f9..614ca5c25 100644
--- a/csharp/App/SaliMax/src/Program.cs
+++ b/csharp/App/SaliMax/src/Program.cs
@@ -25,6 +25,7 @@ using InnovEnergy.Lib.Protocols.Modbus.Channels;
using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Utils;
using InnovEnergy.App.SaliMax.DataTypes;
+using Newtonsoft.Json;
using static System.Int32;
using static InnovEnergy.App.SaliMax.AggregationService.Aggregator;
using static InnovEnergy.App.SaliMax.MiddlewareClasses.MiddlewareAgent;
@@ -809,12 +810,74 @@ internal static class Program
sc.ResetAlarmsAndWarnings = true;
}
+
+ private static void InsertIntoJson(Dictionary jsonDict, String[] keys, string value)
+ {
+
+ Dictionary currentDict = jsonDict;
+ for (int i = 1; i < keys.Length; i++) // Start at 1 to skip empty root
+ {
+ string key = keys[i];
+ if (!currentDict.ContainsKey(key))
+ {
+ currentDict[key] = new Dictionary();
+ }
+
+
+
+ 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)currentDict[key];
+ }
+ }
+
+ }
private static async Task UploadCsv(StatusRecord status, DateTime timeStamp)
{
- status.ToJson();
- var csv = status.ToCsv().LogInfo();
+ //status.ToJson();
+ var csv = status.ToCsv();
+
+
+ Dictionary jsonData = new Dictionary();
+ //Console.WriteLine(csv);
+
+ foreach (var line in csv.Split('\n'))
+ {
+ if (string.IsNullOrWhiteSpace(line)) continue;
+
+ string[] parts = line.Split(';');
+ //if (parts.Length < 2) continue;
+
+ string keyPath = parts[0];
+ string value = parts[1];
+ string unit = parts.Length > 2 ? parts[2].Trim() : "";
+ //Console.WriteLine(line);
+ // Console.WriteLine($"Key: {keyPath}, Value: {value}, Unit: {unit}");
+
+ InsertIntoJson(jsonData, keyPath.Split('/'), value);
+
+ }
+
+ string jsonOutput = JsonConvert.SerializeObject(jsonData, Formatting.None);
+ jsonOutput.LogInfo();
+
await RestApiSavingFile(csv);
@@ -835,7 +898,7 @@ internal static class Program
var logFileConcatenator = new LogFileConcatenator();
- var s3Path = timeStamp.ToUnixTime() + ".csv";
+ var s3Path = timeStamp.ToUnixTime() + ".json";
s3Path.WriteLine("");
var csvToSend = logFileConcatenator.ConcatenateFiles(NbrOfFileToConcatenate);
@@ -902,7 +965,7 @@ internal static class Program
//Create a zip directory and put the compressed file inside
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
- var entry = archive.CreateEntry("data.csv", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
+ var entry = archive.CreateEntry("data.json", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
using (var entryStream = entry.Open())
using (var writer = new StreamWriter(entryStream))
{
diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/CombinedAdamRelaysRecord.cs b/csharp/App/SaliMax/src/SaliMaxRelays/CombinedAdamRelaysRecord.cs
index 23f777169..ef669c818 100644
--- a/csharp/App/SaliMax/src/SaliMaxRelays/CombinedAdamRelaysRecord.cs
+++ b/csharp/App/SaliMax/src/SaliMaxRelays/CombinedAdamRelaysRecord.cs
@@ -208,4 +208,215 @@ public class CombinedAdamRelaysRecord : IRelaysRecord
public IEnumerable K3InverterIsConnectedToIslandBus => _recordAdam6360D.K3InverterIsConnectedToIslandBus;
+}
+using System.Reflection.Metadata;
+
+namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
+#pragma warning disable CS8602 // Dereference of a possibly null reference.
+
+public class CombinedAdamRelaysRecord : IRelaysRecord
+{
+ private const UInt16 SlowFreq = 3000;
+ private const UInt16 HighFreq = 1000;
+
+ public CombinedAdamRelaysRecord(RelaysRecordAdam6060? relaysRecordAdam6060, RelaysRecordAdam6360D? relaysRecordAdam6360D)
+ {
+ _recordAdam6060 = relaysRecordAdam6060;
+ _recordAdam6360D = relaysRecordAdam6360D;
+ }
+
+ private static RelaysRecordAdam6060? _recordAdam6060;
+ private static RelaysRecordAdam6360D? _recordAdam6360D;
+
+ public static IRelaysRecord Instance { get; } = new CombinedAdamRelaysRecord(_recordAdam6060, _recordAdam6360D);
+
+
+ public Boolean K1GridBusIsConnectedToGrid => _recordAdam6360D.K1GridBusIsConnectedToGrid;
+
+ public Boolean K2IslandBusIsConnectedToGridBus => _recordAdam6360D.K2IslandBusIsConnectedToGridBus;
+ public Boolean FiWarning => _recordAdam6360D.FiWarning;
+ public Boolean FiError => _recordAdam6360D.FiError;
+ public Boolean K2ConnectIslandBusToGridBus
+ {
+ get => _recordAdam6360D.K2ConnectIslandBusToGridBus;
+ set => _recordAdam6360D.K2ConnectIslandBusToGridBus = value;
+ }
+
+ public Boolean Inverter1WagoStatus => _recordAdam6360D.Inverter1WagoStatus;
+ public Boolean Inverter2WagoStatus => _recordAdam6360D.Inverter2WagoStatus;
+ public Boolean Inverter3WagoStatus => _recordAdam6360D.Inverter3WagoStatus;
+ public Boolean Inverter4WagoStatus => _recordAdam6360D.Inverter4WagoStatus;
+
+ public Boolean Dc1WagoStatus => _recordAdam6060.Dc1WagoStatus;
+ public Boolean Dc2WagoStatus => _recordAdam6060.Dc2WagoStatus;
+ public Boolean Dc3WagoStatus => _recordAdam6060.Dc3WagoStatus;
+ public Boolean Dc4WagoStatus => _recordAdam6060.Dc4WagoStatus;
+ public Boolean DcSystemControlWagoStatus => _recordAdam6060.DcSystemControlWagoStatus;
+
+ public Boolean LedGreen { get => _recordAdam6360D.LedGreen; set => _recordAdam6360D.LedGreen = value;}
+ public Boolean LedRed { get => _recordAdam6360D.LedRed; set => _recordAdam6360D.LedRed = value;}
+ public Boolean Harvester1Step => _recordAdam6360D.Harvester1Step;
+ public Boolean Harvester2Step => _recordAdam6360D.Harvester2Step;
+ public Boolean Harvester3Step => _recordAdam6360D.Harvester3Step;
+ public Boolean Harvester4Step => _recordAdam6360D.Harvester4Step;
+
+ public UInt16 DigitalOutput0Mode { get => _recordAdam6360D.DigitalOutput0Mode; set => _recordAdam6360D.DigitalOutput0Mode = value; }
+
+ public UInt16 DigitalOutput1Mode
+ {
+ get => _recordAdam6360D.DigitalOutput1Mode;
+ set => _recordAdam6360D.DigitalOutput1Mode = value;
+ }
+
+ public UInt16 DigitalOutput2Mode
+ {
+ get => _recordAdam6360D.DigitalOutput2Mode;
+ set => _recordAdam6360D.DigitalOutput2Mode = value;
+ }
+
+ public UInt16 DigitalOutput3Mode
+ {
+ get => _recordAdam6360D.DigitalOutput3Mode;
+ set => _recordAdam6360D.DigitalOutput3Mode = value;
+ }
+
+ public UInt16 DigitalOutput4Mode
+ {
+ get => _recordAdam6360D.DigitalOutput4Mode;
+ set => _recordAdam6360D.DigitalOutput4Mode = value;
+ }
+
+ public UInt16 DigitalOutput5Mode
+ {
+ get => _recordAdam6360D.DigitalOutput5Mode;
+ set => _recordAdam6360D.DigitalOutput5Mode = value;
+ }
+
+ public Boolean Do0StartPulse { get => _recordAdam6360D.Do0Pulse; set => _recordAdam6360D.Do0Pulse = value; }
+ public Boolean Do1StartPulse { get => _recordAdam6360D.Do1Pulse; set => _recordAdam6360D.Do1Pulse = value; }
+ public Boolean Do2StartPulse { get => _recordAdam6360D.Do2Pulse; set => _recordAdam6360D.Do2Pulse = value; }
+ public Boolean Do3StartPulse { get => _recordAdam6360D.Do3Pulse; set => _recordAdam6360D.Do3Pulse = value; }
+ public Boolean Do4StartPulse { get => _recordAdam6360D.Do4Pulse; set => _recordAdam6360D.Do4Pulse = value; }
+ public Boolean Do5StartPulse { get => _recordAdam6360D.Do5Pulse; set => _recordAdam6360D.Do5Pulse = value; }
+
+
+ public UInt16 PulseOut0LowTime { get => _recordAdam6360D.PulseOut0LowTime; set => _recordAdam6360D.PulseOut0LowTime = value; }
+ public UInt16 PulseOut1LowTime { get => _recordAdam6360D.PulseOut1LowTime; set => _recordAdam6360D.PulseOut1LowTime = value; }
+ public UInt16 PulseOut2LowTime { get => _recordAdam6360D.PulseOut2LowTime; set => _recordAdam6360D.PulseOut2LowTime = value; }
+ public UInt16 PulseOut3LowTime { get => _recordAdam6360D.PulseOut3LowTime; set => _recordAdam6360D.PulseOut3LowTime = value; }
+ public UInt16 PulseOut4LowTime { get => _recordAdam6360D.PulseOut4LowTime; set => _recordAdam6360D.PulseOut4LowTime = value; }
+ public UInt16 PulseOut5LowTime { get => _recordAdam6360D.PulseOut5LowTime; set => _recordAdam6360D.PulseOut5LowTime = value; }
+
+ public UInt16 PulseOut0HighTime { get => _recordAdam6360D.PulseOut0HighTime; set => _recordAdam6360D.PulseOut0HighTime = value; }
+ public UInt16 PulseOut1HighTime { get => _recordAdam6360D.PulseOut1HighTime; set => _recordAdam6360D.PulseOut1HighTime = value; }
+ public UInt16 PulseOut2HighTime { get => _recordAdam6360D.PulseOut2HighTime; set => _recordAdam6360D.PulseOut2HighTime = value; }
+ public UInt16 PulseOut3HighTime { get => _recordAdam6360D.PulseOut3HighTime; set => _recordAdam6360D.PulseOut3HighTime = value; }
+ public UInt16 PulseOut4HighTime { get => _recordAdam6360D.PulseOut4HighTime; set => _recordAdam6360D.PulseOut4HighTime = value; }
+ public UInt16 PulseOut5HighTime { get => _recordAdam6360D.PulseOut5HighTime; set => _recordAdam6360D.PulseOut5HighTime = value; }
+
+ /**************************** Green LED *********************************/
+
+ public void PerformSolidGreenLed()
+ {
+ DigitalOutput0Mode = 0;
+ DigitalOutput1Mode = 0;
+ LedGreen = true;
+ LedRed = false;
+ }
+
+ public void PerformSlowFlashingGreenLed()
+ {
+ PulseOut0HighTime = SlowFreq;
+ PulseOut0LowTime = SlowFreq;
+ DigitalOutput0Mode = 2;
+ Do0StartPulse = true;
+ Do1StartPulse = false; // make sure the red LED is off
+
+ Console.WriteLine("Green Slow Flashing Starting");
+ }
+
+ public void PerformFastFlashingGreenLed()
+ {
+ PulseOut0HighTime = HighFreq;
+ PulseOut0LowTime = HighFreq;
+ DigitalOutput0Mode = 2;
+ Do0StartPulse = true;
+ Do1StartPulse = false;// make sure the red LED is off
+
+ Console.WriteLine("Green Slow Flashing Starting");
+ }
+
+ /**************************** Orange LED *********************************/
+
+ public void PerformSolidOrangeLed()
+ {
+ DigitalOutput0Mode = 0;
+ DigitalOutput1Mode = 0;
+ LedGreen = true;
+ LedRed = true;
+ }
+
+ public void PerformSlowFlashingOrangeLed()
+ {
+ PerformSlowFlashingGreenLed();
+ PerformSlowFlashingRedLed();
+ Do0StartPulse = true;
+ Do1StartPulse = true;
+
+ Console.WriteLine("Orange Slow Flashing Starting");
+ }
+
+ public void PerformFastFlashingOrangeLed()
+ {
+ PerformFastFlashingGreenLed();
+ PerformFastFlashingRedLed();
+ Do0StartPulse = true;
+ Do1StartPulse = true;
+ Console.WriteLine("Orange Fast Flashing Starting");
+ }
+
+ /**************************** RED LED *********************************/
+
+ public void PerformSolidRedLed()
+ {
+ DigitalOutput0Mode = 0;
+ DigitalOutput1Mode = 0;
+ LedGreen = false;
+ LedRed = true;
+ }
+
+ public void PerformSlowFlashingRedLed()
+ {
+ PulseOut1HighTime = SlowFreq;
+ PulseOut1LowTime = SlowFreq;
+ DigitalOutput1Mode = 2;
+ Do0StartPulse = false; // make sure the green LED is off
+ Do1StartPulse = true;
+
+ Console.WriteLine("Red Slow Flashing Starting");
+ }
+
+ public void PerformFastFlashingRedLed()
+ {
+ PulseOut1HighTime = HighFreq;
+ PulseOut1LowTime = HighFreq;
+ DigitalOutput1Mode = 2;
+ Do0StartPulse = false; // make sure the green LED is off
+ Do1StartPulse = true;
+
+ Console.WriteLine("Red Fast Flashing Starting");
+ }
+
+ public RelaysRecordAdam6360D? GetAdam6360DRecord()
+ {
+ return _recordAdam6360D;
+ }
+
+ public RelaysRecordAdam6060? GetAdam6060Record()
+ {
+ return _recordAdam6060;
+ }
+
+ public IEnumerable K3InverterIsConnectedToIslandBus => _recordAdam6360D.K3InverterIsConnectedToIslandBus;
+
}
\ No newline at end of file
diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/IRelaysRecord.cs b/csharp/App/SaliMax/src/SaliMaxRelays/IRelaysRecord.cs
index 1e7f8c089..e06628c44 100644
--- a/csharp/App/SaliMax/src/SaliMaxRelays/IRelaysRecord.cs
+++ b/csharp/App/SaliMax/src/SaliMaxRelays/IRelaysRecord.cs
@@ -1,6 +1,84 @@
namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
+public interface IRelaysRecord
+{
+ Boolean K1GridBusIsConnectedToGrid { get; }
+ Boolean K2IslandBusIsConnectedToGridBus { get; }
+ IEnumerable K3InverterIsConnectedToIslandBus { get; }
+ Boolean FiWarning { get; }
+ Boolean FiError { get; }
+ Boolean K2ConnectIslandBusToGridBus { get; set; }
+
+ // Boolean Inverter1WagoRelay { get; set; } // to add in the future
+ // Boolean Inverter2WagoRelay { get; set; } // to add in the future
+ // Boolean Inverter3WagoRelay { get; set; } // to add in the future
+ // Boolean Inverter4WagoRelay { get; set; } // to add in the future
+
+ Boolean Inverter1WagoStatus { get; }
+ Boolean Inverter2WagoStatus { get; }
+ Boolean Inverter3WagoStatus { get; }
+ Boolean Inverter4WagoStatus { get; }
+
+ Boolean Dc1WagoStatus { get; } // to test
+ Boolean Dc2WagoStatus { get; } // to test
+ Boolean Dc3WagoStatus { get; } // to test
+ Boolean Dc4WagoStatus { get; } // to test
+
+ Boolean DcSystemControlWagoStatus { get; } // to test
+
+ Boolean LedGreen { get; set; }
+ Boolean LedRed { get; }
+ Boolean Harvester1Step { get; }
+ Boolean Harvester2Step { get; }
+ Boolean Harvester3Step { get; }
+ Boolean Harvester4Step { get; }
+
+ Boolean Do0StartPulse { get; set; }
+ Boolean Do1StartPulse { get; set; }
+ Boolean Do2StartPulse { get; set; }
+ Boolean Do3StartPulse { get; set; }
+ Boolean Do4StartPulse { get; set; }
+ Boolean Do5StartPulse { get; set; }
+
+ UInt16 DigitalOutput0Mode { get; set; }
+ UInt16 DigitalOutput1Mode { get; set; }
+ UInt16 DigitalOutput2Mode { get; set; }
+ UInt16 DigitalOutput3Mode { get; set; }
+ UInt16 DigitalOutput4Mode { get; set; }
+ UInt16 DigitalOutput5Mode { get; set; }
+
+ UInt16 PulseOut0LowTime { get; set; }
+ UInt16 PulseOut1LowTime { get; set; }
+ UInt16 PulseOut2LowTime { get; set; }
+ UInt16 PulseOut3LowTime { get; set; }
+ UInt16 PulseOut4LowTime { get; set; }
+ UInt16 PulseOut5LowTime { get; set; }
+
+ UInt16 PulseOut0HighTime { get; set; }
+ UInt16 PulseOut1HighTime { get; set; }
+ UInt16 PulseOut2HighTime { get; set; }
+ UInt16 PulseOut3HighTime { get; set; }
+ UInt16 PulseOut4HighTime { get; set; }
+ UInt16 PulseOut5HighTime { get; set; }
+
+ void PerformSolidGreenLed();
+ void PerformSlowFlashingGreenLed();
+ void PerformFastFlashingGreenLed();
+
+
+ void PerformSolidOrangeLed();
+ void PerformSlowFlashingOrangeLed();
+ void PerformFastFlashingOrangeLed();
+
+ void PerformSolidRedLed();
+ void PerformSlowFlashingRedLed();
+ void PerformFastFlashingRedLed();
+
+}
+namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
+
+
public interface IRelaysRecord
{
Boolean K1GridBusIsConnectedToGrid { get; }
diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDeviceADAM6360.cs b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDeviceADAM6360.cs
index cc94dce66..50b9819d1 100644
--- a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDeviceADAM6360.cs
+++ b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDeviceADAM6360.cs
@@ -11,6 +11,46 @@ public class RelaysDeviceAdam6360
public RelaysDeviceAdam6360(Channel channel) => AdamDevice6360D = new Adam6360DDevice(channel, 2);
+ public RelaysRecordAdam6360D? Read()
+ {
+ try
+ {
+ return AdamDevice6360D.Read();
+ }
+ catch (Exception e)
+ {
+ $"Failed to read from {nameof(RelaysDeviceAdam6360)}\n{e}".LogError();
+ return null;
+ }
+ }
+
+ public void Write(RelaysRecordAdam6360D r)
+ {
+ try
+ {
+ AdamDevice6360D.Write(r);
+ }
+ catch (Exception e)
+ {
+ $"Failed to write to {nameof(RelaysDeviceAdam6360)}\n{e}".LogError();
+ }
+ }
+}
+
+
+using InnovEnergy.Lib.Devices.Adam6360D;
+using InnovEnergy.Lib.Protocols.Modbus.Channels;
+
+namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
+
+public class RelaysDeviceAdam6360
+{
+ private Adam6360DDevice AdamDevice6360D { get; }
+
+ public RelaysDeviceAdam6360(String hostname) => AdamDevice6360D = new Adam6360DDevice(hostname, 2);
+ public RelaysDeviceAdam6360(Channel channel) => AdamDevice6360D = new Adam6360DDevice(channel, 2);
+
+
public RelaysRecordAdam6360D? Read()
{
try
diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDeviceAdam6060.cs b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDeviceAdam6060.cs
index 2533cbd70..1e509c5d4 100644
--- a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDeviceAdam6060.cs
+++ b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDeviceAdam6060.cs
@@ -11,6 +11,44 @@ public class RelaysDeviceAdam6060
public RelaysDeviceAdam6060(Channel channel) => AdamDevice6060 = new Adam6060Device(channel, 2);
+ public RelaysRecordAdam6060? Read()
+ {
+ try
+ {
+ return AdamDevice6060.Read();
+ }
+ catch (Exception e)
+ {
+ $"Failed to read from {nameof(RelaysDeviceAdam6060)}\n{e}".LogError();
+ return null;
+ }
+ }
+
+ public void Write(RelaysRecordAdam6060 r)
+ {
+ try
+ {
+ AdamDevice6060.Write(r);
+ }
+ catch (Exception e)
+ {
+ $"Failed to write to {nameof(RelaysDeviceAdam6060)}\n{e}".LogError();
+ }
+ }
+}
+using InnovEnergy.Lib.Devices.Adam6060;
+using InnovEnergy.Lib.Protocols.Modbus.Channels;
+
+namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
+
+public class RelaysDeviceAdam6060
+{
+ private Adam6060Device AdamDevice6060 { get; }
+
+ public RelaysDeviceAdam6060(String hostname) => AdamDevice6060 = new Adam6060Device(hostname, 2);
+ public RelaysDeviceAdam6060(Channel channel) => AdamDevice6060 = new Adam6060Device(channel, 2);
+
+
public RelaysRecordAdam6060? Read()
{
try
diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDeviceAmax.cs b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDeviceAmax.cs
index 30a95e3de..0e3f86e0a 100644
--- a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDeviceAmax.cs
+++ b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysDeviceAmax.cs
@@ -2,6 +2,43 @@ using InnovEnergy.Lib.Devices.Amax5070;
using InnovEnergy.Lib.Protocols.Modbus.Channels;
+namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
+
+public class RelaysDeviceAmax
+{
+ private Amax5070Device AmaxDevice { get; }
+
+ public RelaysDeviceAmax(Channel channel) => AmaxDevice = new Amax5070Device(channel);
+
+ public RelaysRecordAmax? Read()
+ {
+ try
+ {
+ return AmaxDevice.Read();
+ }
+ catch (Exception e)
+ {
+ $"Failed to read from {nameof(RelaysDeviceAmax)}\n{e}".LogError();
+ return null;
+ }
+ }
+
+ public void Write(RelaysRecordAmax r)
+ {
+ try
+ {
+ AmaxDevice.Write(r);
+ }
+ catch (Exception e)
+ {
+ $"Failed to write to {nameof(RelaysDeviceAmax)}\n{e}".LogError();
+ }
+ }
+}
+using InnovEnergy.Lib.Devices.Amax5070;
+using InnovEnergy.Lib.Protocols.Modbus.Channels;
+
+
namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
public class RelaysDeviceAmax
diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6060.cs b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6060.cs
index 2882736fa..77816f187 100644
--- a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6060.cs
+++ b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6060.cs
@@ -1,6 +1,30 @@
using InnovEnergy.Lib.Devices.Adam6060;
+namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
+
+public class RelaysRecordAdam6060
+{
+ private readonly Adam6060Registers _Regs;
+
+ private RelaysRecordAdam6060(Adam6060Registers regs) => _Regs = regs;
+
+
+ public Boolean Dc1WagoStatus => _Regs.DigitalInput0; // to test
+ public Boolean Dc2WagoStatus => _Regs.DigitalInput1; // to test
+ public Boolean Dc3WagoStatus => _Regs.DigitalInput4; // to test
+ public Boolean Dc4WagoStatus => _Regs.DigitalInput5; // to test
+
+ public Boolean DcSystemControlWagoStatus => _Regs.DigitalInput3; // to test
+
+
+ public static implicit operator Adam6060Registers(RelaysRecordAdam6060 d) => d._Regs;
+ public static implicit operator RelaysRecordAdam6060(Adam6060Registers d) => new RelaysRecordAdam6060(d);
+
+}
+using InnovEnergy.Lib.Devices.Adam6060;
+
+
namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
public class RelaysRecordAdam6060
diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6360D.cs b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6360D.cs
index 46625fdc2..6377bc947 100644
--- a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6360D.cs
+++ b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6360D.cs
@@ -2,6 +2,87 @@ using InnovEnergy.Lib.Devices.Adam6360D;
namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
+public class RelaysRecordAdam6360D
+{
+ private readonly Adam6360DRegisters _Regs;
+
+ private RelaysRecordAdam6360D(Adam6360DRegisters regs) => _Regs = regs;
+
+ public Boolean K1GridBusIsConnectedToGrid => _Regs.DigitalInput6;
+ public Boolean K2IslandBusIsConnectedToGridBus => !_Regs.DigitalInput4;
+
+ public Boolean Inverter1WagoStatus => _Regs.DigitalInput8;
+ public Boolean Inverter2WagoStatus => _Regs.DigitalInput9;
+ public Boolean Inverter3WagoStatus => _Regs.DigitalInput10;
+ public Boolean Inverter4WagoStatus => _Regs.DigitalInput11;
+
+ public IEnumerable K3InverterIsConnectedToIslandBus
+ {
+ get
+ {
+ yield return K3Inverter1IsConnectedToIslandBus;
+ yield return K3Inverter2IsConnectedToIslandBus;
+ yield return K3Inverter3IsConnectedToIslandBus;
+ yield return K3Inverter4IsConnectedToIslandBus;
+ }
+ }
+
+ private Boolean K3Inverter1IsConnectedToIslandBus => !_Regs.DigitalInput0; // change it to private should be ok
+ private Boolean K3Inverter2IsConnectedToIslandBus => !_Regs.DigitalInput1;
+ private Boolean K3Inverter3IsConnectedToIslandBus => !_Regs.DigitalInput2;
+ private Boolean K3Inverter4IsConnectedToIslandBus => !_Regs.DigitalInput3;
+
+ public Boolean FiWarning => !_Regs.DigitalInput5;
+ public Boolean FiError => !_Regs.DigitalInput7;
+
+ public Boolean Harvester1Step =>_Regs.DigitalOutput2;
+ public Boolean Harvester2Step =>_Regs.DigitalOutput3;
+ public Boolean Harvester3Step =>_Regs.DigitalOutput4;
+ public Boolean Harvester4Step =>_Regs.DigitalOutput5;
+
+ public Boolean LedGreen { get =>_Regs.DigitalOutput0; set => _Regs.DigitalOutput0 = value;}
+ public Boolean LedRed { get =>_Regs.DigitalOutput1; set => _Regs.DigitalOutput1 = value;}
+
+ public Boolean Do0Pulse { get => _Regs.Do0Pulse; set => _Regs.Do0Pulse = value;}
+ public Boolean Do1Pulse { get => _Regs.Do1Pulse; set => _Regs.Do1Pulse = value;}
+ public Boolean Do2Pulse { get => _Regs.Do2Pulse; set => _Regs.Do2Pulse = value;}
+ public Boolean Do3Pulse { get => _Regs.Do3Pulse; set => _Regs.Do3Pulse = value;}
+ public Boolean Do4Pulse { get => _Regs.Do4Pulse; set => _Regs.Do4Pulse = value;}
+ public Boolean Do5Pulse { get => _Regs.Do5Pulse; set => _Regs.Do5Pulse = value;}
+
+ public UInt16 PulseOut0LowTime { get => _Regs.PulseOut0LowTime; set => _Regs.PulseOut0LowTime = value;} //in milleseconds
+ public UInt16 PulseOut1LowTime { get => _Regs.PulseOut1LowTime; set => _Regs.PulseOut1LowTime = value;}
+ public UInt16 PulseOut2LowTime { get => _Regs.PulseOut2LowTime; set => _Regs.PulseOut2LowTime = value;}
+ public UInt16 PulseOut3LowTime { get => _Regs.PulseOut3LowTime; set => _Regs.PulseOut3LowTime = value;}
+ public UInt16 PulseOut4LowTime { get => _Regs.PulseOut4LowTime; set => _Regs.PulseOut4LowTime = value;}
+ public UInt16 PulseOut5LowTime { get => _Regs.PulseOut5LowTime; set => _Regs.PulseOut5LowTime = value;}
+
+ public UInt16 PulseOut0HighTime { get => _Regs.PulseOut0HighTime; set => _Regs.PulseOut0HighTime = value;} // in milleseconds
+ public UInt16 PulseOut1HighTime { get => _Regs.PulseOut1HighTime; set => _Regs.PulseOut1HighTime = value;}
+ public UInt16 PulseOut2HighTime { get => _Regs.PulseOut2HighTime; set => _Regs.PulseOut2HighTime = value;}
+ public UInt16 PulseOut3HighTime { get => _Regs.PulseOut3HighTime; set => _Regs.PulseOut3HighTime = value;}
+ public UInt16 PulseOut4HighTime { get => _Regs.PulseOut4HighTime; set => _Regs.PulseOut4HighTime = value;}
+ public UInt16 PulseOut5HighTime { get => _Regs.PulseOut5HighTime; set => _Regs.PulseOut5HighTime = value;}
+
+ public UInt16 DigitalOutput0Mode { get => _Regs.DigitalOutput0Mode; set => _Regs.DigitalOutput0Mode = value;} // To test: 0, 1 or 2
+ public UInt16 DigitalOutput1Mode { get => _Regs.DigitalOutput1Mode; set => _Regs.DigitalOutput1Mode = value;}
+ public UInt16 DigitalOutput2Mode { get => _Regs.DigitalOutput2Mode; set => _Regs.DigitalOutput2Mode = value;}
+ public UInt16 DigitalOutput3Mode { get => _Regs.DigitalOutput3Mode; set => _Regs.DigitalOutput3Mode = value;}
+ public UInt16 DigitalOutput4Mode { get => _Regs.DigitalOutput4Mode; set => _Regs.DigitalOutput4Mode = value;}
+ public UInt16 DigitalOutput5Mode { get => _Regs.DigitalOutput5Mode; set => _Regs.DigitalOutput5Mode = value;}
+
+ public Boolean K2ConnectIslandBusToGridBus { get => _Regs.Relay0; set => _Regs.Relay0 = value;}
+
+ public static implicit operator Adam6360DRegisters(RelaysRecordAdam6360D d) => d._Regs;
+ public static implicit operator RelaysRecordAdam6360D(Adam6360DRegisters d) => new RelaysRecordAdam6360D(d);
+
+}
+
+
+using InnovEnergy.Lib.Devices.Adam6360D;
+
+namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
+
public class RelaysRecordAdam6360D
{
private readonly Adam6360DRegisters _Regs;
diff --git a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAmax.cs b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAmax.cs
index b840dbeb2..13c71d1df 100644
--- a/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAmax.cs
+++ b/csharp/App/SaliMax/src/SaliMaxRelays/RelaysRecordAmax.cs
@@ -2,6 +2,140 @@ using InnovEnergy.Lib.Devices.Amax5070;
namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
+public class RelaysRecordAmax : IRelaysRecord
+{
+ private readonly Amax5070Registers _Regs;
+
+ private RelaysRecordAmax(Amax5070Registers regs) => _Regs = regs;
+
+ public Boolean K1GridBusIsConnectedToGrid => _Regs.DigitalInput22;
+ public Boolean K2IslandBusIsConnectedToGridBus => !_Regs.DigitalInput20;
+
+ public Boolean Inverter1WagoStatus => _Regs.DigitalInput0;
+ public Boolean Inverter2WagoStatus => _Regs.DigitalInput1;
+ public Boolean Inverter3WagoStatus => _Regs.DigitalInput2;
+ public Boolean Inverter4WagoStatus => _Regs.DigitalInput3;
+
+ public Boolean Dc1WagoStatus => _Regs.DigitalInput6;
+ public Boolean Dc2WagoStatus => _Regs.DigitalInput7;
+ public Boolean Dc3WagoStatus => _Regs.DigitalInput10;
+ public Boolean Dc4WagoStatus => _Regs.DigitalInput11;
+ public Boolean DcSystemControlWagoStatus => _Regs.DigitalInput9;
+
+ public Boolean LedGreen
+ {
+ get => _Regs.DigitalOutput0;
+ set => _Regs.DigitalOutput0 = value;
+ }
+
+ public Boolean LedRed => _Regs.DigitalOutput1;
+ public Boolean Harvester1Step => _Regs.DigitalOutput2;
+ public Boolean Harvester2Step => _Regs.DigitalOutput3;
+ public Boolean Harvester3Step => _Regs.DigitalOutput4;
+ public Boolean Harvester4Step => _Regs.DigitalOutput5;
+ public Boolean Do0StartPulse { get; set; }
+ public Boolean Do1StartPulse { get; set; }
+ public Boolean Do2StartPulse { get; set; }
+ public Boolean Do3StartPulse { get; set; }
+ public Boolean Do4StartPulse { get; set; }
+ public Boolean Do5StartPulse { get; set; }
+ public UInt16 DigitalOutput0Mode { get; set; }
+ public UInt16 DigitalOutput1Mode { get; set; }
+ public UInt16 DigitalOutput2Mode { get; set; }
+ public UInt16 DigitalOutput3Mode { get; set; }
+ public UInt16 DigitalOutput4Mode { get; set; }
+ public UInt16 DigitalOutput5Mode { get; set; }
+ public UInt16 PulseOut0LowTime { get; set; }
+ public UInt16 PulseOut1LowTime { get; set; }
+ public UInt16 PulseOut2LowTime { get; set; }
+ public UInt16 PulseOut3LowTime { get; set; }
+ public UInt16 PulseOut4LowTime { get; set; }
+ public UInt16 PulseOut5LowTime { get; set; }
+ public UInt16 PulseOut0HighTime { get; set; }
+ public UInt16 PulseOut1HighTime { get; set; }
+ public UInt16 PulseOut2HighTime { get; set; }
+ public UInt16 PulseOut3HighTime { get; set; }
+ public UInt16 PulseOut4HighTime { get; set; }
+ public UInt16 PulseOut5HighTime { get; set; }
+
+ public void PerformSolidGreenLed()
+ {
+ Console.WriteLine("Solid Green: This is not yet implemented ");
+ }
+
+ public void PerformSlowFlashingGreenLed()
+ {
+ Console.WriteLine("Slow Flashing Green: This is not yet implemented ");
+ }
+
+ public void PerformFastFlashingGreenLed()
+ {
+ Console.WriteLine("Fast Flashing Green: This is not yet implemented ");
+ }
+
+ public void PerformSolidOrangeLed()
+ {
+ Console.WriteLine("Solid Orange: This is not yet implemented ");
+ }
+
+ public void PerformSlowFlashingOrangeLed()
+ {
+ Console.WriteLine("Slow Flashing Orange: This is not yet implemented ");
+ }
+
+ public void PerformFastFlashingOrangeLed()
+ {
+ Console.WriteLine("Fast Flashing Orange: This is not yet implemented ");
+ }
+
+ public void PerformSolidRedLed()
+ {
+ Console.WriteLine("Solid Red: This is not yet implemented ");
+ }
+
+ public void PerformSlowFlashingRedLed()
+ {
+ Console.WriteLine("Slow Flashing Red: This is not yet implemented ");
+ }
+
+ public void PerformFastFlashingRedLed()
+ {
+ Console.WriteLine("Fast Flashing Red: This is not yet implemented ");
+ }
+
+ public IEnumerable K3InverterIsConnectedToIslandBus
+ {
+ get
+ {
+ yield return K3Inverter1IsConnectedToIslandBus;
+ yield return K3Inverter2IsConnectedToIslandBus;
+ yield return K3Inverter3IsConnectedToIslandBus;
+ yield return K3Inverter4IsConnectedToIslandBus;
+ }
+ }
+
+ private Boolean K3Inverter1IsConnectedToIslandBus => !_Regs.DigitalInput16;
+ private Boolean K3Inverter2IsConnectedToIslandBus => !_Regs.DigitalInput17;
+ private Boolean K3Inverter3IsConnectedToIslandBus => !_Regs.DigitalInput18;
+ private Boolean K3Inverter4IsConnectedToIslandBus => !_Regs.DigitalInput19;
+
+ public Boolean FiWarning => !_Regs.DigitalInput21;
+ public Boolean FiError => !_Regs.DigitalInput23;
+
+ public Boolean K2ConnectIslandBusToGridBus
+ {
+ get => _Regs.Relay23;
+ set => _Regs.Relay23 = value;
+ }
+
+ public static implicit operator Amax5070Registers(RelaysRecordAmax d) => d._Regs;
+ public static implicit operator RelaysRecordAmax(Amax5070Registers d) => new RelaysRecordAmax(d);
+
+}
+using InnovEnergy.Lib.Devices.Amax5070;
+
+namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
+
public class RelaysRecordAmax : IRelaysRecord
{
private readonly Amax5070Registers _Regs;
diff --git a/csharp/App/SodiStoreMax/src/AggregationService/Aggregator.cs b/csharp/App/SodiStoreMax/src/AggregationService/Aggregator.cs
index 0fa03f49e..0759d67e9 100644
--- a/csharp/App/SodiStoreMax/src/AggregationService/Aggregator.cs
+++ b/csharp/App/SodiStoreMax/src/AggregationService/Aggregator.cs
@@ -1,5 +1,7 @@
using InnovEnergy.App.SodiStoreMax.Ess;
using InnovEnergy.Lib.Utils;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
using static System.Double;
namespace InnovEnergy.App.SodiStoreMax.AggregationService;
@@ -30,7 +32,7 @@ public static class Aggregator
{
try
{
- AggregatedData hourlyAggregatedData = CreateHourlyData("LogDirectory",DateTime.Now.AddHours(-1).ToUnixTime(),DateTime.Now.ToUnixTime());
+ AggregatedData hourlyAggregatedData = CreateHourlyData("JsonLogDirectory",DateTime.Now.AddHours(-1).ToUnixTime(),DateTime.Now.ToUnixTime());
hourlyAggregatedData.Save("HourlyData");
}
catch (Exception e)
@@ -83,14 +85,14 @@ public static class Aggregator
private static void DeleteHourlyData(String myDirectory, Int64 beforeTimestamp)
{
- var csvFiles = Directory.GetFiles(myDirectory, "*.csv");
+ var jsonFiles = Directory.GetFiles(myDirectory, "*.json");
Console.WriteLine("Delete data before"+beforeTimestamp);
- foreach (var csvFile in csvFiles)
+ foreach (var jsonFile in jsonFiles)
{
- if (IsFileWithinTimeRange(csvFile, 0, beforeTimestamp))
+ if (IsFileWithinTimeRange(jsonFile, 0, beforeTimestamp))
{
- File.Delete(csvFile);
- Console.WriteLine($"Deleted hourly data file: {csvFile}");
+ File.Delete(jsonFile);
+ Console.WriteLine($"Deleted hourly data file: {jsonFile}");
}
}
}
@@ -99,7 +101,7 @@ public static class Aggregator
private static AggregatedData CreateHourlyData(String myDirectory, Int64 afterTimestamp, Int64 beforeTimestamp)
{
// Get all CSV files in the specified directory
- var csvFiles = Directory.GetFiles(myDirectory, "*.csv");
+ var jsonFiles = Directory.GetFiles(myDirectory, "*.json");
var batterySoc = new List();
var pvPowerSum = new List();
var heatingPower = new List();
@@ -111,84 +113,135 @@ public static class Aggregator
Console.WriteLine("File timestamp should start after "+ afterTimestamp);
- foreach (var csvFile in csvFiles)
+ foreach (var jsonFile in jsonFiles)
{
- if (csvFile == "LogDirectory/log.csv")
+ if (jsonFile == "JsonLogDirectory/log.json")
{
continue;
}
- if (IsFileWithinTimeRange(csvFile, afterTimestamp, beforeTimestamp))
+ if (IsFileWithinTimeRange(jsonFile, afterTimestamp, beforeTimestamp))
{
- using var reader = new StreamReader(csvFile);
-
- while (!reader.EndOfStream)
+ try
{
+ // Read and parse JSON
+
+ var jsonData = File.ReadAllText(jsonFile);
- var line = reader.ReadLine();
- var lines = line?.Split(';');
-
- // Assuming there are always three columns (variable name and its value)
- if (lines is { Length: 3 })
+ // Step 2: Find the first '{' character and trim everything before it
+ int startIndex = jsonData.IndexOf('{');
+ if (startIndex != -1)
{
- var variableName = lines[0].Trim();
-
- if (TryParse(lines[1].Trim(), out var value))
- {
- switch (variableName)
- {
- case "/Battery/Soc":
- batterySoc.Add(value);
- break;
-
- case "/PvOnDc/DcWh" :
- pvPowerSum.Add(value);
- break;
-
- case "/Battery/Dc/Power":
-
- if (value < 0)
- {
- batteryDischargePower.Add(value);
- }
- else
- {
- batteryChargePower.Add(value);
-
- }
- break;
-
- case "/GridMeter/ActivePowerExportT3":
- // we are using different register to check which value from the grid meter we need to use
- // At the moment register 8002 amd 8012. in KWh
- gridPowerExport.Add(value);
- break;
- case "/GridMeter/ActivePowerImportT3":
- gridPowerImport.Add(value);
- break;
- case "/Battery/HeatingPower":
- heatingPower.Add(value);
- break;
- // Add more cases as needed
- default:
- // Code to execute when variableName doesn't match any condition
- break;
- }
-
- }
- else
- {
- //Handle cases where variableValue is not a valid number
- // Console.WriteLine(
- // $"Invalid numeric value for variable {variableName}:{lines[1].Trim()}");
- }
+ jsonData = jsonData.Substring(startIndex); // Trim everything before '{'
}
- else
+
+ var jsonObject = JObject.Parse(jsonData);
+
+
+ if (jsonObject["Battery"] != null && jsonObject["Battery"]["Soc"] != null)
{
- // Handle invalid column format
- //Console.WriteLine("Invalid format in column");
+ batterySoc.Add((double)jsonObject["Battery"]["Soc"]);
+ }
+ if (jsonObject["PvOnDc"] != null && jsonObject["PvOnDc"]["DcWh"] != null)
+ {
+ pvPowerSum.Add((double)jsonObject["PvOnDc"]["DcWh"]);
+ }
+ if (jsonObject["Battery"] != null && jsonObject["Battery"]["Dc"]["Power"] != null)
+ {
+ double batteryPower = (double)jsonObject["Battery"]["Dc"]["Power"];
+ if (batteryPower < 0)
+ batteryDischargePower.Add(batteryPower);
+ else
+ batteryChargePower.Add(batteryPower);
+ }
+ if (jsonObject["GridMeter"] != null && jsonObject["GridMeter"]["ActivePowerExportT3"] != null)
+ {
+ gridPowerExport.Add((double)jsonObject["GridMeter"]["ActivePowerExportT3"]);
+ }
+ if (jsonObject["GridMeter"] != null && jsonObject["GridMeter"]["ActivePowerImportT3"] != null)
+ {
+ gridPowerImport.Add((double)jsonObject["GridMeter"]["ActivePowerImportT3"]);
+ }
+ if (jsonObject["Battery"] != null && jsonObject["Battery"]["HeatingPower"] != null)
+ {
+ heatingPower.Add((double)jsonObject["Battery"]["HeatingPower"]);
}
}
+ catch (Exception e)
+ {
+ Console.WriteLine($"Failed to parse JSON file {jsonFile}: {e.Message}");
+ }
+
+
+ // using var reader = new StreamReader(jsonFile);
+ //
+ // while (!reader.EndOfStream)
+ // {
+ //
+ // var line = reader.ReadLine();
+ // var lines = line?.Split(';');
+ //
+ // // Assuming there are always three columns (variable name and its value)
+ // if (lines is { Length: 3 })
+ // {
+ // var variableName = lines[0].Trim();
+ //
+ // if (TryParse(lines[1].Trim(), out var value))
+ // {
+ // switch (variableName)
+ // {
+ // case "/Battery/Soc":
+ // batterySoc.Add(value);
+ // break;
+ //
+ // case "/PvOnDc/DcWh" :
+ // pvPowerSum.Add(value);
+ // break;
+ //
+ // case "/Battery/Dc/Power":
+ //
+ // if (value < 0)
+ // {
+ // batteryDischargePower.Add(value);
+ // }
+ // else
+ // {
+ // batteryChargePower.Add(value);
+ //
+ // }
+ // break;
+ //
+ // case "/GridMeter/ActivePowerExportT3":
+ // // we are using different register to check which value from the grid meter we need to use
+ // // At the moment register 8002 amd 8012. in KWh
+ // gridPowerExport.Add(value);
+ // break;
+ // case "/GridMeter/ActivePowerImportT3":
+ // gridPowerImport.Add(value);
+ // break;
+ // case "/Battery/HeatingPower":
+ // heatingPower.Add(value);
+ // break;
+ // // Add more cases as needed
+ // default:
+ // // Code to execute when variableName doesn't match any condition
+ // break;
+ // }
+ //
+ // }
+ // else
+ // {
+ // //Handle cases where variableValue is not a valid number
+ // // Console.WriteLine(
+ // // $"Invalid numeric value for variable {variableName}:{lines[1].Trim()}");
+ // }
+ // }
+ // else
+ // {
+ // // Handle invalid column format
+ // //Console.WriteLine("Invalid format in column");
+ // }
+ //}
}
}
@@ -244,8 +297,8 @@ public static class Aggregator
private static AggregatedData CreateDailyData(String myDirectory, Int64 afterTimestamp, Int64 beforeTimestamp)
{
- // Get all CSV files in the specified directory
- var csvFiles = Directory.GetFiles(myDirectory, "*.csv");
+ // Get all JSON files in the specified directory
+ var jsonFiles = Directory.GetFiles(myDirectory, "*.json");
var batterySoc = new List();
var pvPower = new List();
var gridPowerImport = new List();
@@ -258,79 +311,133 @@ public static class Aggregator
Console.WriteLine("File timestamp should start after "+ afterTimestamp);
- foreach (var csvFile in csvFiles)
+ foreach (var jsonFile in jsonFiles)
{
- if (csvFile == "LogDirectory/log.csv")
+ if (jsonFile == "JsonLogDirectory/log.json")
{
continue;
}
- if (IsFileWithinTimeRange(csvFile, afterTimestamp, beforeTimestamp))
+ if (IsFileWithinTimeRange(jsonFile, afterTimestamp, beforeTimestamp))
{
- using var reader = new StreamReader(csvFile);
- while (!reader.EndOfStream)
+ try
{
+ var jsonData = File.ReadAllText(jsonFile);
+ //Console.WriteLine("Parse file "+jsonFile);
- var line = reader.ReadLine();
- var lines = line?.Split(';');
+ // Parse JSON into a Dictionary
+ var jsonDict = JsonConvert.DeserializeObject>(jsonData);
- // Assuming there are always three columns (variable name and its value)
- if (lines is { Length: 3 })
+ // Process values
+ foreach (var (variableName, value) in jsonDict)
{
- var variableName = lines[0].Trim();
-
- if (TryParse(lines[1].Trim(), out var value))
+ switch (variableName)
{
- switch (variableName)
- {
- case "/MinSoc" or "/MaxSoc":
- batterySoc.Add(value);
- break;
-
- case "/PvPower":
- pvPower.Add(value);
- break;
+ case "MinSoc":
+ case "MaxSoc":
+ batterySoc.Add(value);
+ break;
- case "/DischargingBatteryPower" :
- batteryDischargePower.Add(value);
- break;
-
- case "/ChargingBatteryPower" :
- batteryChargePower.Add(value);
- break;
-
- case "/GridExportPower":
- gridPowerExport.Add(value);
- break;
-
- case "/GridImportPower":
- gridPowerImport.Add(value);
- break;
-
- case "/HeatingPower":
- heatingPowerAvg.Add(value);
- break;
- // Add more cases as needed
- default:
- // Code to execute when variableName doesn't match any condition
- break;
- }
+ case "PvPower":
+ pvPower.Add(value);
+ break;
- }
- else
- {
- //Handle cases where variableValue is not a valid number
- // Console.WriteLine(
- // $"Invalid numeric value for variable {variableName}:{lines[1].Trim()}");
+ case "DischargingBatteryPower":
+ batteryDischargePower.Add(value);
+ break;
+
+ case "ChargingBatteryPower":
+ batteryChargePower.Add(value);
+ break;
+
+ case "GridExportPower":
+ gridPowerExport.Add(value);
+ break;
+
+ case "GridImportPower":
+ gridPowerImport.Add(value);
+ break;
+
+ case "HeatingPower":
+ heatingPowerAvg.Add(value);
+ break;
+
+ default:
+ // Ignore unknown variables
+ break;
}
}
- else
- {
- // Handle invalid column format
- //Console.WriteLine("Invalid format in column");
- }
+
}
+ catch (Exception e)
+ {
+ Console.WriteLine($"Failed to parse JSON file {jsonFile}: {e.Message}");
+ }
+ // using var reader = new StreamReader(csvFile);
+ //
+ // while (!reader.EndOfStream)
+ // {
+ //
+ // var line = reader.ReadLine();
+ // var lines = line?.Split(';');
+ //
+ // // Assuming there are always three columns (variable name and its value)
+ // if (lines is { Length: 3 })
+ // {
+ // var variableName = lines[0].Trim();
+ //
+ // if (TryParse(lines[1].Trim(), out var value))
+ // {
+ // switch (variableName)
+ // {
+ // case "/MinSoc" or "/MaxSoc":
+ // batterySoc.Add(value);
+ // break;
+ //
+ // case "/PvPower":
+ // pvPower.Add(value);
+ // break;
+ //
+ // case "/DischargingBatteryPower" :
+ // batteryDischargePower.Add(value);
+ // break;
+ //
+ // case "/ChargingBatteryPower" :
+ // batteryChargePower.Add(value);
+ // break;
+ //
+ // case "/GridExportPower":
+ // gridPowerExport.Add(value);
+ // break;
+ //
+ // case "/GridImportPower":
+ // gridPowerImport.Add(value);
+ // break;
+ //
+ // case "/HeatingPower":
+ // heatingPowerAvg.Add(value);
+ // break;
+ // // Add more cases as needed
+ // default:
+ // // Code to execute when variableName doesn't match any condition
+ // break;
+ // }
+ //
+ // }
+ // else
+ // {
+ // //Handle cases where variableValue is not a valid number
+ // // Console.WriteLine(
+ // // $"Invalid numeric value for variable {variableName}:{lines[1].Trim()}");
+ // }
+ // }
+ // else
+ // {
+ // // Handle invalid column format
+ // //Console.WriteLine("Invalid format in column");
+ // }
+ //}
}
}
@@ -360,6 +467,7 @@ public static class Aggregator
+ Console.WriteLine("CSV data reading and storage completed.");
Console.WriteLine("CSV data reading and storage completed.");
Console.WriteLine("-----------------------------------------------------------------------------------------------------------------");
diff --git a/csharp/App/SodiStoreMax/src/LogFileConcatenator.cs b/csharp/App/SodiStoreMax/src/LogFileConcatenator.cs
index 9ee7b4e41..c5e63f6f4 100644
--- a/csharp/App/SodiStoreMax/src/LogFileConcatenator.cs
+++ b/csharp/App/SodiStoreMax/src/LogFileConcatenator.cs
@@ -6,7 +6,7 @@ public class LogFileConcatenator
{
private readonly string _logDirectory;
- public LogFileConcatenator(String logDirectory = "LogDirectory/")
+ public LogFileConcatenator(String logDirectory = "JsonLogDirectory/")
{
_logDirectory = logDirectory;
}
@@ -14,7 +14,7 @@ public class LogFileConcatenator
public String ConcatenateFiles(int numberOfFiles)
{
var logFiles = Directory
- .GetFiles(_logDirectory, "log_*.csv")
+ .GetFiles(_logDirectory, "log_*.json")
.OrderByDescending(file => file)
.Take(numberOfFiles)
.OrderBy(file => file)
diff --git a/csharp/App/SodiStoreMax/src/Logger.cs b/csharp/App/SodiStoreMax/src/Logger.cs
index fadd6babe..e721eb1fc 100644
--- a/csharp/App/SodiStoreMax/src/Logger.cs
+++ b/csharp/App/SodiStoreMax/src/Logger.cs
@@ -9,7 +9,7 @@ public static class Logger
//private const Int32 MaxFileSizeBytes = 2024 * 30; // TODO: move to settings
private const Int32 MaxLogFileCount = 5000; // TODO: move to settings
- private const String LogFilePath = "LogDirectory/log.csv"; // 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);
@@ -22,19 +22,19 @@ public static class Logger
public static T LogDebug(this T t) where T : notnull
{
- _logger.LogDebug(t.ToString()); // TODO: check warning
+ // _logger.LogDebug(t.ToString()); // TODO: check warning
return t;
}
public static T LogError(this T t) where T : notnull
{
- _logger.LogError(t.ToString()); // TODO: check warning
+ // _logger.LogError(t.ToString()); // TODO: check warning
return t;
}
public static T LogWarning(this T t) where T : notnull
{
- _logger.LogWarning(t.ToString()); // TODO: check warning
+ // _logger.LogWarning(t.ToString()); // TODO: check warning
return t;
}
}
\ No newline at end of file
diff --git a/csharp/App/SodiStoreMax/src/Program.cs b/csharp/App/SodiStoreMax/src/Program.cs
index ec9f2e542..eabea15d3 100644
--- a/csharp/App/SodiStoreMax/src/Program.cs
+++ b/csharp/App/SodiStoreMax/src/Program.cs
@@ -30,6 +30,7 @@ using InnovEnergy.Lib.Units;
using InnovEnergy.Lib.Utils;
using InnovEnergy.App.SodiStoreMax.DataTypes;
using InnovEnergy.Lib.Utils.Net;
+using Newtonsoft.Json;
using static System.Int32;
using static InnovEnergy.App.SodiStoreMax.AggregationService.Aggregator;
using static InnovEnergy.App.SodiStoreMax.MiddlewareClasses.MiddlewareAgent;
@@ -798,12 +799,68 @@ internal static class Program
sc.ResetAlarmsAndWarnings = true;
}
+
+ private static void InsertIntoJson(Dictionary jsonDict, String[] keys, string value)
+ {
+
+ Dictionary currentDict = jsonDict;
+ for (int i = 1; i < keys.Length; i++) // Start at 1 to skip empty root
+ {
+ string key = keys[i];
+ if (!currentDict.ContainsKey(key))
+ {
+ currentDict[key] = new Dictionary();
+ }
+
+ 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)currentDict[key];
+ }
+ }
+
+ }
private static async Task UploadCsv(StatusRecord status, DateTime timeStamp)
{
-
- var csv = status.ToCsv().LogInfo();
+ var csv = status.ToCsv();
+
+ Dictionary jsonData = new Dictionary();
+ //Console.WriteLine(csv);
+
+ foreach (var line in csv.Split('\n'))
+ {
+ if (string.IsNullOrWhiteSpace(line)) continue;
+
+ string[] parts = line.Split(';');
+ //if (parts.Length < 2) continue;
+
+ string keyPath = parts[0];
+ string value = parts[1];
+ string unit = parts.Length > 2 ? parts[2].Trim() : "";
+ //Console.WriteLine(line);
+ // Console.WriteLine($"Key: {keyPath}, Value: {value}, Unit: {unit}");
+
+ InsertIntoJson(jsonData, keyPath.Split('/'), value);
+
+ }
+
+ string jsonOutput = JsonConvert.SerializeObject(jsonData, Formatting.None);
+ jsonOutput.LogInfo();
+
await RestApiSavingFile(csv);
var s3Config = status.Config.S3;
@@ -823,7 +880,7 @@ internal static class Program
var logFileConcatenator = new LogFileConcatenator();
- var s3Path = timeStamp.ToUnixTime() + ".csv";
+ var s3Path = timeStamp.ToUnixTime() + ".json";
s3Path.WriteLine("");
var csvToSend = logFileConcatenator.ConcatenateFiles(NbrOfFileToConcatenate);
@@ -890,7 +947,7 @@ internal static class Program
//Create a zip directory and put the compressed file inside
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
{
- var entry = archive.CreateEntry("data.csv", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
+ var entry = archive.CreateEntry("data.json", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
using (var entryStream = entry.Open())
using (var writer = new StreamWriter(entryStream))
{
diff --git a/csharp/App_backup/Backend/Backend.csproj b/csharp/App_backup/Backend/Backend.csproj
new file mode 100644
index 000000000..518c98e37
--- /dev/null
+++ b/csharp/App_backup/Backend/Backend.csproj
@@ -0,0 +1,239 @@
+
+
+
+
+
+ InnovEnergy.App.Backend
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Never
+
+
+
+
+
+
+
+ ..\..\..\..\..\..\.nuget\packages\rabbitmq.client\6.6.0\lib\netstandard2.0\RabbitMQ.Client.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp/App_backup/Backend/Backend.sln b/csharp/App_backup/Backend/Backend.sln
new file mode 100644
index 000000000..c72530762
--- /dev/null
+++ b/csharp/App_backup/Backend/Backend.sln
@@ -0,0 +1,25 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.5.002.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend", "Backend.csproj", "{161624D7-33B9-48B8-BA05-303DCFAEB03E}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {161624D7-33B9-48B8-BA05-303DCFAEB03E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {161624D7-33B9-48B8-BA05-303DCFAEB03E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {161624D7-33B9-48B8-BA05-303DCFAEB03E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {161624D7-33B9-48B8-BA05-303DCFAEB03E}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {A377D3C5-E56D-4A16-AC4B-F3B0BB4CFCCE}
+ EndGlobalSection
+EndGlobal
diff --git a/csharp/App_backup/Backend/Controller.cs b/csharp/App_backup/Backend/Controller.cs
new file mode 100644
index 000000000..cb1ec796e
--- /dev/null
+++ b/csharp/App_backup/Backend/Controller.cs
@@ -0,0 +1,1015 @@
+using System.Diagnostics;
+using System.Net;
+using System.Text.RegularExpressions;
+using InnovEnergy.App.Backend.Database;
+using InnovEnergy.App.Backend.DataTypes;
+using InnovEnergy.App.Backend.DataTypes.Methods;
+using InnovEnergy.App.Backend.Relations;
+using InnovEnergy.App.Backend.Websockets;
+using InnovEnergy.Lib.Utils;
+using Microsoft.AspNetCore.Mvc;
+
+namespace InnovEnergy.App.Backend;
+
+using Token = String;
+
+// create JobStatus class to track download battery log job
+public class JobStatus
+{
+ public string JobId { get; set; }
+ public string Status { get; set; }
+ public string FileName { get; set; }
+ public DateTime StartTime { get; set; }
+}
+
+[Controller]
+[Route("api/")]
+//All the http requests from the frontend that contain "/api" will be forwarded to this controller from the nginx reverse proxy.
+public class Controller : ControllerBase
+{
+ [HttpPost(nameof(Login))]
+ public ActionResult Login(String username, String? password)
+ {
+ //Find the user to the database, verify its password and create a new session.
+ //Store the new session to the database and return it to the frontend.
+ //If the user log out, the session will be deleted. Each session is valid for 24 hours. The db deletes all invalid/expired sessions every 30 minutes.
+ var user = Db.GetUserByEmail(username);
+
+ if (user is null)
+ throw new Exceptions(400, "Null User Exception", "Must provide a user to log in as.", Request.Path.Value!);
+
+ if (!(user.Password.IsNullOrEmpty() && user.MustResetPassword) && !user.VerifyPassword(password))
+ {
+ throw new Exceptions(401, "Wrong Password Exception", "Please try again.", Request.Path.Value!);
+ }
+
+ var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user));
+
+ return Db.Create(session)
+ ? session
+ : throw new Exceptions(401,"Session Creation Exception", "Not allowed to log in.", Request.Path.Value!);
+ }
+
+
+ [HttpPost(nameof(Logout))]
+ public ActionResult Logout(Token authToken)
+ {
+ //Find the session and delete it from the database.
+ var session = Db.GetSession(authToken);
+
+ return session.Logout()
+ ? Ok()
+ : Unauthorized();
+ }
+
+
+
+ [HttpGet(nameof(CreateWebSocket))]
+ public async Task CreateWebSocket(Token authToken)
+ {
+ //Everytime a user logs in, this function is called
+ var session = Db.GetSession(authToken)?.User;
+
+ if (session is null)
+ {
+ Console.WriteLine("------------------------------------Unauthorized user----------------------------------------------");
+ HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ HttpContext.Abort();
+ return;
+ }
+
+ if (!HttpContext.WebSockets.IsWebSocketRequest)
+ {
+ Console.WriteLine("------------------------------------Not a websocket request ----------------------------------------------");
+ HttpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
+ HttpContext.Abort();
+ return;
+ }
+
+ //Create a websocket and pass its descriptor to the HandleWebSocketConnection method.
+ //This descriptor is returned to the frontend on the background
+ var webSocketContext = await HttpContext.WebSockets.AcceptWebSocketAsync();
+ var webSocket = webSocketContext;
+
+ //Handle the WebSocket connection
+ await WebsocketManager.HandleWebSocketConnection(webSocket);
+ }
+
+ [HttpGet(nameof(GetAllErrorsForInstallation))]
+ public ActionResult> GetAllErrorsForInstallation(Int64 id, Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+ if (user == null)
+ return Unauthorized();
+
+ var installation = Db.GetInstallationById(id);
+
+ if (installation is null || !user.HasAccessTo(installation))
+ return Unauthorized();
+
+ return Db.Errors
+ .Where(error => error.InstallationId == id)
+ .OrderByDescending(error => error.Date)
+ .ThenByDescending(error => error.Time)
+ .ToList();
+ }
+
+ [HttpGet(nameof(GetHistoryForInstallation))]
+ public ActionResult> GetHistoryForInstallation(Int64 id, Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+ if (user == null)
+ return Unauthorized();
+
+ var installation = Db.GetInstallationById(id);
+
+ if (installation is null || !user.HasAccessTo(installation))
+ return Unauthorized();
+
+ return Db.UserActions
+ .Where(action =>action.InstallationId == id)
+ .OrderByDescending(action => action.Timestamp)
+ .ToList();
+ }
+
+ [HttpGet(nameof(GetAllWarningsForInstallation))]
+ public ActionResult> GetAllWarningsForInstallation(Int64 id, Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+ if (user == null)
+ return Unauthorized();
+
+ var installation = Db.GetInstallationById(id);
+
+ if (installation is null || !user.HasAccessTo(installation))
+ return Unauthorized();
+
+ return Db.Warnings
+ .Where(error => error.InstallationId == id)
+ .OrderByDescending(error => error.Date)
+ .ThenByDescending(error => error.Time)
+ .ToList();
+ }
+
+
+
+ [HttpGet(nameof(GetCsvTimestampsForInstallation))]
+ public ActionResult> GetCsvTimestampsForInstallation(Int64 id, Int32 start, Int32 end, Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+ if (user == null)
+ return Unauthorized();
+
+ var installation = Db.GetInstallationById(id);
+
+ if (installation is null || !user.HasAccessTo(installation))
+ return Unauthorized();
+
+ var sampleSize = 100;
+ var allTimestamps = new List();
+
+ static string FindCommonPrefix(string str1, string str2)
+ {
+ int minLength = Math.Min(str1.Length, str2.Length);
+ int i = 0;
+ while (i < minLength && str1[i] == str2[i])
+ {
+ i++;
+ }
+ return str1.Substring(0, i);
+ }
+
+ Int64 startTimestamp = Int64.Parse(start.ToString().Substring(0,5));
+ Int64 endTimestamp = Int64.Parse(end.ToString().Substring(0,5));
+
+ if (installation.Product == (int)ProductType.Salidomo)
+ {
+
+ start = Int32.Parse(start.ToString().Substring(0, start.ToString().Length - 2));
+ end = Int32.Parse(end.ToString().Substring(0, end.ToString().Length - 2));
+ }
+
+ string configPath = "/home/ubuntu/.s3cfg";
+
+ while (startTimestamp <= endTimestamp)
+ {
+ string bucketPath = installation.Product==(int)ProductType.Salimax? "s3://"+installation.S3BucketId + "-3e5b3069-214a-43ee-8d85-57d72000c19d/"+startTimestamp :
+ "s3://"+installation.S3BucketId + "-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e/"+startTimestamp;
+ Console.WriteLine("Fetching data for "+startTimestamp);
+
+ try
+ {
+ // Set up process start info
+ ProcessStartInfo startInfo = new ProcessStartInfo
+ {
+ FileName = "s3cmd",
+ Arguments = $"--config {configPath} ls {bucketPath}",
+ RedirectStandardOutput = true,
+ RedirectStandardError = true,
+ UseShellExecute = false,
+ CreateNoWindow = true
+ };
+
+ // Start the process
+ Process process = new Process
+ {
+ StartInfo = startInfo
+ };
+
+ process.Start();
+
+ // Read the output
+ string output = process.StandardOutput.ReadToEnd();
+ string error = process.StandardError.ReadToEnd();
+
+ process.WaitForExit();
+
+ // Check for errors
+ if (process.ExitCode != 0)
+ {
+ Console.WriteLine("Error executing command:");
+ Console.WriteLine(error);
+ }
+ else
+ {
+ // Define a regex pattern to match the filenames without .csv extension
+
+ var pattern = @"/([^/]+)\.(csv|json)$";
+ var regex = new Regex(pattern);
+
+ // Process each line of the output
+ foreach (var line in output.Split('\n'))
+ {
+ var match = regex.Match(line);
+
+ if (match.Success && long.Parse(match.Groups[1].Value) >= start && long.Parse(match.Groups[1].Value) <= end)
+ {
+ allTimestamps.Add(long.Parse(match.Groups[1].Value));
+ //Console.WriteLine(match.Groups[1].Value);
+ }
+ }
+
+ }
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine($"Exception: {e.Message}");
+ }
+
+ startTimestamp++;
+ }
+
+ int totalRecords = allTimestamps.Count;
+ if (totalRecords <= sampleSize)
+ {
+ // If the total records are less than or equal to the sample size, return all records
+ Console.WriteLine("Start timestamp = "+start +" end timestamp = "+end);
+ Console.WriteLine("SampledTimestamps = " + allTimestamps.Count);
+ return allTimestamps;
+ }
+
+ int interval = totalRecords / sampleSize;
+ var sampledTimestamps = new List();
+
+ for (int i = 0; i < totalRecords; i += interval)
+ {
+ sampledTimestamps.Add(allTimestamps[i]);
+ }
+
+ // If we haven't picked enough records (due to rounding), add the latest record to ensure completeness
+ if (sampledTimestamps.Count < sampleSize)
+ {
+ sampledTimestamps.Add(allTimestamps.Last());
+ }
+
+ Console.WriteLine("Start timestamp = "+start +" end timestamp = "+end);
+ Console.WriteLine("TotalRecords = "+totalRecords + " interval = "+ interval);
+ Console.WriteLine("SampledTimestamps = " + sampledTimestamps.Count);
+
+ return sampledTimestamps;
+ }
+
+ [HttpGet(nameof(GetUserById))]
+ public ActionResult GetUserById(Int64 id, Token authToken)
+ {
+ var session = Db.GetSession(authToken)?.User;
+ if (session == null)
+ return Unauthorized();
+
+ var user = Db.GetUserById(id);
+
+ if (user is null || !session.HasAccessTo(user))
+ return Unauthorized();
+
+ return user
+ .HidePassword()
+ .HideParentIfUserHasNoAccessToParent(session);
+ }
+
+
+ [HttpGet(nameof(GetInstallationById))]
+ public ActionResult GetInstallationById(Int64 id, Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+ if (user == null)
+ return Unauthorized();
+
+ var installation = Db.GetInstallationById(id);
+
+ if (installation is null || !user.HasAccessTo(installation))
+ return Unauthorized();
+
+ return installation
+ .FillOrderNumbers()
+ .HideParentIfUserHasNoAccessToParent(user)
+ .HideWriteKeyIfUserIsNotAdmin(user.UserType);
+ }
+
+ [HttpGet(nameof(GetUsersWithDirectAccessToInstallation))]
+ public ActionResult> GetUsersWithDirectAccessToInstallation(Int64 id, Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+ if (user == null)
+ return Unauthorized();
+
+ var installation = Db.GetInstallationById(id);
+
+ if (installation is null || !user.HasAccessTo(installation))
+ return Unauthorized();
+
+ return installation
+ .UsersWithDirectAccess()
+ .Where(u => u.IsDescendantOf(user))
+ .Select(u => u.HidePassword())
+ .ToList();
+ }
+
+ [HttpGet(nameof(GetUsersWithInheritedAccessToInstallation))]
+ public ActionResult> GetUsersWithInheritedAccessToInstallation(Int64 id, Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+ if (user == null)
+ return Unauthorized();
+
+ var installation = Db.GetInstallationById(id);
+
+ if (installation is null || !user.HasAccessTo(installation))
+ return Unauthorized();
+
+ return installation
+ .Ancestors()
+ .SelectMany(f => f.UsersWithDirectAccess()
+ .Where(u => u.IsDescendantOf(user))
+ .Select(u => new { folderId = f.Id, folderName = f.Name, user = u.HidePassword() }))
+ .ToList();
+ }
+
+ [HttpGet(nameof(GetUsersWithDirectAccessToFolder))]
+ public ActionResult> GetUsersWithDirectAccessToFolder(Int64 id, Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+ if (user == null)
+ return Unauthorized();
+
+ var folder = Db.GetFolderById(id);
+
+ if (folder is null || !user.HasAccessTo(folder))
+ return Unauthorized();
+
+ return folder
+ .UsersWithDirectAccess()
+ .Where(u => u.IsDescendantOf(user))
+ .Select(u => u.HidePassword())
+ .ToList();
+ }
+
+ [HttpGet(nameof(GetUsersWithInheritedAccessToFolder))]
+ public ActionResult> GetUsersWithInheritedAccessToFolder(Int64 id, Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+ if (user == null)
+ return Unauthorized();
+
+ var folder = Db.GetFolderById(id);
+
+ if (folder is null || !user.HasAccessTo(folder))
+ return Unauthorized();
+
+ return folder
+ .Ancestors()
+ .SelectMany(f => f.UsersWithDirectAccess()
+ .Where(u => u.IsDescendantOf(user))
+ .Select(u => new { folderId = f.Id, folderName = f.Name, user = u.HidePassword() }))
+ .ToList();
+ }
+
+ [HttpGet(nameof(GetFolderById))]
+ public ActionResult GetFolderById(Int64 id, Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+ if (user == null)
+ return Unauthorized();
+
+ var folder = Db.GetFolderById(id);
+
+ if (folder is null || !user.HasAccessTo(folder))
+ return Unauthorized();
+
+ return folder.HideParentIfUserHasNoAccessToParent(user);
+ }
+
+ [HttpGet(nameof(GetAllDirectChildUsers))]
+ public ActionResult> GetAllDirectChildUsers(Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+ if (user == null)
+ return Unauthorized();
+
+ return user.ChildUsers().Select(u => u.HidePassword()).ToList();
+ }
+
+ [HttpGet(nameof(GetAllChildUsers))]
+ public ActionResult> GetAllChildUsers(Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+ if (user == null)
+ return Unauthorized();
+
+ return user
+ .DescendantUsers()
+ .Select(u => u.HidePassword())
+ .ToList();
+ }
+
+
+ [HttpGet(nameof(GetAllInstallations))]
+ public ActionResult> GetAllInstallations(Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+
+ if (user is null)
+ return Unauthorized();
+
+ return user
+ .AccessibleInstallations(product:(int)ProductType.Salimax)
+ .Select(i => i.FillOrderNumbers().HideParentIfUserHasNoAccessToParent(user).HideWriteKeyIfUserIsNotAdmin(user.UserType))
+ .ToList();
+ }
+
+ [HttpGet(nameof(GetAllSalidomoInstallations))]
+ public ActionResult> GetAllSalidomoInstallations(Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+
+ if (user is null)
+ return Unauthorized();
+
+ return user
+ .AccessibleInstallations(product:(int)ProductType.Salidomo)
+ .ToList();
+ }
+
+ [HttpGet(nameof(GetAllSodioHomeInstallations))]
+ public ActionResult> GetAllSodioHomeInstallations(Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+
+ if (user is null)
+ return Unauthorized();
+
+ return user
+ .AccessibleInstallations(product:(int)ProductType.SodioHome)
+ .ToList();
+ }
+
+
+
+ [HttpGet(nameof(GetAllFolders))]
+ public ActionResult> GetAllFolders(Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+
+ if (user is null)
+ return Unauthorized();
+
+ return new(user.AccessibleFolders().HideParentIfUserHasNoAccessToParent(user));
+ }
+
+
+ [HttpGet(nameof(GetAllFoldersAndInstallations))]
+ public ActionResult> GetAllFoldersAndInstallations(int productId, Token authToken)
+ {
+ var user = Db.GetSession(authToken)?.User;
+
+ if (user is null)
+ return Unauthorized();
+
+
+ var foldersAndInstallations = user
+ .AccessibleFoldersAndInstallations()
+ .Do(o => o.FillOrderNumbers())
+ .Select(o => o.HideParentIfUserHasNoAccessToParent(user))
+ .OfType