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 @@
       <Folder Include="resources\" />
     </ItemGroup>
 
+    <ItemGroup>
+      <Compile Remove="src\temp\**" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <EmbeddedResource Remove="src\temp\**" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <None Remove="src\temp\**" />
+    </ItemGroup>
+
 </Project>
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<Double>();
         var pvPowerSum            = new List<Double>(); 
         var heatingPower          = new List<Double>(); 
@@ -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<Double>();
         var pvPower               = new List<Double>();
         var gridPowerImport       = new List<Double>();
@@ -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<Dictionary<string, Double>>(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<Boolean> 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<HourlyData>(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<T>(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<string, object> jsonDict, String[] keys, string value)
+    {
+        
+        Dictionary<string, object> 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<string, object>();
+            }
+            
+            
+
+            if (i == keys.Length - 1) // Last key, store the value
+            {
+                
+                if (!value.Contains(",") && double.TryParse(value, out double doubleValue)) // Try to parse value as a number
+                {
+                    currentDict[key] = Math.Round(doubleValue, 2); // Round to 2 decimal places
+                   
+                }
+                else
+                {
+                    currentDict[key] = value; // Store as string if not a number
+                }
+               
+                
+            }
+            else
+            {
+                currentDict = (Dictionary<string, object>)currentDict[key];
+            }
+        }
+        
+    }
 
     private static async Task<Boolean> UploadCsv(StatusRecord status, DateTime timeStamp)
     {
 
-        status.ToJson();
-        var csv  = status.ToCsv().LogInfo();
+        //status.ToJson();
+        var csv  = status.ToCsv();
+        
+        
+        Dictionary<string, object> jsonData = new Dictionary<string, object>();
+        //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<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> 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<Boolean> 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<Double>();
         var pvPowerSum            = new List<Double>(); 
         var heatingPower          = new List<Double>(); 
@@ -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<Double>();
         var pvPower               = new List<Double>();
         var gridPowerImport       = new List<Double>();
@@ -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<Dictionary<string, Double>>(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<T>(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<T>(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<T>(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<string, object> jsonDict, String[] keys, string value)
+    {
+        
+        Dictionary<string, object> 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<string, object>();
+            }
+
+            if (i == keys.Length - 1) // Last key, store the value
+            {
+                
+                if (!value.Contains(",") && double.TryParse(value, out double doubleValue)) // Try to parse value as a number
+                {
+                    currentDict[key] = Math.Round(doubleValue, 2); // Round to 2 decimal places
+                   
+                }
+                else
+                {
+                    currentDict[key] = value; // Store as string if not a number
+                }
+            }
+            else
+            {
+                currentDict = (Dictionary<string, object>)currentDict[key];
+            }
+        }
+        
+    }
 
     private static async Task<Boolean> UploadCsv(StatusRecord status, DateTime timeStamp)
     {
-        
-        var csv  = status.ToCsv().LogInfo();
 
+        var csv = status.ToCsv();
+        
+        Dictionary<string, object> jsonData = new Dictionary<string, object>();
+        //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 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+    <Import Project="../InnovEnergy.App.props" />
+    
+    <PropertyGroup>
+        <RootNamespace>InnovEnergy.App.Backend</RootNamespace>
+    </PropertyGroup>
+    
+    <ItemGroup>
+        <PackageReference Include="AWSSDK.S3" Version="3.7.205.17" />
+        <PackageReference Include="Flurl.Http" Version="3.2.4" />
+        <PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.5.1" />
+        <PackageReference Include="Microsoft.AspNet.Identity.Core" Version="2.2.3" />
+        <PackageReference Include="Microsoft.AspNet.Identity.Owin" Version="2.2.3" />
+        <PackageReference Include="Microsoft.AspNet.WebApi.Core" Version="5.2.9" />
+        <PackageReference Include="Microsoft.AspNetCore.Authentication.Certificate" Version="6.0.21" />
+        <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
+        <PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
+        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
+        <PackageReference Include="Microsoft.Identity.Web" Version="1.26.0" />
+        <PackageReference Include="Microsoft.Owin.Cors" Version="4.2.2" />
+        <PackageReference Include="Microsoft.Owin.Host.SystemWeb" Version="4.2.2" />
+        <PackageReference Include="Microsoft.Owin.Security" Version="4.2.2" />
+        <PackageReference Include="Microsoft.Owin.Security.Cookies" Version="4.2.2" />
+        <PackageReference Include="Microsoft.Owin.Security.OAuth" Version="4.2.2" />
+        <PackageReference Include="RabbitMQ.Client" Version="6.6.0" />
+        <PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
+        <PackageReference Include="sqlite-net-sqlcipher" Version="1.9.141-beta" />
+        <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
+        <PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.6" />
+        <PackageReference Include="Swashbuckle.AspNetCore.Filters.Abstractions" Version="7.0.6" />
+        <PackageReference Include="System.Data.SQLite.Core" Version="1.0.118" />
+    </ItemGroup>
+
+  
+    <ItemGroup>
+      <ProjectReference Include="../../Lib/Utils/Utils.csproj" />
+      <ProjectReference Include="../../Lib/Mailer/Mailer.csproj" />
+      <ProjectReference Include="../../Lib/S3Utils/S3Utils.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <None Update="Resources/s3cmd.py">
+        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </None>
+      <None Remove="DbBackups\db-1698326303.sqlite" />
+      <None Remove="DbBackups\db-1698327045.sqlite" />
+      <None Remove="DbBackups\db-1699453468.sqlite" />
+      <None Remove="DbBackups\db-1699453414.sqlite" />
+      <None Remove="DbBackups\db-1699453413.sqlite" />
+      <None Remove="DbBackups\db-1699452457.sqlite" />
+      <None Remove="DbBackups\db-1699452190.sqlite" />
+      <None Remove="DbBackups\db-1699452189.sqlite" />
+      <None Remove="DbBackups\db-1699452183.sqlite" />
+      <None Remove="DbBackups\db-1699452182.sqlite" />
+      <None Remove="DbBackups\db-1699452103.sqlite" />
+      <None Remove="DbBackups\db-1699452102.sqlite" />
+      <None Remove="DbBackups\db-1699448121.sqlite" />
+      <None Remove="DbBackups\db-1699448016.sqlite" />
+      <None Remove="DbBackups\db-1699448015.sqlite" />
+      <None Remove="DbBackups\db-1699448014.sqlite" />
+      <None Remove="DbBackups\db-1699441586.sqlite" />
+      <None Remove="DbBackups\db-1699441583.sqlite" />
+      <None Remove="DbBackups\db-1699441582.sqlite" />
+      <None Remove="DbBackups\db-1699440129.sqlite" />
+      <None Remove="DbBackups\db-1699440124.sqlite" />
+      <None Remove="DbBackups\db-1699440123.sqlite" />
+      <None Remove="DbBackups\db-1699438975.sqlite" />
+      <None Remove="DbBackups\db-1699438972.sqlite" />
+      <None Remove="DbBackups\db-1699438971.sqlite" />
+      <None Remove="DbBackups\db-1699438952.sqlite" />
+      <None Remove="DbBackups\db-1699438945.sqlite" />
+      <None Remove="DbBackups\db-1699438944.sqlite" />
+      <None Remove="DbBackups\db-1699438895.sqlite" />
+      <None Remove="DbBackups\db-1699438888.sqlite" />
+      <None Remove="DbBackups\db-1699438887.sqlite" />
+      <None Remove="DbBackups\db-1699437588.sqlite" />
+      <None Remove="DbBackups\db-1699437586.sqlite" />
+      <None Remove="DbBackups\db-1699437585.sqlite" />
+      <None Remove="DbBackups\db-1699437584.sqlite" />
+      <None Remove="DbBackups\db-1699437551.sqlite" />
+      <None Remove="DbBackups\db-1699437550.sqlite" />
+      <None Remove="DbBackups\db-1699437549.sqlite" />
+      <None Remove="DbBackups\db-1699436793.sqlite" />
+      <None Remove="DbBackups\db-1699436791.sqlite" />
+      <None Remove="DbBackups\db-1699436790.sqlite" />
+      <None Remove="DbBackups\db-1699436653.sqlite" />
+      <None Remove="DbBackups\db-1699436652.sqlite" />
+      <None Remove="DbBackups\db-1699436088.sqlite" />
+      <None Remove="DbBackups\db-1699436067.sqlite" />
+      <None Remove="DbBackups\db-1699436066.sqlite" />
+      <None Remove="DbBackups\db-1699434989.sqlite" />
+      <None Remove="DbBackups\db-1699434979.sqlite" />
+      <None Remove="DbBackups\db-1699434978.sqlite" />
+      <None Remove="DbBackups\db-1699434917.sqlite" />
+      <None Remove="DbBackups\db-1699434916.sqlite" />
+      <None Remove="DbBackups\db-1699433682.sqlite" />
+      <None Remove="DbBackups\db-1699433681.sqlite" />
+      <None Remove="DbBackups\db-1699433494.sqlite" />
+      <None Remove="DbBackups\db-1699433493.sqlite" />
+      <None Remove="DbBackups\db-1699432892.sqlite" />
+      <None Remove="DbBackups\db-1699432891.sqlite" />
+      <None Remove="DbBackups\db-1699432622.sqlite" />
+      <None Remove="DbBackups\db-1699432621.sqlite" />
+      <None Remove="DbBackups\db-1699375972.sqlite" />
+      <None Remove="DbBackups\db-1699375971.sqlite" />
+      <None Remove="DbBackups\db-1699375970.sqlite" />
+      <None Remove="DbBackups\db-1699375582.sqlite" />
+      <None Remove="DbBackups\db-1699375581.sqlite" />
+      <None Remove="DbBackups\db-1699375265.sqlite" />
+      <None Remove="DbBackups\db-1699375264.sqlite" />
+      <None Remove="DbBackups\db-1699375174.sqlite" />
+      <None Remove="DbBackups\db-1699375173.sqlite" />
+      <None Remove="DbBackups\db-1699375167.sqlite" />
+      <None Remove="DbBackups\db-1699375166.sqlite" />
+      <None Remove="DbBackups\db-1699374877.sqlite" />
+      <None Remove="DbBackups\db-1699374876.sqlite" />
+      <None Remove="DbBackups\db-1699374338.sqlite" />
+      <None Remove="DbBackups\db-1699374337.sqlite" />
+      <None Remove="DbBackups\db-1699374216.sqlite" />
+      <None Remove="DbBackups\db-1699374215.sqlite" />
+      <None Remove="DbBackups\db-1699369902.sqlite" />
+      <None Remove="DbBackups\db-1699369901.sqlite" />
+      <None Remove="DbBackups\db-1699369278.sqlite" />
+      <None Remove="DbBackups\db-1699369277.sqlite" />
+      <None Remove="DbBackups\db-1699368950.sqlite" />
+      <None Remove="DbBackups\db-1699368949.sqlite" />
+      <None Remove="DbBackups\db-1699368806.sqlite" />
+      <None Remove="DbBackups\db-1699368805.sqlite" />
+      <None Remove="DbBackups\db-1699368804.sqlite" />
+      <None Remove="DbBackups\db-1699366271.sqlite" />
+      <None Remove="DbBackups\db-1699366256.sqlite" />
+      <None Remove="DbBackups\db-1699366255.sqlite" />
+      <None Remove="DbBackups\db-1699366240.sqlite" />
+      <None Remove="DbBackups\db-1699366239.sqlite" />
+      <None Remove="DbBackups\db-1699366132.sqlite" />
+      <None Remove="DbBackups\db-1699365906.sqlite" />
+      <None Remove="DbBackups\db-1699365905.sqlite" />
+      <None Remove="DbBackups\db-1698656873.sqlite" />
+      <None Remove="DbBackups\db-1698656872.sqlite" />
+      <None Remove="DbBackups\db-1698330524.sqlite" />
+      <None Remove="DbBackups\db-1698330511.sqlite" />
+      <None Remove="DbBackups\db-1698330510.sqlite" />
+      <None Remove="DbBackups\db-1698330455.sqlite" />
+      <None Remove="DbBackups\db-1698330444.sqlite" />
+      <None Remove="DbBackups\db-1698330406.sqlite" />
+      <None Remove="DbBackups\db-1698330386.sqlite" />
+      <None Remove="DbBackups\db-1698330385.sqlite" />
+      <None Remove="DbBackups\db-1698329746.sqlite" />
+      <None Remove="DbBackups\db-1698329745.sqlite" />
+      <None Remove="DbBackups\db-1698329744.sqlite" />
+      <None Remove="DbBackups\db-1698329652.sqlite" />
+      <None Remove="DbBackups\db-1698329603.sqlite" />
+      <None Remove="DbBackups\db-1698329346.sqlite" />
+      <None Remove="DbBackups\db-1698329331.sqlite" />
+      <None Remove="DbBackups\db-1698329329.sqlite" />
+      <None Remove="DbBackups\db-1698329274.sqlite" />
+      <None Remove="DbBackups\db-1698329086.sqlite" />
+      <None Remove="DbBackups\db-1698329070.sqlite" />
+      <None Remove="DbBackups\db-1698329067.sqlite" />
+      <None Remove="DbBackups\db-1698329009.sqlite" />
+      <None Remove="DbBackups\db-1698328961.sqlite" />
+      <None Remove="DbBackups\db-1698328621.sqlite" />
+      <None Remove="DbBackups\db-1698328605.sqlite" />
+      <None Remove="DbBackups\db-1698328557.sqlite" />
+      <None Remove="DbBackups\db-1698328538.sqlite" />
+      <None Remove="DbBackups\db-1698328537.sqlite" />
+      <None Remove="DbBackups\db-1698328516.sqlite" />
+      <None Remove="DbBackups\db-1698328504.sqlite" />
+      <None Remove="DbBackups\db-1698328489.sqlite" />
+      <None Remove="DbBackups\db-1698328461.sqlite" />
+      <None Remove="DbBackups\db-1698328447.sqlite" />
+      <None Remove="DbBackups\db-1698328381.sqlite" />
+      <None Remove="DbBackups\db-1698328203.sqlite" />
+      <None Remove="DbBackups\db-1698328201.sqlite" />
+      <None Remove="DbBackups\db-1698328184.sqlite" />
+      <None Remove="DbBackups\db-1698328174.sqlite" />
+      <None Remove="DbBackups\db-1698328173.sqlite" />
+      <None Remove="DbBackups\db-1698327908.sqlite" />
+      <None Remove="DbBackups\db-1698327870.sqlite" />
+      <None Remove="DbBackups\db-1698327855.sqlite" />
+      <None Remove="DbBackups\db-1698327854.sqlite" />
+      <None Remove="DbBackups\db-1698327853.sqlite" />
+      <None Remove="DbBackups\db-1698327737.sqlite" />
+      <None Remove="DbBackups\db-1698327658.sqlite" />
+      <None Remove="DbBackups\db-1698327641.sqlite" />
+      <None Remove="DbBackups\db-1698327640.sqlite" />
+      <None Remove="DbBackups\db-1698327639.sqlite" />
+      <None Remove="DbBackups\db-1698327576.sqlite" />
+      <None Remove="DbBackups\db-1698327461.sqlite" />
+      <None Remove="DbBackups\db-1698327450.sqlite" />
+      <None Remove="DbBackups\db-1698327449.sqlite" />
+      <None Remove="DbBackups\db-1698327398.sqlite" />
+      <None Remove="DbBackups\db-1698327351.sqlite" />
+      <None Remove="DbBackups\db-1698327339.sqlite" />
+      <None Remove="DbBackups\db-1698327338.sqlite" />
+      <None Remove="DbBackups\db-1698327227.sqlite" />
+      <None Remove="DbBackups\db-1698327194.sqlite" />
+      <None Remove="DbBackups\db-1698327133.sqlite" />
+      <None Remove="DbBackups\db-1698327071.sqlite" />
+      <None Remove="DbBackups\db-1698327022.sqlite" />
+      <None Remove="DbBackups\db-1698326991.sqlite" />
+      <None Remove="DbBackups\db-1698326990.sqlite" />
+      <None Remove="DbBackups\db-1698326807.sqlite" />
+      <None Remove="DbBackups\db-1698326334.sqlite" />
+      <None Remove="DbBackups\db-1698326333.sqlite" />
+      <None Remove="DbBackups\db-1698326332.sqlite" />
+      <None Remove="DbBackups\db-1698326302.sqlite" />
+      <None Remove="DbBackups\db-1698325689.sqlite" />
+      <None Remove="DbBackups\db-1698325688.sqlite" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <Content Update="Resources/urlAndKey.json">
+        <CopyToPublishDirectory>Never</CopyToPublishDirectory>
+      </Content>
+    </ItemGroup>
+
+
+
+    <ItemGroup>
+      <Reference Include="RabbitMQ.Client">
+        <HintPath>..\..\..\..\..\..\.nuget\packages\rabbitmq.client\6.6.0\lib\netstandard2.0\RabbitMQ.Client.dll</HintPath>
+      </Reference>
+    </ItemGroup>
+
+
+
+    <ItemGroup>
+      <Compile Remove="DataTypes\CsvName.cs" />
+    </ItemGroup>
+
+
+
+
+
+
+
+</Project>
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<Session> 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<IEnumerable<Error>> 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<IEnumerable<UserAction>> 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<IEnumerable<Warning>> 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<IEnumerable<Int64>> 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<Int64>();
+            
+        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<Int64>();
+        
+        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<User> 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<Installation> 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<IEnumerable<Object>> 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<IEnumerable<Object>> 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<IEnumerable<Object>> 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<IEnumerable<Object>> 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<Folder> 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<IEnumerable<User>> 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<IEnumerable<User>> 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<IEnumerable<Installation>> 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<IEnumerable<Installation>> 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<IEnumerable<Installation>> 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<IEnumerable<Folder>> 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<IEnumerable<Object>> 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<Object>();  // Important! JSON serializer must see Objects otherwise
+                                // it will just serialize the members of TreeNode %&@#!!!
+        
+                                // TODO Filter out write keys
+        return new (foldersAndInstallations);
+    }
+    
+
+    [HttpPost(nameof(CreateUser))]
+    public async Task<ActionResult<User>> CreateUser([FromBody] User newUser, Token authToken)
+    {
+        
+        var create = Db.GetSession(authToken).Create(newUser);
+        if (create)
+        {
+            var mail_success= await Db.SendNewUserEmail(newUser);
+            if (!mail_success)
+            {
+                Db.GetSession(authToken).Delete(newUser);
+            }
+            
+            return mail_success ? newUser.HidePassword():Unauthorized();
+        }
+
+        return Unauthorized() ;
+    }
+    
+    [HttpPost(nameof(CreateInstallation))]
+    public async Task<ActionResult<Installation>> CreateInstallation([FromBody] Installation installation, Token authToken)
+    {
+        
+        var session = Db.GetSession(authToken);
+        
+        if (! await session.Create(installation))
+            return Unauthorized();
+        
+        return installation;
+    }
+    
+    [HttpPost(nameof(CreateFolder))]
+    public ActionResult<Folder> CreateFolder([FromBody] Folder folder, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+
+        if (!session.Create(folder))
+            return Unauthorized();
+        
+        return folder.HideParentIfUserHasNoAccessToParent(session!.User);
+    }
+    
+    [HttpPost(nameof(GrantUserAccessToFolder))]
+    public ActionResult GrantUserAccessToFolder(FolderAccess folderAccess, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+
+        // TODO: automatic BadRequest when properties are null during deserialization
+        var folder = Db.GetFolderById(folderAccess.FolderId);
+        var user   = Db.GetUserById(folderAccess.UserId);
+
+        return session.GrantUserAccessTo(user, folder) 
+            ? Ok()
+            : Unauthorized();
+    }
+    
+    
+    [HttpPost(nameof(RevokeUserAccessToFolder))]
+    public ActionResult RevokeUserAccessToFolder(FolderAccess folderAccess, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+
+        // TODO: automatic BadRequest when properties are null during deserialization
+        var folder = Db.GetFolderById(folderAccess.FolderId);
+        var user   = Db.GetUserById(folderAccess.UserId);
+
+        return session.RevokeUserAccessTo(user, folder) 
+             ? Ok()
+             : Unauthorized();
+    }
+    
+    
+    [HttpPost(nameof(GrantUserAccessToInstallation))]
+    public ActionResult GrantUserAccessToInstallation(InstallationAccess installationAccess, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+       
+        // TODO: automatic BadRequest when properties are null during deserialization
+        var installation = Db.GetInstallationById(installationAccess.InstallationId);
+        var user         = Db.GetUserById(installationAccess.UserId);
+        
+        return session.GrantUserAccessTo(user, installation) 
+             ? Ok()
+             : Unauthorized();
+    }
+    
+    [HttpPost(nameof(RevokeUserAccessToInstallation))]
+    public ActionResult RevokeUserAccessToInstallation(InstallationAccess installationAccess, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+       
+        // TODO: automatic BadRequest when properties are null during deserialization
+        var installation = Db.GetInstallationById(installationAccess.InstallationId);
+        var user         = Db.GetUserById(installationAccess.UserId);
+        
+        return session.RevokeUserAccessTo(user, installation) 
+             ? Ok()
+             : Unauthorized();
+    }
+    
+    
+    
+    [HttpPut(nameof(UpdateUser))]
+    public ActionResult<User> UpdateUser([FromBody]  User updatedUser, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+
+        if (!session.Update(updatedUser)) 
+            return Unauthorized();
+
+        return updatedUser.HidePassword();
+    }
+    
+    
+    [HttpPut(nameof(UpdatePassword))]
+    public ActionResult<User> UpdatePassword(String newPassword, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+
+        return session.UpdatePassword(newPassword)
+            ? Ok()
+            : Unauthorized();
+    }
+    
+    
+    
+    [HttpPut(nameof(UpdateInstallation))]
+    public ActionResult<Installation> UpdateInstallation([FromBody] Installation installation, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+
+        if (!session.Update(installation))
+            return Unauthorized();
+
+        if (installation.Product == (int)ProductType.Salimax)
+        { 
+            return installation.FillOrderNumbers().HideParentIfUserHasNoAccessToParent(session!.User).HideWriteKeyIfUserIsNotAdmin(session.User.UserType);
+        }
+        
+        return installation.HideParentIfUserHasNoAccessToParent(session!.User);
+    }
+    
+    [HttpPost(nameof(AcknowledgeError))]
+    public ActionResult AcknowledgeError(Int64 id, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+        
+        if (session == null)
+            return Unauthorized();
+        
+        var error=Db.Errors
+            .FirstOrDefault(error => error.Id == id);
+
+        error.Seen = true;
+        
+        return Db.Update(error)
+            ? Ok()
+            : Unauthorized();
+    }
+    
+    [HttpPost(nameof(AcknowledgeWarning))]
+    public ActionResult AcknowledgeWarning(Int64 id, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+        
+        if (session == null)
+            return Unauthorized();
+        
+        var warning=Db.Warnings
+            .FirstOrDefault(warning => warning.Id == id);
+
+        warning.Seen = true;
+        
+        return Db.Update(warning)
+            ? Ok()
+            : Unauthorized();
+    }
+    
+    [HttpPut(nameof(UpdateFolder))]
+    public ActionResult<Folder> UpdateFolder([FromBody] Folder folder, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+
+        if (!session.Update(folder))
+            return Unauthorized();
+        
+        return folder.HideParentIfUserHasNoAccessToParent(session!.User);
+    }
+    
+    [HttpPut(nameof(MoveInstallation))]
+    public ActionResult MoveInstallation(Int64 installationId,Int64 parentId, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+
+        return session.MoveInstallation(installationId, parentId)
+            ? Ok()
+            : Unauthorized();
+    }
+    
+    [HttpPost(nameof(UpdateFirmware))]
+    public async Task<ActionResult> UpdateFirmware(Int64 batteryNode, Int64 installationId,String version,Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+        var installationToUpdate = Db.GetInstallationById(installationId);
+        
+
+        if (installationToUpdate != null)
+        {
+            _ = session.RunScriptInBackground(installationToUpdate.VpnIp, batteryNode,version,installationToUpdate.Product);
+        }
+
+        return Ok();
+    }
+    
+    private static Dictionary<string, JobStatus> JobStatuses = new Dictionary<string, JobStatus>();
+
+    [HttpPost("StartDownloadBatteryLog")]
+    public async Task<ActionResult<string>> StartDownloadBatteryLog(long batteryNode, long installationId, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+        var installationToDownload = Db.GetInstallationById(installationId);
+
+        if (installationToDownload != null)
+        {
+            string jobId = Guid.NewGuid().ToString();
+            _ = Task.Run(async () =>
+            {
+                await session.RunDownloadLogScript(installationToDownload.VpnIp, batteryNode, installationToDownload.Product);
+                string fileName = $"{installationToDownload.VpnIp}-node{batteryNode}-{DateTime.Now:dd-MM-yyyy}.bin";
+                string filePath = $"/home/ubuntu/backend/downloadBatteryLog/{fileName}";
+
+                if (System.IO.File.Exists(filePath))
+                {
+                    SaveJobStatus(jobId, "Completed", fileName:fileName);
+                }
+                else
+                {
+                    SaveJobStatus(jobId, "Failed");
+                }
+            });
+
+            // Store initial job status in in-memory storage
+            SaveJobStatus(jobId, "Processing");
+
+            return Ok(jobId);
+        }
+
+        return NotFound();
+    }
+
+    [HttpGet("DownloadBatteryLog")]
+    public async Task<ActionResult> DownloadBatteryLog(string jobId)
+    
+    {
+        Console.WriteLine("-----------------------------------Start uploading battery log-----------------------------------");
+        var jobStatus = JobStatuses.TryGetValue(jobId, out var status) ? status : null;
+        if (jobStatus == null || jobStatus.Status != "Completed" || string.IsNullOrEmpty(jobStatus.FileName))
+        {
+            return NotFound();
+        }
+
+        string fileName = jobStatus.FileName;
+        string filePath = $"/home/ubuntu/backend/downloadBatteryLog/{fileName}";
+
+        if (!System.IO.File.Exists(filePath))
+        {
+            return NotFound();
+        }
+
+        string contentType = "application/octet-stream";
+        var memory = new MemoryStream();
+        await using (var stream = new FileStream(filePath, FileMode.Open))
+        {
+            await stream.CopyToAsync(memory);
+        }
+        memory.Position = 0;
+
+        var fileContentResult = new FileContentResult(memory.ToArray(), contentType)
+        {
+            //FileDownloadName = Path.GetFileName(filePath)
+            FileDownloadName = fileName
+        };
+        
+        Console.WriteLine("-----------------------------------Stop uploading battery log-----------------------------------");
+
+        return fileContentResult;
+    }
+    
+    [HttpDelete("DeleteBatteryLog")]
+    public IActionResult DeleteBatteryLog(string fileName)
+    {
+        Console.WriteLine("-----------------------------------Start deleting downloaded battery log-----------------------------------");
+        string filePath = $"/home/ubuntu/backend/downloadBatteryLog/{fileName}";
+        try
+        {
+            if (System.IO.File.Exists(filePath))
+            {
+                System.IO.File.Delete(filePath);
+                Console.WriteLine("-----------------------------------Stop deleting downloaded battery log-----------------------------------");
+                return Ok();
+            }
+            else
+            {
+                return NotFound("File not found.");
+            }
+        }
+        catch (Exception ex)
+        {
+            return StatusCode(500, $"Internal server error: {ex.Message}");
+        }
+    }
+    
+    private void SaveJobStatus(string jobId, string status, string fileName = null)
+    {
+        JobStatuses[jobId] = new JobStatus
+        {
+            JobId = jobId,
+            Status = status,
+            FileName = fileName,
+            StartTime = DateTime.UtcNow // Initialize StartTime when saving
+        };
+    }
+    
+    [HttpGet("GetJobResult")]
+    public ActionResult GetJobResult(string jobId)
+    {
+        if (string.IsNullOrEmpty(jobId))
+        {
+            return BadRequest(new { status = "Error", message = "Job ID is required." });
+        }
+        
+        if (!JobStatuses.TryGetValue(jobId, out var jobStatus))
+        {
+            return NotFound();
+        }
+
+        if (jobStatus.Status == "Completed")
+        {
+            return Ok(new { status = "Completed",  fileName = jobStatus.FileName });
+        }
+        else if (jobStatus.Status == "Failed")
+        {
+            return StatusCode(500, new { status = "Failed", message = "Job processing failed." });
+        }
+        else if (jobStatus.Status == "Processing")
+        {
+            // Check for timeout
+            var startTime = jobStatus.StartTime;
+            var currentTime = DateTime.UtcNow;
+
+            if ((currentTime - startTime).TotalMinutes > 60)//60 minutes as timeout => Running multiple tasks in parallel on a crowded backend server will increase the time each task takes to complete
+            {
+                return StatusCode(500, new { status = "Failed", message = "Job in back end timeout exceeded." });
+            }
+
+            return Ok(new { status = "Processing" });
+        }
+        else
+        {
+            return BadRequest(new { status = "Unknown", message = "Unknown job status." });
+        }
+    }
+    
+    
+    [HttpPost(nameof(InsertNewAction))]
+    public async Task<ActionResult<IEnumerable<Object>>> InsertNewAction([FromBody] UserAction action, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+        var actionSuccess = await session.InsertUserAction(action);
+        return actionSuccess ? Ok() : Unauthorized();
+
+    }
+    
+    [HttpPost(nameof(UpdateAction))]
+    public async Task<ActionResult<IEnumerable<Object>>> UpdateAction([FromBody] UserAction action, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+        var actionSuccess = await session.UpdateUserAction(action);
+        return actionSuccess ? Ok() : Unauthorized();
+    }
+
+    
+    [HttpPost(nameof(DeleteAction))]
+    public async Task<ActionResult<IEnumerable<Object>>> DeleteAction(Int64 actionId, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+        var actionSuccess = await session.DeleteUserAction(actionId);
+        return actionSuccess ? Ok() : Unauthorized();
+    }
+    
+        
+    [HttpPost(nameof(EditInstallationConfig))]
+    public async Task<ActionResult<IEnumerable<Object>>> EditInstallationConfig([FromBody] Configuration config, Int64 installationId,Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+        
+        // Send configuration changes
+        var success = await session.SendInstallationConfig(installationId, config);
+
+        // Record configuration change
+        if (success)
+        {
+            // Create a new UserAction object
+            var action = new UserAction
+            {
+                InstallationId = installationId,
+                Timestamp = DateTime.Now,
+                Description = config.GetConfigurationString()
+            };
+            
+            var actionSuccess = await session.InsertUserAction(action);
+            return actionSuccess?Ok():Unauthorized();
+        }
+        
+        return Unauthorized();
+        
+    }
+    
+    [HttpPut(nameof(MoveFolder))]
+    public ActionResult MoveFolder(Int64 folderId,Int64 parentId, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+
+        return session.MoveFolder(folderId, parentId)
+            ? Ok()
+            : Unauthorized();
+    }
+    
+    [HttpDelete(nameof(DeleteUser))]
+    public ActionResult DeleteUser(Int64 userId, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+        var user    = Db.GetUserById(userId);
+
+        return session.Delete(user) 
+             ? Ok()
+             : Unauthorized();
+    }
+    
+    [HttpDelete(nameof(DeleteInstallation))]
+    public async Task<ActionResult> DeleteInstallation(Int64 installationId, Token authToken)
+    {
+        var session      = Db.GetSession(authToken);
+        var installation = Db.GetInstallationById(installationId);
+
+        return await session.Delete(installation)
+             ? Ok()  
+             : Unauthorized();
+    }
+
+    [HttpDelete(nameof(DeleteFolder))]
+    public ActionResult DeleteFolder(Int64 folderId, Token authToken)
+    {
+        var session = Db.GetSession(authToken);
+        var folder  = Db.GetFolderById(folderId);
+
+        return session.Delete(folder) 
+             ? Ok() 
+             : Unauthorized();
+        
+    }
+    
+    [HttpPost(nameof(ResetPasswordRequest))]
+    public async Task<ActionResult<IEnumerable<Object>>> ResetPasswordRequest(String username)
+    {
+        var user = Db.GetUserByEmail(username);
+        
+        if (user is null)
+            return Unauthorized();
+        
+        var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user));
+        var success = Db.Create(session);
+        
+        return success && await Db.SendPasswordResetEmail(user, session.Token)
+             ? Ok() 
+             : Unauthorized();
+    }
+
+
+    [HttpGet(nameof(ResetPassword))]
+    public ActionResult<Object> ResetPassword(Token token)
+    {
+        var user = Db.GetSession(token)?.User;
+
+        if (user is null)
+            return Unauthorized();
+
+        Db.DeleteUserPassword(user);
+
+        return Redirect($"https://monitor.innov.energy/?username={user.Email}&reset=true"); // TODO: move to settings file
+    }
+
+}
+
+
+
diff --git a/csharp/App_backup/Backend/DataTypes/Configuration.cs b/csharp/App_backup/Backend/DataTypes/Configuration.cs
new file mode 100644
index 000000000..76ac6edbe
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/Configuration.cs
@@ -0,0 +1,20 @@
+namespace InnovEnergy.App.Backend.DataTypes;
+
+public class Configuration
+{
+    public Double  MinimumSoC                                    { get; set; }
+    public Double  GridSetPoint                                  { get; set; } 
+    public CalibrationChargeType CalibrationChargeState          { get; set; } 
+    public DateTime CalibrationChargeDate                        { get; set; } 
+    public String GetConfigurationString()
+    {
+        return $"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}";
+    }
+}
+
+public enum CalibrationChargeType
+{
+    RepetitivelyEvery,
+    AdditionallyOnce,
+    ChargePermanently
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/Error.cs b/csharp/App_backup/Backend/DataTypes/Error.cs
new file mode 100644
index 000000000..bbddb018a
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/Error.cs
@@ -0,0 +1,24 @@
+using SQLite;
+
+namespace InnovEnergy.App.Backend.DataTypes;
+
+public abstract class LogEntry
+{
+    [PrimaryKey, AutoIncrement]
+    public  Int64   Id                     { get; set; }
+    public Int64    InstallationId         { get; set; }
+    public String   Description            { get; set; } = null!;
+    public String Date                     { get; set; } = null!;
+    public String Time                     { get; set; } = null!;
+    public String   DeviceCreatedTheMessage{ get; set; } = null!;
+    public Boolean  Seen                   { get; set; }
+}
+
+// Derived class for errors
+public class Error : LogEntry
+{
+}
+
+public class Warning : LogEntry
+{
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/Folder.cs b/csharp/App_backup/Backend/DataTypes/Folder.cs
new file mode 100644
index 000000000..e953821bc
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/Folder.cs
@@ -0,0 +1,3 @@
+namespace InnovEnergy.App.Backend.DataTypes;
+
+public class Folder : TreeNode { }   
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/Installation.cs b/csharp/App_backup/Backend/DataTypes/Installation.cs
new file mode 100644
index 000000000..19e174222
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/Installation.cs
@@ -0,0 +1,49 @@
+using SQLite;
+
+namespace InnovEnergy.App.Backend.DataTypes;
+
+public enum ProductType
+{
+    Salimax = 0,
+    Salidomo = 1,
+    SodioHome =2
+}
+
+public enum StatusType
+{
+    Offline = -1,
+    Green = 0,
+    Warning = 1,
+    Alarm = 2
+}
+
+public class Installation : TreeNode
+{
+    //Each installation has 2 roles, a read role and a write role.
+    //There are 2 keys per role a public key and a secret
+    //Product can be 0 or 1, 0 for Salimax, 1 for Salidomo
+    public String Location         { get; set; } = "";
+    public String Region           { get; set; } = "";
+    public String Country          { get; set; } = "";
+    public String VpnIp            { get; set; } = "";
+    public String InstallationName { get; set; } = "";
+    
+    public String S3Region      { get; set; } = "sos-ch-dk-2";
+    public String S3Provider    { get; set; } = "exo.io";
+    public String S3WriteKey    { get; set; } = "";
+    public String S3Key         { get; set; } = "";
+    public String S3WriteSecret { get; set; } = "";
+    public String S3Secret      { get; set; } = "";
+    public int    S3BucketId    { get; set; } = 0;
+    public String ReadRoleId    { get; set; } = "";
+    public String WriteRoleId   { get; set; } = "";
+    public Boolean TestingMode  { get; set; } = false;
+    public int     Status       { get; set; } = -1;
+    public int Product          { get; set; } = (int)ProductType.Salimax;
+    public int Device           { get; set; } = 0;
+    public string SerialNumber  { get; set; } = "";
+    
+    [Ignore]
+    public String OrderNumbers  { get; set; }
+    public String VrmLink       { get; set; } = "";
+}   
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/Methods/ExoCmd.cs b/csharp/App_backup/Backend/DataTypes/Methods/ExoCmd.cs
new file mode 100644
index 000000000..56d3e991e
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/Methods/ExoCmd.cs
@@ -0,0 +1,445 @@
+using System.Text.Json;
+using InnovEnergy.Lib.S3Utils;
+using InnovEnergy.Lib.S3Utils.DataTypes;
+using System.Diagnostics.CodeAnalysis;
+using System.Net;
+using System.Net.Http.Headers;
+using System.Net.Sockets;
+using System.Text;
+using System.Text.Json.Nodes;
+using InnovEnergy.App.Backend.Database;
+
+namespace InnovEnergy.App.Backend.DataTypes.Methods;
+
+public static class ExoCmd
+{
+    
+    [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
+    public static readonly S3Credentials S3Credentials = JsonSerializer.Deserialize<S3Credentials>(File.OpenRead("./Resources/exoscaleS3.json"))!;
+
+    private static Byte[] HmacSha256Digest(String message, String secret)
+    {
+        var encoding = new UTF8Encoding();
+        var keyBytes = encoding.GetBytes(secret);
+        var messageBytes = encoding.GetBytes(message);
+        var cryptographer = new System.Security.Cryptography.HMACSHA256(keyBytes);
+
+        var bytes = cryptographer.ComputeHash(messageBytes);
+        return bytes;
+    }
+
+
+    private static String BuildSignature(String method, String path, String? data, Int64 time)
+    {
+        var messageToSign = "";
+        messageToSign += method + " /v2/" + path + "\n";
+        messageToSign += data + "\n";
+
+        // query strings
+        messageToSign += "\n";
+        // headers
+        messageToSign += "\n";
+
+        messageToSign += time;
+
+        //Console.WriteLine("Message to sign:\n" + messageToSign);
+
+
+        var hmac = HmacSha256Digest(messageToSign, S3Credentials.Secret);
+        return Convert.ToBase64String(hmac);
+    }
+    
+    private static String BuildSignature(String method, String path, Int64 time)
+    {
+        return BuildSignature(method, path, null, time);
+    }
+
+    public static async Task<JsonArray> GetAccessKeys()
+    {
+
+        var url = "https://api-ch-dk-2.exoscale.com/v2/api-key";
+        var method = "api-key";
+        var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
+
+        var authheader = "credential="+S3Credentials.Key+",signed-query-args="+",expires="+unixtime+",signature="+BuildSignature("GET", method, unixtime);
+        
+        var client = new HttpClient();
+        
+        client.DefaultRequestHeaders.Authorization =
+            new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
+
+        var response = await client.GetAsync(url);
+        var responseString = await response.Content.ReadAsStringAsync(); 
+
+        var responseJson = JsonNode.Parse(responseString) ;
+        Console.WriteLine(responseJson["api-keys"].AsArray().Count);
+
+        return responseJson["api-keys"].AsArray();
+    }
+
+    public static async Task<(String,String)> CreateReadKey(this Installation installation)
+    {
+        var readRoleId = installation.ReadRoleId;
+        
+
+        if (String.IsNullOrEmpty(readRoleId) 
+            ||! await CheckRoleExists(readRoleId))
+        {
+            readRoleId = await installation.CreateReadRole();
+            Thread.Sleep(4000);         // Exoscale is to slow for us the role might not be there yet
+        }
+        return await CreateKey(installation, readRoleId);
+    }
+
+    private static async Task<Boolean> CheckRoleExists(String roleId)
+    {
+        const String url = "https://api-ch-dk-2.exoscale.com/v2/iam-role";
+        const String method = "iam-role";
+
+        var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
+
+        var authheader = "credential="+S3Credentials.Key+",signed-query-args="+",expires="+unixtime+",signature="+BuildSignature("GET", method, unixtime);
+        
+        var client = new HttpClient();
+        
+        client.DefaultRequestHeaders.Authorization =
+            new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
+
+        var response = await client.GetAsync(url);
+        var responseString = await response.Content.ReadAsStringAsync();
+        return responseString.Contains(roleId);
+    }
+
+    private static async Task<(String,String)> CreateKey(Installation installation, String roleName)
+    {
+        var url = "https://api-ch-dk-2.exoscale.com/v2/api-key";
+        var method = "api-key";
+        var contentString = $$"""{"role-id": "{{roleName}}", "name":"{{ installation.BucketName()}}"}""";
+        var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60;
+        
+        
+        var authheader = "credential=" + S3Credentials.Key + ",expires=" + unixtime + ",signature=" +
+                         BuildSignature("POST", method, contentString, unixtime);
+
+        var client = new HttpClient();
+
+        client.DefaultRequestHeaders.Authorization =
+            new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
+        var content = new StringContent(contentString, Encoding.UTF8, "application/json");
+
+        var response = await client.PostAsync(url, content);
+        if (response.StatusCode != HttpStatusCode.OK){
+            Console.WriteLine("Fuck");
+        }
+        //Console.WriteLine($"Created Key for {installation.InstallationName}");
+        var responseString = await response.Content.ReadAsStringAsync(); 
+
+        var responseJson = JsonNode.Parse(responseString) ;
+        return (responseJson!["key"]!.GetValue<String>(), responseJson!["secret"]!.GetValue<String>());
+    }
+
+
+    public static async Task<String> CreateReadRole(this Installation installation)
+    {
+        const String url = "https://api-ch-dk-2.exoscale.com/v2/iam-role";
+        const String method = "iam-role";
+        String rolename = installation.Product==(int)ProductType.Salimax?Db.Installations.Count(f => f.Product == (int)ProductType.Salimax) + installation.Name:Db.Installations.Count(f => f.Product == (int)ProductType.Salidomo) + installation.Name;
+
+
+        var contentString = $$"""
+                              {
+                              "name" : "{{rolename}}",
+                              "policy" : {
+                                  "default-service-strategy": "deny",
+                                  "services": {
+                                      "sos": {
+                                          "type": "rules",
+                                          "rules": [
+                                              {
+                                                  "expression": "operation == 'get-object' && resources.bucket.startsWith('{{installation.BucketName()}}')",
+                                                  "action": "allow"
+                                              }
+                                          ]
+                                      }
+                                  }
+                                }
+                              }
+                              """;
+
+        var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
+        var authheader = "credential="+S3Credentials.Key+",signed-query-args="+",expires="+unixtime+",signature="+BuildSignature("POST", method, contentString, unixtime);
+        
+        var client = new HttpClient();
+        
+        client.DefaultRequestHeaders.Authorization =
+            new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
+        var content = new StringContent(contentString, Encoding.UTF8, "application/json");
+
+
+        var response = await client.PostAsync(url, content);
+        
+        var responseString = await response.Content.ReadAsStringAsync();
+        //Console.WriteLine(responseString);
+        
+        //Put Role ID into database
+        var id = JsonNode.Parse(responseString)!["reference"]!["id"]!.GetValue<String>();
+        installation.ReadRoleId = id;
+        Db.Update(installation);
+        return id;
+    }
+    
+    public static async Task<bool> RemoveReadRole(this Installation installation)
+    {
+        var roleId = installation.ReadRoleId;
+        var url = $"https://api-ch-dk-2.exoscale.com/v2/iam-role/{roleId}";
+        var method = $"iam-role/{roleId}";
+
+        var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60;
+        var authheader = "credential=" + S3Credentials.Key  + ",expires=" + unixtime + ",signature=" + BuildSignature("DELETE", method, unixtime);
+        
+        
+        var client = new HttpClient();
+
+        client.DefaultRequestHeaders.Authorization =
+            new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
+
+        try
+        {
+            var response = await client.DeleteAsync(url);
+
+            if (response.IsSuccessStatusCode)
+            {
+                Console.WriteLine($"Successfully deleted read role with ID {roleId}.");
+                return true;
+            }
+           
+            Console.WriteLine($"Failed to delete read role. HTTP Status: {response.StatusCode}. Response: {await response.Content.ReadAsStringAsync()}");
+            return false;
+        }
+        catch (Exception ex)
+        {
+            Console.WriteLine($"Error occurred while deleting read role: {ex.Message}");
+            return false;
+        }
+    }
+    
+    public static async Task<bool> RemoveWriteRole(this Installation installation)
+    {
+        var roleId = installation.WriteRoleId;
+        var url = $"https://api-ch-dk-2.exoscale.com/v2/iam-role/{roleId}";
+        var method = $"iam-role/{roleId}";
+
+        var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60;
+        var authheader = "credential=" + S3Credentials.Key  + ",expires=" + unixtime + ",signature=" + BuildSignature("DELETE", method, unixtime);
+        
+        
+        var client = new HttpClient();
+
+        client.DefaultRequestHeaders.Authorization =
+            new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
+
+        try
+        {
+            var response = await client.DeleteAsync(url);
+
+            if (response.IsSuccessStatusCode)
+            {
+                Console.WriteLine($"Successfully deleted write role with ID {roleId}.");
+                return true;
+            }
+           
+            Console.WriteLine($"Failed to delete write role. HTTP Status: {response.StatusCode}. Response: {await response.Content.ReadAsStringAsync()}");
+            return false;
+        }
+        catch (Exception ex)
+        {
+            Console.WriteLine($"Error occurred while deleting write role: {ex.Message}");
+            return false;
+        }
+    }
+    
+    
+    public static async Task<Boolean> RevokeReadKey(this Installation installation)
+    {
+        //Check exoscale documentation https://openapi-v2.exoscale.com/topic/topic-api-request-signature
+        
+        var url = $"https://api-ch-dk-2.exoscale.com/v2/access-key/{installation.S3Key}";
+        var method = $"access-key/{installation.S3Key}";
+        
+        var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
+
+        var authheader = "credential="+S3Credentials.Key+",expires="+unixtime+",signature="+BuildSignature("DELETE", method, unixtime);
+        
+        var client = new HttpClient();
+        
+        client.DefaultRequestHeaders.Authorization =
+            new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
+        
+        var response = await client.DeleteAsync(url);
+        return response.IsSuccessStatusCode;
+    }
+    
+    public static async Task<Boolean> RevokeWriteKey(this Installation installation)
+    {
+        //Check exoscale documentation https://openapi-v2.exoscale.com/topic/topic-api-request-signature
+        
+        var url = $"https://api-ch-dk-2.exoscale.com/v2/access-key/{installation.S3WriteKey}";
+        var method = $"access-key/{installation.S3WriteKey}";
+        
+        var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
+
+        var authheader = "credential="+S3Credentials.Key+",expires="+unixtime+",signature="+BuildSignature("DELETE", method, unixtime);
+        
+        var client = new HttpClient();
+        
+        client.DefaultRequestHeaders.Authorization =
+            new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
+        
+        var response = await client.DeleteAsync(url);
+        return response.IsSuccessStatusCode;
+    }
+    
+    public static async Task<(String, String)> CreateWriteKey(this Installation installation)
+    {
+        var writeRoleId = installation.WriteRoleId;
+
+        if (String.IsNullOrEmpty(writeRoleId) 
+            || !await CheckRoleExists(writeRoleId))
+        {
+            writeRoleId = await installation.CreateWriteRole();
+            Thread.Sleep(4000);         // Exoscale is to slow for us the role might not be there yet
+        }
+        return await CreateKey(installation, writeRoleId);
+    }
+    
+    public static async Task<String> CreateWriteRole(this Installation installation)
+    {
+        const String url = "https://api-ch-dk-2.exoscale.com/v2/iam-role";
+        const String method = "iam-role";
+        String rolename = installation.Product==(int)ProductType.Salimax?Db.Installations.Count(f => f.Product == (int)ProductType.Salimax) + installation.Name:Db.Installations.Count(f => f.Product == (int)ProductType.Salidomo) + installation.Name;
+
+        var contentString = $$"""
+                              {
+                              "name" : "WRITE{{rolename}}",
+                              "policy" : {
+                                  "default-service-strategy": "deny",
+                                  "services": {
+                                      "sos": {
+                                      "type": "rules",
+                                      "rules":[{
+                                      "action" : "allow",
+                                      "expression": "resources.bucket.startsWith('{{installation.BucketName()}}')"
+                                      }]
+                                   }
+                                 }
+                              }
+                              }
+                              """;
+
+        var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
+
+        var authheader = "credential="+S3Credentials.Key+",signed-query-args="+",expires="+unixtime+",signature="+BuildSignature("POST", method, contentString, unixtime);
+        
+        var client = new HttpClient();
+        
+        client.DefaultRequestHeaders.Authorization =
+            new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
+        var content = new StringContent(contentString, Encoding.UTF8, "application/json");
+
+
+        var response = await client.PostAsync(url, content);
+        
+        var responseString = await response.Content.ReadAsStringAsync();
+        //Console.WriteLine(responseString);
+        
+        //Put Role ID into database
+        var id = JsonNode.Parse(responseString)!["reference"]!["id"]!.GetValue<String>();
+        installation.WriteRoleId = id;
+        ;
+        Db.Update(installation);
+        
+        return id;
+    }
+        
+    public static async Task<Boolean> CreateBucket(this Installation installation)
+    {
+        var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Credentials!);
+        return await s3Region.PutBucket(installation.BucketName()) != null;
+    }
+    
+    public static async Task<Boolean> DeleteBucket(this Installation installation)
+    {
+        var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Credentials!);
+        return await s3Region.DeleteBucket(installation.BucketName()) ;
+    }
+    
+    
+    public static async Task<Boolean> SendConfig(this Installation installation, Configuration config)
+    {
+        
+        // This looks hacky but here we grab the vpn-Ip of the installation by its installation Name (e.g. Salimax0001)
+        // From the vpn server (here salidomo, but we use the vpn home ip for future-proofing)
+        // using var client = new HttpClient();
+        // var webRequest = client.GetAsync("10.2.0.1/vpnstatus.txt");
+        // var text = webRequest.ToString();
+        // var lines = text!.Split(new [] { Environment.NewLine }, StringSplitOptions.None);
+        // var vpnIp = lines.First(l => l.Contains(installation.InstallationName)).Split(",")[1];
+        //
+        // // Writing the config to a file and then sending that file with rsync sounds inefficient
+        // // We should find a better solution...
+        // // TODO The VPN server should do this not the backend!!!
+        // await File.WriteAllTextAsync("./config.json", config);
+        // var result = await Cli.Wrap("rsync")
+        //     .WithArguments("./config.json")
+        //     .AppendArgument($@"root@{vpnIp}:/salimax")
+        //     .ExecuteAsync();
+        
+        // return result.ExitCode == 200;
+        
+        var maxRetransmissions = 4;
+        UdpClient udpClient = new UdpClient();
+        udpClient.Client.ReceiveTimeout = 2000;
+        int port = 9000;
+        
+        Console.WriteLine("Trying to reach installation with IP: " + installation.VpnIp);
+        //Try at most MAX_RETRANSMISSIONS times to reach an installation.
+        for (int j = 0; j < maxRetransmissions; j++)
+        {
+            //string message = "This is a message from RabbitMQ server, you can subscribe to the RabbitMQ queue";
+            byte[] data = Encoding.UTF8.GetBytes(JsonSerializer.Serialize<Configuration>(config));
+            udpClient.Send(data, data.Length, installation.VpnIp, port);
+
+            //Console.WriteLine($"Sent UDP message to {installation.VpnIp}:{port}: {config}");
+            Console.WriteLine($"Sent UDP message to {installation.VpnIp}:{port}"+" GridSetPoint is "+config.GridSetPoint +" and MinimumSoC is "+config.MinimumSoC);
+
+            IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse(installation.VpnIp), port);
+
+            try
+            {
+                byte[] replyData = udpClient.Receive(ref remoteEndPoint);
+                string replyMessage = Encoding.UTF8.GetString(replyData);
+                Console.WriteLine("Received " + replyMessage + " from installation " + installation.VpnIp);
+                return true;
+            }
+            catch (SocketException ex)
+            {
+                if (ex.SocketErrorCode == SocketError.TimedOut){Console.WriteLine("Timed out waiting for a response. Retry...");}
+                else
+                {
+                    Console.WriteLine("Error: " + ex.Message);
+                    return false;
+                }
+            }
+        }
+        
+        
+        return false;
+
+        //var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Credentials!);
+        //var url = s3Region.Bucket(installation.BucketName()).Path("config.json");
+        //return await url.PutObject(config);
+
+
+    }
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/Methods/Folder.cs b/csharp/App_backup/Backend/DataTypes/Methods/Folder.cs
new file mode 100644
index 000000000..e06fd2c91
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/Methods/Folder.cs
@@ -0,0 +1,100 @@
+using InnovEnergy.App.Backend.Database;
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.Backend.DataTypes.Methods;
+
+public static class FolderMethods
+{
+    public static IEnumerable<User> UsersWithAccess(this Folder folder)
+    {
+        var direct    = folder.UsersWithDirectAccess();
+        var inherited = folder.UsersWithInheritedAccess();
+        
+        return direct.Concat(inherited);
+    }
+    
+    public static IEnumerable<User> UsersWithDirectAccess(this Folder folder)
+    {
+        return Db
+              .FolderAccess
+              .Where(a => a.FolderId == folder.Id)
+              .Select(a => Db.GetUserById(a.UserId))
+              .NotNull();
+    }
+
+    public static IEnumerable<User> UsersWithInheritedAccess(this Folder folder)
+    {
+        return folder
+              .Ancestors()
+              .SelectMany(f => f.UsersWithDirectAccess())
+              .NotNull();
+    }
+
+    private static IEnumerable<Folder> ChildFolders(this Folder parent)
+    {
+        // Unsafe can give back loops
+        return Db
+              .Folders
+              .Where(f => f.ParentId == parent.Id);
+    }
+    
+    public static IEnumerable<Folder> UniqueChildFolders(this Folder parent)
+    {
+        
+        //var set = new HashSet<Folder>(Db.Folders, EqualityComparer<Folder>.Default);
+
+        return ChildFolders(parent);
+    }
+    
+    public static IEnumerable<Installation> ChildInstallations(this Folder parent)
+    {
+        return Db
+            .Installations
+            .Where(f => f.ParentId == parent.Id);
+    }
+    
+    
+    public static IEnumerable<Folder> DescendantFolders(this Folder parent)
+    {
+        
+        Console.WriteLine("Parent is "+parent.Id+" looking for descendant folders");
+        return parent
+              .TraverseDepthFirstPreOrder(UniqueChildFolders)
+              .Skip(1); // skip self
+    }
+
+    public static IEnumerable<Folder> DescendantFoldersAndSelf(this Folder parent)
+    {
+        return parent
+            .TraverseDepthFirstPreOrder(UniqueChildFolders);
+    }
+    public static Boolean IsDescendantOf(this Folder folder, Folder ancestor)
+    {
+        return folder
+              .Ancestors()
+              .Any(u => u.Id == ancestor.Id);
+    }
+
+    public static IEnumerable<Folder> Ancestors(this Folder folder)
+    {
+        return folder
+              .Unfold(Parent)
+              .Skip(1);  // skip self
+    }
+
+    public static Folder? Parent(this Folder folder)
+    {
+        return IsRoot(folder)    
+             ? null 
+             : Db.GetFolderById(folder.ParentId);
+    }
+
+    public static Boolean IsRoot(this Folder folder)
+    { 
+        return folder.ParentId <= 0
+            && Db.GetFolderById(folder.Id)?.Id == 0; // might have been 0 because it is a relative root 
+    }
+    
+   
+}
+
diff --git a/csharp/App_backup/Backend/DataTypes/Methods/Installation.cs b/csharp/App_backup/Backend/DataTypes/Methods/Installation.cs
new file mode 100644
index 000000000..cc5ebda69
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/Methods/Installation.cs
@@ -0,0 +1,161 @@
+using InnovEnergy.App.Backend.Database;
+using InnovEnergy.App.Backend.Relations;
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.Backend.DataTypes.Methods;
+
+
+public static class InstallationMethods
+{
+    private static readonly String BucketNameSalt = 
+        // Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == "" 
+        // ? "stage" :"3e5b3069-214a-43ee-8d85-57d72000c19d";
+        "3e5b3069-214a-43ee-8d85-57d72000c19d";
+    
+    private static readonly String SalidomoBucketNameSalt = "c0436b6a-d276-4cd8-9c44-1eae86cf5d0e";
+
+    public static String BucketName(this Installation installation)
+    {
+        if (installation.Product == (int)ProductType.Salimax)
+        {
+            return $"{installation.S3BucketId}-{BucketNameSalt}";
+        }
+
+        return $"{installation.S3BucketId}-{SalidomoBucketNameSalt}";
+        
+    }
+    
+    public static async Task<Boolean> RenewS3Credentials(this Installation installation)
+    {
+        if(!installation.S3Key.IsNullOrEmpty()) 
+            await installation.RevokeReadKey();
+        
+        
+        var (key,secret) = await installation.CreateReadKey();
+
+        installation.S3Key    = key;
+        installation.S3Secret = secret;
+        
+        if (installation.S3WriteKey == "" || installation.S3WriteSecret == "")
+        {
+            var (writeKey,writeSecret) = await installation.CreateWriteKey();
+            installation.S3WriteSecret = writeSecret;
+            installation.S3WriteKey    = writeKey;
+        }
+
+        return Db.Update(installation);
+    }
+    
+
+    public static IEnumerable<User> UsersWithAccess(this Installation installation)
+    {
+        return installation
+              .UsersWithDirectAccess()
+              .Concat(installation.UsersWithInheritedAccess());
+    }
+    
+    public static IEnumerable<User> UsersWithDirectAccess(this Installation installation)
+    {
+        return Db
+              .InstallationAccess
+              .Where(a => a.InstallationId == installation.Id)
+              .Select(a => Db.GetUserById(a.UserId))
+              .NotNull();
+    }
+
+    public static IEnumerable<User> UsersWithInheritedAccess(this Installation installation)
+    {
+        return installation
+              .Ancestors()
+              .SelectMany(f => f.UsersWithDirectAccess())
+              .NotNull();
+    }
+
+    public static IEnumerable<Folder> Ancestors(this Installation installation)
+    {
+        var parentFolder = Parent(installation);
+
+        if (parentFolder is null)
+            return Enumerable.Empty<Folder>();
+        
+        return parentFolder
+              .Ancestors()
+              .Prepend(parentFolder);
+    }
+    
+    public static Folder? Parent(this Installation installation)
+    {
+        if (installation.ParentId <= 0) // relative root
+        {
+            var i = Db.GetInstallationById(installation.Id);
+            if (i is null)
+                return null;
+
+            installation = i;
+        }
+        
+        return Db.GetFolderById(installation.ParentId);
+    }
+    
+    public static Installation HideWriteKeyIfUserIsNotAdmin(this Installation installation, int userIsAdmin)
+    {
+        if(userIsAdmin==2) 
+            return installation;
+        
+        installation.S3WriteKey    = "";
+        installation.S3WriteSecret = "";
+
+        return installation;
+    }
+    
+    public static Boolean WasMoved(this Installation installation)
+    {
+        var existingInstallation = Db.GetInstallationById(installation.Id);
+
+        return existingInstallation is not null
+            && existingInstallation.ParentId != installation.ParentId;
+    }
+
+    public static Boolean Exists(this Installation installation)
+    {
+        return Db.Installations.Any(i => i.Id == installation.Id);
+    }
+
+    public static String GetOrderNumbers(this Installation installation)
+    {
+        return Db.OrderNumber2Installation
+            .Where(i => i.InstallationId == installation.Id)
+            .Select(i => i.OrderNumber)
+            .ToReadOnlyList().JoinWith(",");
+    }
+    
+    public static Installation FillOrderNumbers(this Installation installation)
+    {
+        installation.OrderNumbers = installation.GetOrderNumbers();
+        return installation;
+    }
+    
+    public static Boolean SetOrderNumbers(this Installation installation)
+    {
+        var relations = Db.OrderNumber2Installation.Where(i => i.InstallationId == installation.Id).ToList();
+        foreach (var orderNumber in installation.OrderNumbers.Split(","))
+        {
+            var rel = relations.FirstOrDefault(i => i.OrderNumber == orderNumber);
+            if ( rel != null) {relations.Remove(rel); continue;}
+            var o2I = new OrderNumber2Installation
+            {
+                OrderNumber = orderNumber,
+                InstallationId = installation.Id
+            };
+            Db.Create(o2I);
+        }
+
+        foreach (var rel in relations)
+        {
+            Db.Delete(rel);
+        }
+
+        return true;
+    }
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/Methods/Session.cs b/csharp/App_backup/Backend/DataTypes/Methods/Session.cs
new file mode 100644
index 000000000..5880891c6
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/Methods/Session.cs
@@ -0,0 +1,430 @@
+using System.Diagnostics;
+using InnovEnergy.App.Backend.Database;
+using InnovEnergy.App.Backend.Relations;
+using InnovEnergy.App.Backend.Websockets;
+using InnovEnergy.Lib.Utils;
+using Org.BouncyCastle.Asn1.X509;
+
+namespace InnovEnergy.App.Backend.DataTypes.Methods;
+
+public static class SessionMethods
+{
+    public static Boolean Create(this Session? session, Folder? folder)
+    {
+        var user = session?.User;
+
+        return user is not null
+            && folder is not null 
+            && user.UserType!=0
+            && user.HasAccessTo(folder.Parent()) 
+            && Db.Create(folder)                // TODO: these two in a transaction
+            && Db.Create(new FolderAccess { UserId = user.Id, FolderId = folder.Id });
+    }
+
+    public static Boolean Update(this Session? session, Folder? folder)
+    {
+        var user     = session?.User;
+        var original = Db.GetFolderById(folder?.Id);
+        
+        return user is not null
+            && folder is not null 
+            && original is not null
+            && user.UserType !=0
+            && user.HasAccessTo(folder)
+            && folder
+              .WithParentOf(original)   // prevent moving
+              .Apply(Db.Update);
+    }
+    
+    public static Boolean MoveFolder(this Session? session, Int64 folderId, Int64 parentId)
+    {
+        var user     = session?.User;
+        var folder = Db.GetFolderById(folderId);
+        var parent = Db.GetFolderById(parentId);
+        
+        return user is not null
+               && folder is not null 
+               && user.UserType !=0
+               && user.HasAccessTo(folder)
+               && user.HasAccessTo(parent)
+               && folder
+                   .Do(() => folder.ParentId = parentId)
+                   .Apply(Db.Update);
+    }
+    
+    public static Boolean MoveInstallation(this Session? session, Int64 installationId, Int64 parentId)
+    {
+        var user     = session?.User;
+        var installation = Db.GetInstallationById(installationId);
+        var parent = Db.GetFolderById(parentId);
+
+        if(installation == null || installation.ParentId == parentId) return false;
+        
+        return user is not null
+               && user.UserType  !=0
+               && user.HasAccessTo(installation)
+               && user.HasAccessTo(parent)
+               && installation
+                   .Do(() => installation.ParentId = parentId)
+                   .Apply(Db.Update);
+    }
+    
+    public static async Task RunScriptInBackground(this Session? session, String vpnIp, Int64 batteryNode,String version,Int64 product)
+    {
+        Console.WriteLine("-----------------------------------Start updating firmware-----------------------------------");
+        string scriptPath = (product == (int)ProductType.Salimax) 
+            ? "/home/ubuntu/backend/uploadBatteryFw/update_firmware_Salimax.sh" 
+            : "/home/ubuntu/backend/uploadBatteryFw/update_firmware_Salidomo.sh";
+
+        await Task.Run(() =>
+        {
+            var process = new Process();
+            process.StartInfo.FileName = "/bin/bash";
+            process.StartInfo.Arguments = $"{scriptPath} {vpnIp} {batteryNode} {version}"; 
+            process.StartInfo.UseShellExecute = false;
+            process.StartInfo.RedirectStandardOutput = true; 
+        
+            process.Start();
+            var output = process.StandardOutput.ReadToEnd();
+            process.WaitForExit(); 
+            Console.WriteLine(output);
+        });
+        Console.WriteLine("-----------------------------------Stop updating firmware-----------------------------------");
+    }
+    
+    public static async Task RunDownloadLogScript(this Session? session, String vpnIp, Int64 batteryNode,Int64 product)
+    {
+        Console.WriteLine("-----------------------------------Start downloading battery log-----------------------------------");
+        string scriptPath = (product == (int)ProductType.Salimax) 
+            ? "/home/ubuntu/backend/downloadBatteryLog/download_bms_log_Salimax.sh" 
+            : "/home/ubuntu/backend/downloadBatteryLog/download_bms_log_Salidomo.sh";
+    
+        await Task.Run(() =>
+        {
+            var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    FileName = "/bin/bash",
+                    Arguments = $"{scriptPath} {vpnIp} {batteryNode}",
+                    UseShellExecute = false,
+                    RedirectStandardOutput = true
+                }
+            };
+    
+            process.Start();
+            var output = process.StandardOutput.ReadToEnd();
+            process.WaitForExit();
+            Console.WriteLine(output);
+        });
+        Console.WriteLine("-----------------------------------Stop downloading battery log-----------------------------------");
+    }
+
+    public static async Task<Boolean> SendInstallationConfig(this Session? session, Int64 installationId, Configuration configuration)
+    {
+        var user = session?.User;
+        var installation = Db.GetInstallationById(installationId);
+
+        return user is not null
+               && installation is not null
+               && user.UserType !=0
+               && user.HasAccessTo(installation)
+               && await installation.SendConfig(configuration);
+    }
+    
+    public static async Task<Boolean> InsertUserAction(this Session? session, UserAction action)
+    {
+        var user = session?.User;
+
+        if (user is null || user.UserType == 0)
+            return false;
+
+        action.UserName = user.Name;
+        
+        var installation = Db.GetInstallationById(action.InstallationId);
+        installation.TestingMode = action.TestingMode;
+        installation.Apply(Db.Update);
+        WebsocketManager.InformWebsocketsForInstallation(action.InstallationId);
+        
+        // Save the configuration change to the database
+        Db.HandleAction(action);
+        return true;
+    }
+    
+    public static async Task<Boolean> UpdateUserAction(this Session? session, UserAction action)
+    {
+        var user = session?.User;
+
+        if (user is null || user.UserType == 0)
+            return false;
+        
+        var installation = Db.GetInstallationById(action.InstallationId);
+        if (installation.TestingMode != action.TestingMode)
+        {
+            installation.TestingMode = action.TestingMode;
+            installation.Apply(Db.Update);
+            WebsocketManager.InformWebsocketsForInstallation(action.InstallationId);
+        }
+
+        Db.UpdateAction(action);
+        return true;
+    }
+
+    
+    public static async Task<Boolean> DeleteUserAction(this Session? session, Int64 actionId)
+    {
+        var user = session?.User;
+
+        if (user is null || user.UserType == 0)
+            return false;
+        var action = Db.GetActionById(actionId);
+        if (action.TestingMode)
+        {
+            var installation = Db.GetInstallationById(action.InstallationId);
+            installation.TestingMode = false;
+            installation.Apply(Db.Update);
+            WebsocketManager.InformWebsocketsForInstallation(action.InstallationId);
+        }
+        
+        Db.Delete(action);
+        Console.WriteLine("---------------Deleted the Action in the database-----------------");
+        return true;
+    }
+
+    public static Boolean Delete(this Session? session, Folder? folder)
+    {
+        var user = session?.User;
+
+        return user is not null
+            && folder is not null
+            && user.UserType !=0
+            && user.HasAccessTo(folder)
+            && Db.Delete(folder);
+    }
+    
+    
+    public static async Task<Boolean> Create(this Session? session, Installation? installation)
+    {
+        var user = session?.User;
+        
+        
+        
+        //Salimax installation
+        if (installation.Product == (int)ProductType.Salimax)
+        {
+            return user is not null
+                   && user.UserType != 0
+                   && user.HasAccessToParentOf(installation)
+                   && Db.Create(installation) // TODO: these two in a transaction
+                   && installation.SetOrderNumbers()
+                   && Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id })
+                   && await installation.CreateBucket()
+                   && await installation.RenewS3Credentials();
+            
+        }
+        
+        if (installation.Product == (int)ProductType.Salidomo)
+        {
+            return user is not null
+                   && user.UserType != 0
+                   && user.HasAccessToParentOf(installation)
+                   && Db.Create(installation)
+                   && await installation.CreateBucket()
+                   && await installation.RenewS3Credentials();
+        }
+        
+        if (installation.Product == (int)ProductType.SodioHome)
+        {
+            return user is not null
+                   && user.UserType != 0
+                   && user.HasAccessToParentOf(installation)
+                   && Db.Create(installation);
+        } 
+        
+        
+        
+        return false;
+    }
+    
+    
+    public static Boolean Update(this Session? session, Installation? installation)
+    {
+        var user = session?.User;
+
+        var original = Db.GetInstallationById(installation?.Id);
+        //Salimax installation
+        if (installation.Product == (int)ProductType.Salimax)
+        {
+        
+            return user is not null
+                   && installation is not null
+                   && original is not null
+                   && user.UserType !=0
+                   && user.HasAccessTo(installation)
+                   && installation.SetOrderNumbers()
+                   && installation
+                       .WithParentOf(original)  // prevent moving
+                       .Apply(Db.Update); 
+        }
+
+        
+        return user is not null
+               && installation is not null
+               && original is not null
+               && user.UserType !=0
+               && user.HasAccessToParentOf(installation)
+               && installation
+                   .Apply(Db.Update); 
+        
+
+    }
+
+    public static async Task<Boolean> Delete(this Session? session, Installation? installation)
+    {
+        var user = session?.User;
+
+        if (user is not null
+            && installation is not null
+            && user.UserType != 0)
+        {
+            if (installation.Product is (int)ProductType.Salimax or (int)ProductType.Salidomo)
+            {
+                return 
+                    Db.Delete(installation)
+                    && await installation.RevokeReadKey()
+                    && await installation.RevokeWriteKey()
+                    && await installation.RemoveReadRole()
+                    && await installation.RemoveWriteRole()
+                    && await installation.DeleteBucket();
+            }
+            else
+            {
+                return Db.Delete(installation);
+
+            }
+        }
+
+        return false;
+
+    }
+    
+    public static Boolean Create(this Session? session, User newUser)
+    {
+        var sessionUser = session?.User;
+        var userAlreadyExists = Db.GetUserByEmail(newUser.Email);
+        
+        return sessionUser is not null
+               && userAlreadyExists is null
+               && sessionUser.UserType !=0
+               && newUser
+                   .WithParent(sessionUser)
+                   .Do(() => newUser.MustResetPassword = true)
+                   .Do(() => newUser.Password = null)
+                   .Apply(Db.Create);
+        // && Mailer.Mailer.SendVerificationMessage(newUser);
+
+        // .Do(() => newUser.Password = newUser.SaltAndHashPassword(newUser.Password))
+
+        //Send Email to new user to verify email and set password
+
+    }
+
+    public static Boolean Update(this Session? session, User? editedUser)
+    {
+        var sessionUser = session?.User;
+        var originalUser = Db.GetUserById(editedUser?.Id);
+        
+        return editedUser is not null 
+            && sessionUser is not null
+            && originalUser is not null
+            && sessionUser.UserType  !=0
+            && sessionUser.HasAccessTo(originalUser)
+            && editedUser
+              .WithParentOf(originalUser)     // prevent moving
+              .WithNameOf(originalUser)     
+              .WithPasswordOf(originalUser)   
+              .Apply(Db.Update);
+    }
+    
+    public static Boolean UpdatePassword(this Session? session, String? newPassword)
+    {
+        var sessionUser = session?.User;
+        
+        return sessionUser is not null
+               && sessionUser
+                   .Do(() => sessionUser.Password = sessionUser.SaltAndHashPassword(newPassword))
+                   .Do(() => sessionUser.MustResetPassword = false)
+                   .Apply(Db.Update);
+    }
+   
+    public static Boolean Delete(this Session? session, User? userToDelete)
+    {
+        var sessionUser = session?.User;
+        
+        return sessionUser is not null
+            && userToDelete is not null
+            && sessionUser.UserType !=0
+            && sessionUser.HasAccessTo(userToDelete)
+            && Db.Delete(userToDelete);
+    }
+
+    
+    public static Boolean GrantUserAccessTo(this Session? session, User? user, Installation? installation)
+    {
+        var sessionUser = session?.User;
+
+        return sessionUser is not null
+            && installation is not null
+            && user is not null
+            && user.IsDescendantOf(sessionUser)
+            && sessionUser.HasAccessTo(installation)
+            && !user.HasAccessTo(installation)
+            && Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id });
+    }
+
+    public static Boolean GrantUserAccessTo(this Session? session, User? user, Folder? folder)
+    {
+        var sessionUser = session?.User;
+
+        return sessionUser is not null
+            && folder is not null
+            && user is not null
+            && user.IsDescendantOf(sessionUser)
+            && sessionUser.HasAccessTo(folder)
+            && !user.HasAccessTo(folder)
+            && Db.Create(new FolderAccess { UserId = user.Id, FolderId = folder.Id });
+    }
+    
+    public static Boolean RevokeUserAccessTo(this Session? session, User? user, Installation? installation)
+    {
+        var sessionUser = session?.User;
+        
+        return sessionUser is not null
+            && installation is not null
+            && user is not null
+            && user.IsDescendantOf(sessionUser)
+            && sessionUser.HasAccessTo(installation)
+            && user.HasAccessTo(installation)
+            && Db.InstallationAccess.Delete(a => a.UserId == user.Id && a.InstallationId == installation.Id) > 0;
+    }
+    
+    public static Boolean RevokeUserAccessTo(this Session? session, User? user, Folder? folder)
+    {
+        var sessionUser = session?.User;
+        
+        return sessionUser is not null
+            && folder is not null
+            && user is not null
+            && user.IsDescendantOf(sessionUser)
+            && sessionUser.HasAccessTo(folder)
+            && user.HasAccessTo(folder)
+            && Db.FolderAccess.Delete(a => a.UserId == user.Id && a.FolderId == folder.Id) > 0;
+    }
+    
+    public static Boolean Logout(this Session? session)
+    {
+        return session is not null
+            && Db.Sessions.Delete(s => s.Token == session.Token) > 0;
+    }
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/Methods/TreeNode.cs b/csharp/App_backup/Backend/DataTypes/Methods/TreeNode.cs
new file mode 100644
index 000000000..ab3ea5091
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/Methods/TreeNode.cs
@@ -0,0 +1,48 @@
+using InnovEnergy.App.Backend.Database;
+
+namespace InnovEnergy.App.Backend.DataTypes.Methods;
+
+public static class TreeNodeMethods
+{
+    
+    public static T WithParentOf<T>(this T treeNode, T other) where T: TreeNode
+    {
+        treeNode.ParentId = other.ParentId;
+        return treeNode;
+    }
+    
+    public static T WithNameOf<T>(this T treeNode, T other) where T: TreeNode
+    {
+        treeNode.Name = other.Name;
+        return treeNode;
+    }
+    
+    public static T WithParent<T>(this T treeNode, T other) where T: TreeNode
+    {
+        treeNode.ParentId = other.Id;
+        return treeNode;
+    }
+    
+    public static T HideParentIfUserHasNoAccessToParent<T>(this T node, User? accessingUser) 
+    {
+        if (accessingUser is not null && node is TreeNode treeNode && !accessingUser.HasAccessToParentOf(treeNode))
+        {
+            treeNode.ParentId = 0;
+        }
+        
+        return node;
+    }
+
+    public static TreeNode FillOrderNumbers(this TreeNode treeNode)
+    {
+        if (treeNode is Installation installation)
+        {
+            installation.FillOrderNumbers();
+        }
+
+        return treeNode;
+    }
+    
+    
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/Methods/User.cs b/csharp/App_backup/Backend/DataTypes/Methods/User.cs
new file mode 100644
index 000000000..b20930562
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/Methods/User.cs
@@ -0,0 +1,261 @@
+using System.Security.Cryptography;
+using System.Web;
+using InnovEnergy.App.Backend.Database;
+using InnovEnergy.Lib.Mailer;
+using InnovEnergy.Lib.Utils;
+using Convert = System.Convert;
+using static System.Text.Encoding;
+
+
+namespace InnovEnergy.App.Backend.DataTypes.Methods;
+
+
+public static class UserMethods
+{
+    public static IEnumerable<Installation> AccessibleInstallations(this User user,int product)
+    {
+        var direct      = user.DirectlyAccessibleInstallations().ToList().Where(f=>f.Product==product);
+        var fromFolders = user
+                         .AccessibleFolders()
+                         .SelectMany(u => u.ChildInstallations()).ToList().Where(f=>f.Product==product);
+        
+        return direct
+              .Concat(fromFolders)
+              .Distinct();
+    }
+    
+    public static IEnumerable<Installation> AccessibleInstallations(this User user)
+    {
+        var direct      = user.DirectlyAccessibleInstallations().ToList();
+        var fromFolders = user
+            .AccessibleFolders()
+            .SelectMany(u => u.ChildInstallations()).ToList();
+        
+        return direct
+            .Concat(fromFolders)
+            .Distinct();
+    }
+    
+
+    public static IEnumerable<Folder> AccessibleFolders(this User user)
+    {
+        return user
+              .DirectlyAccessibleFolders()
+              .SelectMany(f => f.DescendantFolders().Prepend(f))
+              .Distinct();
+    }
+
+    public static IEnumerable<TreeNode> AccessibleFoldersAndInstallations(this User user,int product)
+    {
+        var folders       = user.AccessibleFolders() as IEnumerable<TreeNode>;
+        
+        user.AccessibleInstallations(product).ForEach(i => i.FillOrderNumbers());
+        var installations = user.AccessibleInstallations(product);
+
+        return folders.Concat(installations);
+    }
+    
+    public static IEnumerable<TreeNode> AccessibleFoldersAndInstallations(this User user)
+    {
+        var folders       = user.AccessibleFolders() as IEnumerable<TreeNode>;
+        
+        user.AccessibleInstallations().ForEach(i => i.FillOrderNumbers());
+        var installations = user.AccessibleInstallations();
+
+        return folders.Concat(installations);
+    }
+
+    public static IEnumerable<Installation> DirectlyAccessibleInstallations(this User user)
+    {
+        return Db
+              .InstallationAccess
+              .Where(r => r.UserId == user.Id)
+              .Select(r => r.InstallationId)
+              .Select(i => Db.GetInstallationById(i))
+              .NotNull()
+              .Do(i => i.HideParentIfUserHasNoAccessToParent(user)); // hide inaccessible parents from calling user
+    }
+
+    public static IEnumerable<Folder> DirectlyAccessibleFolders(this User user)
+    {
+        
+        return Db
+              .FolderAccess
+              .Where(r => r.UserId == user.Id)
+              .Select(r => r.FolderId)
+              .Select(i => Db.GetFolderById(i))
+              .NotNull()
+              .Do(f => f.HideParentIfUserHasNoAccessToParent(user)); // hide inaccessible parents from calling user;
+    }
+
+    public static IEnumerable<User> ChildUsers(this User parent)
+    {
+        return Db
+              .Users
+              .Where(f => f.ParentId == parent.Id);
+    }
+
+    public static IEnumerable<User> DescendantUsers(this User parent)
+    {
+        return parent
+              .TraverseDepthFirstPreOrder(ChildUsers)
+              .Skip(1); // skip self
+    }
+
+    public static Boolean IsDescendantOf(this User user, User ancestor)
+    {
+        return user
+              .Ancestors()
+              .Any(u => u.Id == ancestor.Id);
+    }
+
+    private static IEnumerable<User> Ancestors(this User user)
+    {
+        return user
+              .Unfold(Parent)
+              .Skip(1);  // skip self
+    }
+
+    public static Boolean VerifyPassword(this User user, String? password)
+    {
+        return password is not null 
+            && Db.GetUserByEmail(user.Email)?.Password == user.SaltAndHashPassword(password);
+    }
+    
+    
+    public static String SaltAndHashPassword(this User user, String password)
+    {
+        var dataToHash = $"{password}{user.Salt()}";
+
+        return dataToHash
+              .Apply(UTF8.GetBytes)
+              .Apply(SHA256.HashData)
+              .Apply(Convert.ToBase64String);
+    }
+    
+    public static User? Parent(this User u)
+    {
+        return u.IsRoot()
+             ? null
+             : Db.GetUserById(u.ParentId);
+    }
+
+    public static Boolean IsRoot(this User user)
+    { 
+        return user.ParentId <= 0
+            && Db.GetUserById(user.Id)?.Id == 0; // might have been 0 because it is a relative root 
+    }
+    
+    public static Boolean HasDirectAccessTo(this User user, Folder folder)
+    {
+        return Db
+              .FolderAccess
+              .Any(r => r.FolderId == folder.Id && r.UserId == user.Id);
+    }
+
+    public static Boolean HasAccessTo(this User user, Folder? folder)
+    {
+        if (folder is null)
+            return false;
+        
+        return user.HasDirectAccessTo(folder)
+            || folder
+              .Ancestors()
+              .Any(user.HasDirectAccessTo);
+    }
+
+    public static Boolean HasDirectAccessTo(this User user, Installation installation)
+    {
+        return Db
+              .InstallationAccess
+              .Any(r => r.UserId == user.Id && r.InstallationId == installation.Id);
+    }
+
+    public static Boolean HasAccessTo(this User user, Installation? installation)
+    {
+        if (installation is null)
+           return false;
+
+        return user.HasDirectAccessTo(installation) 
+            || installation
+              .Ancestors()
+              .Any(user.HasDirectAccessTo);
+    }
+    
+    
+    public static Boolean HasAccessTo(this User user, User? other)
+    {
+        if (other is null)
+            return false;
+
+        return other.Id == user.Id 
+            || other
+              .Ancestors()
+              .Contains(user);
+    }
+    
+    
+    public static Boolean HasAccessToParentOf(this User user, TreeNode? other)
+    {
+        
+        return other?.Type switch
+        {
+            "Installation" => user.HasAccessTo(Db.GetFolderById(other.ParentId)),
+            "User"         => user.HasAccessTo(Db.GetUserById(other.ParentId)),
+            "Folder"       => user.HasAccessTo(Db.GetFolderById(other.ParentId)),
+            _              => false
+        };
+    }
+
+    private static String Salt(this User user)
+    {
+        // + id          => salt unique per user
+        // + InnovEnergy => globally unique 
+        
+        return $"{user.Id}InnovEnergy";   
+    }
+    
+    public static User WithPasswordOf(this User user, User other)
+    {
+        user.Password = other.Password;
+        return user;
+    }
+
+    public static User HidePassword(this User user) 
+    {
+        user.Password = "";
+        return user;
+    }
+    
+    public static Task SendEmail(this User user, String subject, String body)
+    {
+        return Mailer.Send(user.Name, user.Email, subject, body);
+    }
+    
+    public static Task SendPasswordResetEmail(this User user, String token)
+    {
+        const String subject   = "Reset the password of your Inesco Energy Account";
+        const String resetLink = "https://monitor.innov.energy/api/ResetPassword";  // TODO: move to settings file
+        var encodedToken = HttpUtility.UrlEncode(token);
+        
+        var body = $"Dear {user.Name}\n" +
+                   $"To reset your password " +
+                   $"please open this link:{resetLink}?token={encodedToken}";
+
+        return user.SendEmail(subject, body);
+    }
+
+    public static Task SendNewUserWelcomeMessage(this User user)
+    {
+        const String subject = "Your new Inesco Energy Account";
+
+        var resetLink = $"https://monitor.innov.energy/?username={user.Email}";  // TODO: move to settings file
+    
+        var body = $"Dear {user.Name}\n" +
+                   $"To set your password and log in to your " +
+                   $"Inesco Energy Account open this link:{resetLink}";
+
+        return user.SendEmail(subject, body);
+    }
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/TreeNode.Equality.cs b/csharp/App_backup/Backend/DataTypes/TreeNode.Equality.cs
new file mode 100644
index 000000000..e9c6767df
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/TreeNode.Equality.cs
@@ -0,0 +1,23 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace InnovEnergy.App.Backend.DataTypes;
+
+public abstract partial class TreeNode
+{
+    public override Boolean Equals(Object? obj)
+    {
+        if (ReferenceEquals(null, obj)) return false;
+        if (ReferenceEquals(this, obj)) return true;
+        if (obj.GetType() != GetType()) return false;
+        
+        return Equals((TreeNode)obj);
+    }
+
+    protected Boolean Equals(TreeNode other) => Id == other.Id;
+    
+    [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
+    public override Int32 GetHashCode() => Id.GetHashCode();
+
+    public static Boolean operator ==(TreeNode? left, TreeNode? right) => Equals(left, right);
+    public static Boolean operator !=(TreeNode? left, TreeNode? right) => !Equals(left, right);
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/TreeNode.EqualityComparer.cs b/csharp/App_backup/Backend/DataTypes/TreeNode.EqualityComparer.cs
new file mode 100644
index 000000000..c5bf974c8
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/TreeNode.EqualityComparer.cs
@@ -0,0 +1,25 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace InnovEnergy.App.Backend.DataTypes;
+
+public abstract partial class TreeNode
+{
+    private sealed class IdEqualityComparer : IEqualityComparer<TreeNode>
+    {
+        public Boolean Equals(TreeNode x, TreeNode y)
+        {
+            if (ReferenceEquals(x, y)) return true;
+            if (ReferenceEquals(x, null)) return false;
+            if (ReferenceEquals(y, null)) return false;
+            if (x.GetType() != y.GetType()) return false;
+            return x.Id == y.Id;
+        }
+
+        public Int32 GetHashCode(TreeNode obj)
+        {
+            return obj.Id.GetHashCode();
+        }
+    }
+
+    public static IEqualityComparer<TreeNode> IdComparer { get; } = new IdEqualityComparer();
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/TreeNode.cs b/csharp/App_backup/Backend/DataTypes/TreeNode.cs
new file mode 100644
index 000000000..a0dd788df
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/TreeNode.cs
@@ -0,0 +1,19 @@
+using SQLite;
+namespace InnovEnergy.App.Backend.DataTypes;
+
+public abstract partial class TreeNode
+{
+    //This is the parent class of each relation. It has an autoincrement Id, name, information, parent Id and Type.
+    //Ignore means: "Do not map this property to a database column."
+    [PrimaryKey, AutoIncrement]
+    public  Int64  Id                       { get; set; }
+    public virtual String Name              { get; set; } = "";  // overridden by User (unique)
+    public         String Information       { get; set; } = "";  // unstructured random info
+
+    [Indexed] 
+    public Int64  ParentId                  { get; set; }    
+
+    [Ignore]
+    public String Type => GetType().Name;     
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/User.cs b/csharp/App_backup/Backend/DataTypes/User.cs
new file mode 100644
index 000000000..4738c7f45
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/User.cs
@@ -0,0 +1,17 @@
+using SQLite;
+
+namespace InnovEnergy.App.Backend.DataTypes;
+
+public class User : TreeNode
+{
+    
+    [Unique]
+    public String  Email               { get; set; } = null!;
+    public int UserType                { get; set; } = 0;
+    public Boolean MustResetPassword   { get; set; } = false;
+    public String?  Password           { get; set; } = null!;
+
+    [Unique]
+    public override String Name { get; set; } = null!;
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/UserAction.cs b/csharp/App_backup/Backend/DataTypes/UserAction.cs
new file mode 100644
index 000000000..c546ced32
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/UserAction.cs
@@ -0,0 +1,20 @@
+using SQLite;
+
+namespace InnovEnergy.App.Backend.DataTypes;
+
+public class UserAction
+{
+    [PrimaryKey, AutoIncrement]
+    public Int64 Id { get; set; } // Primary key for the table, auto-incremented
+
+    [Indexed]
+    public String UserName { get; set; } = null!;// User Name who made the configuration change
+    
+    public Int64 InstallationId { get; set; } // Installation ID where the configuration change is made
+    
+    public Boolean TestingMode { get; set; }
+
+    public DateTime Timestamp { get; set; } // Timestamp of the configuration change
+
+    public String Description { get; set; } = null!;// Serialized string representing the new configuration
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DataTypes/WebsocketMessage.cs b/csharp/App_backup/Backend/DataTypes/WebsocketMessage.cs
new file mode 100644
index 000000000..fa8c93694
--- /dev/null
+++ b/csharp/App_backup/Backend/DataTypes/WebsocketMessage.cs
@@ -0,0 +1,10 @@
+namespace InnovEnergy.App.Backend.DataTypes;
+
+public class WebsocketMessage
+{
+    
+    public int id              { get; set; }
+    public int status          { get; set; }
+    public Boolean testingMode { get; set; }
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Database/Create.cs b/csharp/App_backup/Backend/Database/Create.cs
new file mode 100644
index 000000000..9263b0793
--- /dev/null
+++ b/csharp/App_backup/Backend/Database/Create.cs
@@ -0,0 +1,152 @@
+using InnovEnergy.App.Backend.DataTypes;
+using InnovEnergy.App.Backend.Relations;
+
+
+namespace InnovEnergy.App.Backend.Database;
+
+
+public static partial class Db
+{
+    
+    private static Boolean Insert(Object obj)
+    {
+        var success = Connection.Insert(obj) > 0;
+        if (success)  Backup();
+        return success;
+    }
+    
+    public static Boolean Create(Installation installation)
+    {
+        // The bucket Id it calculated as follows: It is 1 + the maximum bucket id of all the existing installations of the same product
+        // SQLite wrapper is smart and *modifies* t's Id to the one generated (autoincrement) by the insertion
+        installation.S3BucketId = Installations.Where(inst => inst.Product == installation.Product).Max(inst => (int?)inst.S3BucketId)+1 ?? 0;
+        return Insert(installation);
+    }
+    
+    public static Boolean Create(Error error)
+    {
+        return Insert(error);
+    }
+    
+    public static Boolean Create(Warning warning)
+    {
+        return Insert(warning);
+    }
+    
+    public static Boolean Create(Folder folder)
+    {
+        return Insert(folder);
+    }
+    
+    public static Boolean Create(User user)
+    {
+        return Insert(user);
+    }
+   
+    public static Boolean Create(Session session)
+    {
+        return Insert(session);
+    }
+    
+    public static Boolean Create(InstallationAccess installationAccess)
+    {
+        return Insert(installationAccess);
+    }
+    
+    public static Boolean Create(FolderAccess folderAccess)
+    {
+        return Insert(folderAccess);
+    }
+
+    public static Boolean Create(OrderNumber2Installation o2i)
+    {
+        return Insert(o2i);
+    }
+    
+    public static Boolean Create(UserAction action)
+    {
+         return Insert(action);
+    }
+    
+    public static void HandleAction(UserAction newAction)
+    {
+        //Find the total number of actions for this installation
+        var totalActions = UserActions.Count(action => action.InstallationId == newAction.InstallationId);
+
+        //If there are 100 actions, remove the one with the oldest timestamp
+        if (totalActions == 100)
+        {
+            var oldestAction = 
+                UserActions.Where(action => action.InstallationId == newAction.InstallationId)
+                    .OrderBy(action => action.Timestamp)
+                    .FirstOrDefault();
+                            
+            //Remove the old action
+            Delete(oldestAction);
+
+            //Add the new action
+            Create(newAction);
+        }
+        else
+        {
+            Console.WriteLine("---------------Added the new Action to the database-----------------");
+            Create(newAction);
+        }
+    }
+    
+    //This function is called from the RabbitMQ manager when a new error arrives to the database.
+    //We keep only the last 100 errors for each installation. If we already have stored 100 errors, we delete the older one and we insert the new one.
+    
+    public static void HandleError(Error newError,int installationId)
+    {
+        //Find the total number of errors for this installation
+        var totalErrors = Errors.Count(error => error.InstallationId == installationId);
+
+        //If there are 100 errors, remove the one with the oldest timestamp
+        if (totalErrors == 100)
+        {
+            var oldestError = 
+                Errors.Where(error => error.InstallationId == installationId)
+                    .OrderBy(error => error.Date)
+                    .FirstOrDefault();
+                            
+            //Remove the old error
+            Delete(oldestError);
+
+            //Add the new error
+            Create(newError);
+        }
+        else
+        {
+            Console.WriteLine("---------------Added the new Alarm to the database-----------------");
+            Create(newError);
+        }
+    }
+    
+    public static void HandleWarning(Warning newWarning,int installationId)
+    {
+        //Find the total number of warnings for this installation
+        var totalWarnings = Warnings.Count(warning => warning.InstallationId == installationId);
+
+        //If there are 100 warnings, remove the one with the oldest timestamp
+        if (totalWarnings == 100)
+        {
+            var oldestWarning = 
+                Warnings.Where(warning => warning.InstallationId == installationId)
+                    .OrderBy(warning => warning.Date)
+                    .FirstOrDefault();
+                            
+            //Remove the old warning
+            Delete(oldestWarning);
+
+            //Add the new warning
+            Create(newWarning);
+        }
+        else
+        {
+            Console.WriteLine("---------------Added the new Warning to the database-----------------");
+            Create(newWarning);
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Database/Db.cs b/csharp/App_backup/Backend/Database/Db.cs
new file mode 100644
index 000000000..39d11b9c5
--- /dev/null
+++ b/csharp/App_backup/Backend/Database/Db.cs
@@ -0,0 +1,310 @@
+using InnovEnergy.App.Backend.DataTypes;
+using InnovEnergy.App.Backend.DataTypes.Methods;
+using InnovEnergy.App.Backend.Relations;
+using InnovEnergy.Lib.S3Utils;
+using InnovEnergy.Lib.S3Utils.DataTypes;
+using InnovEnergy.Lib.Utils;
+using SQLite;
+using SQLiteConnection = SQLite.SQLiteConnection;
+
+namespace InnovEnergy.App.Backend.Database;
+
+//The methods of the Db class are located in multiple files (Create.cs, Read,cs, Delete.cs, Update.cs)
+//That's why the class definition is partial
+
+public static partial class Db
+{
+    private static SQLiteConnection Connection { get; } = InitConnection();
+    public static TableQuery<Session> Sessions => Connection.Table<Session>();
+    public static TableQuery<Folder> Folders => Connection.Table<Folder>();
+    public static TableQuery<Installation> Installations => Connection.Table<Installation>();
+    public static TableQuery<User> Users => Connection.Table<User>();
+    public static TableQuery<FolderAccess> FolderAccess => Connection.Table<FolderAccess>();
+    public static TableQuery<InstallationAccess> InstallationAccess => Connection.Table<InstallationAccess>();
+    public static TableQuery<OrderNumber2Installation> OrderNumber2Installation => Connection.Table<OrderNumber2Installation>();
+    public static TableQuery<Error> Errors => Connection.Table<Error>();
+    public static TableQuery<Warning> Warnings => Connection.Table<Warning>();
+    public static TableQuery<UserAction> UserActions => Connection.Table<UserAction>();
+    
+
+    public static void Init()
+    {
+        //Used to force static constructor
+        //Since this class is static, we call Init method from the Program.cs to initialize all the fields of the class
+        //When a class is loaded, the fields are initialized before the constructor's code is executed.
+        //The TableQuery fields are lazy meaning that they will be initialized when they get accessed
+        //The connection searches for the latest backup and binds all the tables to it.
+    }
+    
+    //This is the constructor of the class
+    static Db()
+    {
+        Connection.RunInTransaction(() =>
+        {
+            Connection.CreateTable<User>();
+            Connection.CreateTable<Installation>();
+            Connection.CreateTable<Folder>();
+            Connection.CreateTable<FolderAccess>();
+            Connection.CreateTable<InstallationAccess>();
+            Connection.CreateTable<Session>();
+            Connection.CreateTable<OrderNumber2Installation>();
+            Connection.CreateTable<Error>();
+            Connection.CreateTable<Warning>();
+            Connection.CreateTable<UserAction>();
+        });
+        
+        //UpdateKeys();
+        CleanupSessions().SupressAwaitWarning();
+        DeleteSnapshots().SupressAwaitWarning();
+    }
+
+    
+    private static SQLiteConnection InitConnection()
+    {
+        var latestDb = new DirectoryInfo("DbBackups")
+            .GetFiles()
+            .OrderBy(f => f.LastWriteTime)
+            .Last().Name;
+        
+        Console.WriteLine("latestdb is "+latestDb);
+
+        //This is the file connection from the DbBackups folder
+        var fileConnection = new SQLiteConnection("DbBackups/" + latestDb);
+
+        //Create a table if it does not exist
+        fileConnection.CreateTable<User>();
+        fileConnection.CreateTable<Installation>();
+        fileConnection.CreateTable<Folder>();
+        fileConnection.CreateTable<FolderAccess>();
+        fileConnection.CreateTable<InstallationAccess>();
+        fileConnection.CreateTable<Session>();
+        fileConnection.CreateTable<OrderNumber2Installation>();
+        fileConnection.CreateTable<Error>();
+        fileConnection.CreateTable<Warning>();
+        fileConnection.CreateTable<UserAction>();
+
+        return fileConnection;
+        //return CopyDbToMemory(fileConnection);
+    }
+    
+    public static void BackupDatabase()
+    {
+        var filename = "db-" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + ".sqlite";
+        Connection.Backup("DbBackups/" + filename);
+    }
+    
+    //Delete all except 10 snapshots every 24 hours.
+    private static async Task  DeleteSnapshots()
+    {
+        while (true)
+        {
+            try
+            {
+                var files = new DirectoryInfo("DbBackups")
+                    .GetFiles()
+                    .OrderByDescending(f => f.LastWriteTime);
+                
+                var filesToDelete = files.Skip(10);
+
+                foreach (var file in filesToDelete)
+                {
+                    Console.WriteLine("File to delete is " + file.Name);
+                    file.Delete();
+                }
+
+            }
+            catch(Exception e)
+            {
+                Console.WriteLine("An error has occured when cleaning database snapshots, exception is:\n"+e);
+            }
+
+            await Task.Delay(TimeSpan.FromHours(24));
+        }
+    }
+
+    //Delete all expired sessions every half an hour. An expired session is a session remained for more than 1 day.
+    private static async Task CleanupSessions()
+    {
+        while (true)
+        {
+            try
+            {
+                var deadline = DateTime.Now.AddDays(-Session.MaxAge.Days);
+                foreach (var session in Sessions)
+                {
+                    if (session.LastSeen < deadline)
+                    {
+                        Console.WriteLine("Need to remove session of user id " + session.User.Name + "last time is "+session.LastSeen);
+                    }
+                    
+                }
+
+                Sessions.Delete(s => s.LastSeen < deadline);
+            }
+            catch(Exception e)
+            {
+                Console.WriteLine("An error has occured when cleaning stale sessions, exception is:\n"+e);
+            }
+
+            await Task.Delay(TimeSpan.FromHours(0.5));
+        }
+    }
+    
+    private static async Task RemoveNonExistingKeys()
+    {
+        while (true)
+        {
+            try
+            {
+                var validReadKeys = Installations
+                    .Select(i => i.S3Key)
+                    .Distinct()
+                    .ToList();
+                
+                
+
+                var validWriteKeys = Installations
+                    .Select(i => i.S3WriteKey)
+                    .Distinct()
+                    .ToList();
+
+                Console.WriteLine("VALID READ KEYS");
+                for (int i = 0; i < validReadKeys.Count; i++)
+                {
+                    Console.WriteLine(validReadKeys[i]);
+                }
+                
+                Console.WriteLine("VALID WRITE KEYS");
+
+                for (int i = 0; i < validReadKeys.Count; i++)
+                {
+                    Console.WriteLine(validWriteKeys[i]);
+                }
+              
+
+                const String provider = "exo.io";
+                var S3keys = await ExoCmd.GetAccessKeys();
+
+                foreach (var keyMetadata in S3keys)
+                {
+                    if (keyMetadata["key"].ToString()!="EXOa0b53cf10517307cec1bf00e" && !validReadKeys.Contains(keyMetadata["key"].ToString()) && !validWriteKeys.Contains(keyMetadata["key"].ToString()))
+                    {
+                        //await ExoCmd.RevokeReadKey(keyMetadata["key"].ToString());
+                        Console.WriteLine("Deleted key "+keyMetadata["key"]);
+                    }
+
+                }
+            }
+            catch(Exception e)
+            {
+                Console.WriteLine("An error has occured when updating S3 keys, exception is:\n"+e);
+            }
+
+            await Task.Delay(TimeSpan.FromHours(24));
+        }
+    
+    }
+
+    private static async Task UpdateKeys()
+    {
+        while (true)
+        {
+            try
+            {
+                await UpdateS3Urls();
+            }
+            catch(Exception e)
+            {
+                Console.WriteLine("An error has occured when updating S3 keys, exception is:\n"+e);
+            }
+
+            await RemoveNonExistingKeys();
+
+            await Task.Delay(TimeSpan.FromHours(24));
+        }
+    }
+
+
+
+    private static Boolean RunTransaction(Func<Boolean> func)
+    {
+        var savepoint = Connection.SaveTransactionPoint();
+        var success = false;
+
+        try
+        {
+            success = func();
+        }
+        finally
+        {
+            if (success)
+                Connection.Release(savepoint);
+            else
+                Connection.RollbackTo(savepoint);
+        }
+
+        return success;
+    }
+    
+
+    private static async Task UpdateS3Urls()
+    {
+        var regions = Installations
+            .Select(i => i.S3Region)
+            .Distinct()
+            .ToList();
+
+        const String provider = "exo.io";
+        Console.WriteLine("-----------------------UPDATED READ KEYS-------------------------------------------------------------------");
+
+        foreach (var region in regions)
+        {
+            var s3Region = new S3Region($"https://{region}.{provider}", ExoCmd.S3Credentials!);
+            var bucketList = await s3Region.ListAllBuckets();
+
+            var installations = from bucket in bucketList.Buckets
+                from installation in Installations
+                where installation.BucketName() == bucket.BucketName
+                select installation;
+
+            foreach (var installation in installations)
+            {
+                await installation.RenewS3Credentials();
+            }
+
+        }
+    }
+
+    public static async Task<Boolean> SendPasswordResetEmail(User user, String sessionToken)
+    {
+        try
+        {
+            await user.SendPasswordResetEmail(sessionToken);
+            return true;
+        }
+        catch
+        {
+            return false;
+        }
+    }
+
+    public static async Task<Boolean> SendNewUserEmail(User user)
+    {
+        try
+        {
+            await user.SendNewUserWelcomeMessage();
+            return true;
+        }
+        catch
+        {
+            return false;
+        }
+    }
+
+    public static Boolean DeleteUserPassword(User user)
+    {
+        user.Password = "";
+        user.MustResetPassword = true;
+        return Update(user);
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Database/Delete.cs b/csharp/App_backup/Backend/Database/Delete.cs
new file mode 100644
index 000000000..a4dd5a748
--- /dev/null
+++ b/csharp/App_backup/Backend/Database/Delete.cs
@@ -0,0 +1,144 @@
+using InnovEnergy.App.Backend.DataTypes;
+using InnovEnergy.App.Backend.DataTypes.Methods;
+using InnovEnergy.App.Backend.Relations;
+namespace InnovEnergy.App.Backend.Database;
+
+public static partial class Db 
+{
+    //Since we do not want to stress the memory in the VM a lot, we make a snapshot of the database every 100 transactions.
+    private static int _backupCounter = 0;
+    private static void Backup()
+    {
+        _backupCounter++;
+        if (_backupCounter > 100)
+        {
+            _backupCounter = 0;
+            BackupDatabase();
+        }
+    }
+    public static Boolean Delete(Folder folder)
+    {
+        var deleteSuccess= RunTransaction(DeleteFolderAndAllItsDependencies);
+        if (deleteSuccess)
+        {
+            Backup();
+        }
+
+        return deleteSuccess;
+
+        Boolean DeleteFolderAndAllItsDependencies()
+        {
+            return folder
+                  .DescendantFoldersAndSelf()
+                  .All(DeleteDescendantFolderAndItsDependencies);
+        }
+        
+        Boolean DeleteDescendantFolderAndItsDependencies(Folder f)
+        {
+            FolderAccess  .Delete(r => r.FolderId == f.Id);
+            Installations.Delete(r => r.ParentId == f.Id);
+            var delete = Folders.Delete(r => r.Id == f.Id);            
+            
+            return delete>0;
+        }
+    }
+    
+    public static Boolean Delete(Error errorToDelete)
+    {
+        var deleteSuccess = RunTransaction(DeleteError);
+        
+        if (deleteSuccess)
+            Backup();
+        return deleteSuccess;
+        
+        
+        Boolean DeleteError()
+        {
+            return Errors.Delete(error => error.Id == errorToDelete.Id) >0;
+        }
+    }
+    
+    public static Boolean Delete(UserAction actionToDelete)
+    {
+        var deleteSuccess = RunTransaction(DeleteAction);
+        
+        
+        if (deleteSuccess)
+            Backup();
+        return deleteSuccess;
+        
+        
+        Boolean DeleteAction()
+        {
+            return UserActions.Delete(action => action.Id == actionToDelete.Id) >0;
+            
+        }
+    }
+    
+    public static Boolean Delete(Warning warningToDelete)
+    {
+        var deleteSuccess = RunTransaction(DeleteWarning);
+        if (deleteSuccess)
+            Backup();
+        return deleteSuccess;
+        
+        
+        Boolean DeleteWarning()
+        {
+            return Warnings.Delete(warning => warning.Id == warningToDelete.Id) >0;
+        }
+    }
+    
+    public static Boolean Delete(Installation installation)
+    {
+        var deleteSuccess = RunTransaction(DeleteInstallationAndItsDependencies);
+        if (deleteSuccess)
+            Backup();
+        return deleteSuccess;
+        
+        
+        Boolean DeleteInstallationAndItsDependencies()
+        {
+            InstallationAccess.Delete(i => i.InstallationId == installation.Id);
+            if (installation.Product == (int)ProductType.Salimax)
+            {
+                //For Salimax, delete the OrderNumber2Installation entries associated with this installation id.
+                OrderNumber2Installation.Delete(i => i.InstallationId == installation.Id);
+            }
+            
+            return Installations.Delete(i => i.Id == installation.Id) > 0;
+        }
+    }
+
+    public static Boolean Delete(User user)
+    {
+        var deleteSuccess = RunTransaction(DeleteUserAndHisDependencies);
+        if (deleteSuccess)
+            Backup();
+        return deleteSuccess;
+        
+        Boolean DeleteUserAndHisDependencies()
+        {
+            FolderAccess      .Delete(u => u.UserId == user.Id);
+            InstallationAccess.Delete(u => u.UserId == user.Id);
+            return Users.Delete(u => u.Id == user.Id) > 0;
+        }
+    }
+    
+    
+    #pragma warning disable CS0618
+    
+    // private!!
+    private static Boolean Delete(Session session)
+    {
+        var delete = Sessions.Delete(s => s.Id == session.Id) > 0;
+        if (delete) 
+            Backup();
+        return delete;
+    }
+
+    public static void Delete(OrderNumber2Installation relation)
+    {
+        OrderNumber2Installation.Delete(s => s.InstallationId == relation.InstallationId && s.OrderNumber == relation.OrderNumber);
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Database/Read.cs b/csharp/App_backup/Backend/Database/Read.cs
new file mode 100644
index 000000000..f47298cb3
--- /dev/null
+++ b/csharp/App_backup/Backend/Database/Read.cs
@@ -0,0 +1,59 @@
+using InnovEnergy.App.Backend.DataTypes;
+using InnovEnergy.App.Backend.Relations;
+namespace InnovEnergy.App.Backend.Database;
+
+
+public static partial class Db 
+{
+    //In this file, we provide all the methods that can be used in order to retrieve information from the database (read)
+    public static Folder? GetFolderById(Int64? id)
+    {
+        return Folders
+              .FirstOrDefault(f => f.Id == id);
+    }
+        
+    public static Installation? GetInstallationById(Int64? id)
+    {
+        return Installations
+            .FirstOrDefault(i => i.Id == id);
+    }
+    
+    public static UserAction? GetActionById(Int64? id)
+    {
+        return UserActions
+            .FirstOrDefault(i => i.Id == id);
+    }
+    
+    public static User? GetUserById(Int64? id)
+    {
+        return Users
+            .FirstOrDefault(u => u.Id == id);
+    }
+    
+    public static User? GetUserByEmail(String email)
+    {
+        return Users
+              .FirstOrDefault(u => u.Email == email);
+    }
+
+    public static Session? GetSession(String token)
+    {
+        //This method is called in almost every controller function.
+        //After logging in, the frontend receives a session object which contains a token. For all the future REST API calls, this token is used for session authentication.
+        var session = Sessions
+                     .FirstOrDefault(s => s.Token == token);
+
+        if (session is null)
+        {
+            return null;
+        }
+
+        if (!session.Valid)
+        {
+            Delete(session);
+            return null;
+        }
+
+        return session;
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Database/Update.cs b/csharp/App_backup/Backend/Database/Update.cs
new file mode 100644
index 000000000..88e748240
--- /dev/null
+++ b/csharp/App_backup/Backend/Database/Update.cs
@@ -0,0 +1,60 @@
+using InnovEnergy.App.Backend.DataTypes;
+namespace InnovEnergy.App.Backend.Database;
+
+
+public static partial class Db 
+{
+    //We can execute the updates manually for each table, but we prefer the abstract way using Connection.Update method
+    //We pass an object as an argument and the Connection will connect this object with the corresponding table.
+    //The update is being done based on the primary id of the object.
+    
+    private static Boolean Update(Object obj)
+    {
+        var success = Connection.Update(obj) > 0;
+        if(success) Backup();
+        return success;
+    }
+    
+    public static Boolean Update(Folder folder)
+    {
+        return Update(obj: folder);
+    }
+    
+    public static Boolean Update(Error error)
+    {
+        return Update(obj: error);
+    }
+    
+    public static Boolean Update(Warning warning)
+    {
+        return Update(obj: warning);
+    }
+
+    public static Boolean Update(Installation installation)
+    {
+        return Update(obj: installation);
+    }
+    
+    public static Boolean Update(User user)
+    {
+        var originalUser = GetUserById(user.Id);
+        if (originalUser is null) return false;
+        
+        // these columns must not be modified!
+        user.ParentId = originalUser.ParentId;   
+        user.Name = originalUser.Name;
+            
+        return Update(obj: user);
+    }
+    
+    public static void UpdateAction(UserAction updatedAction)
+    {
+        var existingAction = UserActions.FirstOrDefault(action => action.Id == updatedAction.Id);
+        
+        if (existingAction != null)
+        {
+            Update(updatedAction);
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/DbBackups/db-1701700887.sqlite b/csharp/App_backup/Backend/DbBackups/db-1701700887.sqlite
new file mode 100644
index 000000000..3a52deccd
Binary files /dev/null and b/csharp/App_backup/Backend/DbBackups/db-1701700887.sqlite differ
diff --git a/csharp/App_backup/Backend/DbBackups/db-1701764858.sqlite b/csharp/App_backup/Backend/DbBackups/db-1701764858.sqlite
new file mode 100644
index 000000000..4457f1434
Binary files /dev/null and b/csharp/App_backup/Backend/DbBackups/db-1701764858.sqlite differ
diff --git a/csharp/App_backup/Backend/DbBackups/db-1701787282.sqlite b/csharp/App_backup/Backend/DbBackups/db-1701787282.sqlite
new file mode 100644
index 000000000..7926b9b77
Binary files /dev/null and b/csharp/App_backup/Backend/DbBackups/db-1701787282.sqlite differ
diff --git a/csharp/App_backup/Backend/DbBackups/db-1701787283.sqlite b/csharp/App_backup/Backend/DbBackups/db-1701787283.sqlite
new file mode 100644
index 000000000..dd1f12197
Binary files /dev/null and b/csharp/App_backup/Backend/DbBackups/db-1701787283.sqlite differ
diff --git a/csharp/App_backup/Backend/DbBackups/db-1701787284.sqlite b/csharp/App_backup/Backend/DbBackups/db-1701787284.sqlite
new file mode 100644
index 000000000..6f7f7ca65
Binary files /dev/null and b/csharp/App_backup/Backend/DbBackups/db-1701787284.sqlite differ
diff --git a/csharp/App_backup/Backend/DbBackups/db-1701797550.sqlite b/csharp/App_backup/Backend/DbBackups/db-1701797550.sqlite
new file mode 100644
index 000000000..fc934d7a8
Binary files /dev/null and b/csharp/App_backup/Backend/DbBackups/db-1701797550.sqlite differ
diff --git a/csharp/App_backup/Backend/DbBackups/db-1701859479.sqlite b/csharp/App_backup/Backend/DbBackups/db-1701859479.sqlite
new file mode 100644
index 000000000..43c8feaa5
Binary files /dev/null and b/csharp/App_backup/Backend/DbBackups/db-1701859479.sqlite differ
diff --git a/csharp/App_backup/Backend/DbBackups/db-1702291326.sqlite b/csharp/App_backup/Backend/DbBackups/db-1702291326.sqlite
new file mode 100644
index 000000000..47d8860df
Binary files /dev/null and b/csharp/App_backup/Backend/DbBackups/db-1702291326.sqlite differ
diff --git a/csharp/App_backup/Backend/DbBackups/db-1702291327.sqlite b/csharp/App_backup/Backend/DbBackups/db-1702291327.sqlite
new file mode 100644
index 000000000..7074d72e2
Binary files /dev/null and b/csharp/App_backup/Backend/DbBackups/db-1702291327.sqlite differ
diff --git a/csharp/App_backup/Backend/DbBackups/db-1702291328.sqlite b/csharp/App_backup/Backend/DbBackups/db-1702291328.sqlite
new file mode 100644
index 000000000..f27d2b051
Binary files /dev/null and b/csharp/App_backup/Backend/DbBackups/db-1702291328.sqlite differ
diff --git a/csharp/App_backup/Backend/Exceptions.cs b/csharp/App_backup/Backend/Exceptions.cs
new file mode 100644
index 000000000..963a488a8
--- /dev/null
+++ b/csharp/App_backup/Backend/Exceptions.cs
@@ -0,0 +1,16 @@
+namespace InnovEnergy.App.Backend;
+
+public class Exceptions : Exception
+{
+    public String Type { get; set; }
+    public String Detail { get; set; }
+    public String Instance { get; set; }
+    public Int32? Status { get; set; }
+    public Exceptions(Int32? status, String type, String detail, String instance)
+    {
+        Type = type;
+        Detail = detail;
+        Instance = instance;
+        Status = status;
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/MailerConfig.json b/csharp/App_backup/Backend/MailerConfig.json
new file mode 100644
index 000000000..b5de08366
--- /dev/null
+++ b/csharp/App_backup/Backend/MailerConfig.json
@@ -0,0 +1,8 @@
+{
+  "SmtpServerUrl" : "mail.agenturserver.de",
+  "SmtpUsername"  : "p518526p69",
+  "SmtpPassword"  : "i;b*xqm4iB5uhl",
+  "SmtpPort"      : 587,
+  "SenderName"    : "InnovEnergy",
+  "SenderAddress" : "noreply@innov.energy"
+}
diff --git a/csharp/App_backup/Backend/Program.cs b/csharp/App_backup/Backend/Program.cs
new file mode 100644
index 000000000..780ba91fa
--- /dev/null
+++ b/csharp/App_backup/Backend/Program.cs
@@ -0,0 +1,92 @@
+using System.Diagnostics;
+using Flurl.Http;
+using Hellang.Middleware.ProblemDetails;
+using InnovEnergy.App.Backend.Database;
+using InnovEnergy.App.Backend.Websockets;
+using Microsoft.AspNetCore.HttpOverrides;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.OpenApi.Models;
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.Backend;
+
+public static class Program
+{
+    public static async Task Main(String[] args)
+    {
+        //First, we initialize the database. This is an empty constructor of the Db class that will be called.
+        //In addition, we initialize WatchDog in order to restart the backend service in case of failure.
+        //Finally, we start all the  backend services. We call the InitializeEnvironment function of RabbitMqManager to create the queue (factory/connection)
+        //Then, we generate a consumer that binds to the queue. This is a separate async Task so it must not be awaited (it acts as a separate thread).
+        //Finally, we call the MonitorSalimaxInstallationTable and MonitorSalidomoInstallationTable from the WebsocketManager class.
+        //Those methods will build in-memory data structures to track the connected frontends and update them regarding the offline installations.
+        
+        Watchdog.NotifyReady();
+        Db.Init();
+        var builder = WebApplication.CreateBuilder(args);
+        
+        RabbitMqManager.InitializeEnvironment();
+        RabbitMqManager.StartRabbitMqConsumer().SupressAwaitWarning();
+        WebsocketManager.MonitorSalimaxInstallationTable().SupressAwaitWarning();
+        WebsocketManager.MonitorSalidomoInstallationTable().SupressAwaitWarning();
+    
+        builder.Services.AddControllers();
+        builder.Services.AddProblemDetails(setup =>
+        {
+            //This includes the stacktrace in Development Env
+            setup.IncludeExceptionDetails = (_, _) => builder.Environment.IsDevelopment() || builder.Environment.IsStaging();
+            
+            //This handles our Exceptions
+            setup.Map<Exceptions>(exception => new ProblemDetails
+            {
+                Detail = exception.Detail,
+                Status = exception.Status,
+                Type = exception.Type,
+                Instance = exception.Instance
+            });
+            
+        });
+        builder.Services.AddSwaggerGen(c =>
+        {
+            c.SwaggerDoc("v1", OpenApiInfo);
+            c.UseAllOfToExtendReferenceSchemas();
+            c.SupportNonNullableReferenceTypes();
+        });
+
+        var app = builder.Build();
+
+        app.Use(async (context, next) =>
+        {
+            context.Request.WriteLine();
+            
+            await next(context);
+        });
+
+        
+        app.UseWebSockets();
+        app.UseForwardedHeaders(new ForwardedHeadersOptions
+        {
+            ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
+        });
+        
+        if (app.Environment.IsDevelopment())
+        {
+            app.UseSwagger();
+            app.UseSwaggerUI();
+        }
+        
+        app.UseCors(p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()) ;
+        //app.UseHttpsRedirection();
+        app.MapControllers();
+        app.UseProblemDetails();
+        
+        app.Run();
+    }
+
+    private static OpenApiInfo OpenApiInfo { get; } = new OpenApiInfo
+    {
+        Title = "Innesco Backend API",
+        Version = "v1"
+    };
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Properties/launchSettings.json b/csharp/App_backup/Backend/Properties/launchSettings.json
new file mode 100644
index 000000000..760aaabab
--- /dev/null
+++ b/csharp/App_backup/Backend/Properties/launchSettings.json
@@ -0,0 +1,17 @@
+{
+  "$schema": "https://json.schemastore.org/launchsettings.json",
+  "$SdkResolverGlobalJsonPath": "",
+  "profiles": {
+    "Backend": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      
+      "launchUrl": "swagger",
+      "applicationUrl": "http://localhost:7087",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development",
+        "HOME":"~/backend"
+      }
+    }
+  }
+}
diff --git a/csharp/App_backup/Backend/Relations/FolderAccess.cs b/csharp/App_backup/Backend/Relations/FolderAccess.cs
new file mode 100644
index 000000000..545647a46
--- /dev/null
+++ b/csharp/App_backup/Backend/Relations/FolderAccess.cs
@@ -0,0 +1,9 @@
+using SQLite;
+
+namespace InnovEnergy.App.Backend.Relations;
+
+public class FolderAccess : Relation<Int64, Int64>
+{
+    [Indexed] public Int64 UserId   { get => Left ; init => Left  = value;}
+    [Indexed] public Int64 FolderId { get => Right; init => Right = value;}
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Relations/InstallationAccess.cs b/csharp/App_backup/Backend/Relations/InstallationAccess.cs
new file mode 100644
index 000000000..c233ca953
--- /dev/null
+++ b/csharp/App_backup/Backend/Relations/InstallationAccess.cs
@@ -0,0 +1,9 @@
+using SQLite;
+
+namespace InnovEnergy.App.Backend.Relations;
+
+public class InstallationAccess : Relation<Int64, Int64>
+{
+    [Indexed] public Int64 UserId         { get => Left ; init => Left  = value;}
+    [Indexed] public Int64 InstallationId { get => Right; init => Right = value;}
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Relations/OrderNumber2Installation.cs b/csharp/App_backup/Backend/Relations/OrderNumber2Installation.cs
new file mode 100644
index 000000000..a994f7fb7
--- /dev/null
+++ b/csharp/App_backup/Backend/Relations/OrderNumber2Installation.cs
@@ -0,0 +1,10 @@
+using SQLite;
+
+namespace InnovEnergy.App.Backend.Relations;
+
+public class OrderNumber2Installation : Relation<String, Int64>
+{
+    [Indexed] public String OrderNumber          { get => Left ; set => Left  = value;}
+    [Indexed] public Int64  InstallationId       { get => Right; set => Right = value;}
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Relations/Relation.cs b/csharp/App_backup/Backend/Relations/Relation.cs
new file mode 100644
index 000000000..7e37b87ab
--- /dev/null
+++ b/csharp/App_backup/Backend/Relations/Relation.cs
@@ -0,0 +1,34 @@
+using System.Diagnostics.CodeAnalysis;
+using SQLite;
+
+namespace InnovEnergy.App.Backend.Relations;
+
+public abstract class Relation<L,R>
+{
+    [PrimaryKey, AutoIncrement]
+    [Obsolete("Do not use for any business logic")]
+    public Int64 Id { get; set; }
+
+    [Ignore] protected L Left  { get; set; } = default!;
+    [Ignore] protected R Right { get; set; } = default!;
+
+    protected Boolean Equals(Relation<L, R> other)
+    {
+        return EqualityComparer<L>.Default.Equals(Left, other.Left) 
+            && EqualityComparer<R>.Default.Equals(Right, other.Right);
+    }
+
+    public override Boolean Equals(Object? obj)
+    {
+        if (ReferenceEquals(null, obj)) return false;
+        if (ReferenceEquals(this, obj)) return true;
+        
+        return obj.GetType() == GetType() && Equals((Relation<L, R>)obj);
+    }
+
+    [SuppressMessage("ReSharper", "NonReadonlyMemberInGetHashCode")]
+    public override Int32 GetHashCode() => HashCode.Combine(Left, Right);
+
+    public static Boolean operator ==(Relation<L, R>? left, Relation<L, R>? right) => Equals(left, right);
+    public static Boolean operator !=(Relation<L, R>? left, Relation<L, R>? right) => !Equals(left, right);
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Relations/Session.cs b/csharp/App_backup/Backend/Relations/Session.cs
new file mode 100644
index 000000000..e87b762e4
--- /dev/null
+++ b/csharp/App_backup/Backend/Relations/Session.cs
@@ -0,0 +1,57 @@
+using InnovEnergy.App.Backend.Database;
+using InnovEnergy.App.Backend.DataTypes;
+using InnovEnergy.App.Backend.DataTypes.Methods;
+using SQLite;
+
+namespace InnovEnergy.App.Backend.Relations;
+
+public class Session : Relation<String, Int64>
+{
+    public static TimeSpan MaxAge { get; } = TimeSpan.FromDays(1);
+
+    [Unique ] public String   Token    { get => Left ; init => Left  = value;}
+    [Indexed] public Int64    UserId   { get => Right; init => Right = value;}
+    [Indexed] public DateTime LastSeen { get; set; }
+    public Boolean AccessToSalimax     { get; set; } = false;
+    public Boolean AccessToSalidomo    { get; set; } = false;
+    public Boolean AccessToSodioHome    { get; set; } = false;
+    [Ignore]  public Boolean Valid => DateTime.Now - LastSeen <=MaxAge ;
+    
+    // Private backing field
+    private User? _User;
+
+    [Ignore] public User User
+    {
+        get => _User ??= Db.GetUserById(UserId)!;
+        set => _User =value;
+    }
+    
+
+    [Obsolete("To be used only by deserializer")]
+    public Session()
+    {}
+
+    //We need to return a session object to the frontend. Only the public fields can be included.
+    //For this reason, we use the public User User. It is a public field but ignored, so it can be included to the object returned
+    //to the frontend but it will not get inserted to the database.
+    //When we initialize it like that: User =  Db.GetUserById(user.Id)!, the set will be called and the private member will be initialized as well.
+    //What if the getSession method is called from another function of the controller?
+    //GetSession will retrieve a session object from the database, but this does not have the metadata included (the private fields and the ignored public fields)
+    //Thus, the get will be called and the private field _User will be initialized on the fly.
+    public Session(User user)
+    {
+        User =  Db.GetUserById(user.Id)!;
+        Token    = CreateToken();
+        UserId   = user.Id;
+        LastSeen = DateTime.Now;
+        AccessToSalimax = user.AccessibleInstallations(product: (int)ProductType.Salimax).ToList().Count > 0;
+        AccessToSalidomo = user.AccessibleInstallations(product: (int)ProductType.Salidomo).ToList().Count > 0;
+        AccessToSodioHome = user.AccessibleInstallations(product: (int)ProductType.SodioHome).ToList().Count > 0;
+    }
+    
+    private static String CreateToken()
+    {
+        return Guid.NewGuid().ToString("N");
+    }
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Resources/CORS b/csharp/App_backup/Backend/Resources/CORS
new file mode 100644
index 000000000..171e6ce09
--- /dev/null
+++ b/csharp/App_backup/Backend/Resources/CORS
@@ -0,0 +1,9 @@
+<?xml version="1.0" ?>
+<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
+  <CORSRule>
+    <AllowedOrigin>*</AllowedOrigin>
+    <AllowedMethod>GET</AllowedMethod>
+    <AllowedMethod>HEAD</AllowedMethod>
+    <AllowedHeader>*</AllowedHeader>
+  </CORSRule>
+</CORSConfiguration>
diff --git a/csharp/App_backup/Backend/Resources/deploy.sh b/csharp/App_backup/Backend/Resources/deploy.sh
new file mode 100644
index 000000000..0a142a00c
--- /dev/null
+++ b/csharp/App_backup/Backend/Resources/deploy.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+sudo systemctl restart backend
+sudo cp -rf ~/frontend/build/* 
+sudo npm install -g serve/var/www/html/monitor.innov.energy/html/
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Resources/exoscaleS3.json b/csharp/App_backup/Backend/Resources/exoscaleS3.json
new file mode 100644
index 000000000..97008ddc7
--- /dev/null
+++ b/csharp/App_backup/Backend/Resources/exoscaleS3.json
@@ -0,0 +1,4 @@
+{
+  "Key": "EXOa0b53cf10517307cec1bf00e",
+  "Secret": "7MbZBQbHDJ1-eRsZH47BEbRvPaTT7io8H7OGqFujdQ4"
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Resources/s3AdminKey.json b/csharp/App_backup/Backend/Resources/s3AdminKey.json
new file mode 100644
index 000000000..ea6d3db68
--- /dev/null
+++ b/csharp/App_backup/Backend/Resources/s3AdminKey.json
@@ -0,0 +1,4 @@
+{
+  "Key": "EXO1abcb772bf43ab72951ba1dc",
+  "Secret": "_ym1KsGBSp90S5dwhZn18XD-u9Y4ghHvyIxg5gv5fHw" 
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Resources/s3ReadOnlyKey.json b/csharp/App_backup/Backend/Resources/s3ReadOnlyKey.json
new file mode 100644
index 000000000..a55ff349c
--- /dev/null
+++ b/csharp/App_backup/Backend/Resources/s3ReadOnlyKey.json
@@ -0,0 +1,4 @@
+{  
+   "Key": "EXOb6d6dc1880cdd51f1ebc6692",     
+   "Secret": "kpIey4QJlQFuWG_WoTazcY7kBEjN2f_ll2cDBeg64m4" 
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Resources/s3ReadWriteKey.json b/csharp/App_backup/Backend/Resources/s3ReadWriteKey.json
new file mode 100644
index 000000000..c93b2a7c0
--- /dev/null
+++ b/csharp/App_backup/Backend/Resources/s3ReadWriteKey.json
@@ -0,0 +1,4 @@
+{
+   "Key": "EXO87ca85e29dd412f1238f1cf0",
+   "Secret": "-T9TAqy9a3-0-xj7HKsFFJOCcxfRpcnL6OW5oOrOcWU"
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Resources/s3cmd.py b/csharp/App_backup/Backend/Resources/s3cmd.py
new file mode 100755
index 000000000..6691d8797
--- /dev/null
+++ b/csharp/App_backup/Backend/Resources/s3cmd.py
@@ -0,0 +1,3380 @@
+#!/usr/bin/python3
+# -*- coding: utf-8 -*-
+
+## --------------------------------------------------------------------
+## s3cmd - S3 client
+##
+## Authors   : Michal Ludvig and contributors
+## Copyright : TGRMN Software - http://www.tgrmn.com - and contributors
+## Website   : http://s3tools.org
+## License   : GPL Version 2
+## --------------------------------------------------------------------
+## This program is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published by
+## the Free Software Foundation; either version 2 of the License, or
+## (at your option) any later version.
+## This program is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+## GNU General Public License for more details.
+## --------------------------------------------------------------------
+
+from __future__ import absolute_import, print_function, division
+
+import sys
+
+if sys.version_info < (2, 6):
+    sys.stderr.write(u"ERROR: Python 2.6 or higher required, sorry.\n")
+    # 72 == EX_OSFILE
+    sys.exit(72)
+
+PY3 = (sys.version_info >= (3, 0))
+
+import codecs
+import errno
+import glob
+import io
+import locale
+import logging
+import os
+import re
+import shutil
+import socket
+import subprocess
+import tempfile
+import time
+import traceback
+
+from copy import copy
+from optparse import OptionParser, Option, OptionValueError, IndentedHelpFormatter
+from logging import debug, info, warning, error
+
+
+try:
+    import htmlentitydefs
+except Exception:
+    # python 3 support
+    import html.entities as htmlentitydefs
+
+try:
+    unicode
+except NameError:
+    # python 3 support
+    # In python 3, unicode -> str, and str -> bytes
+    unicode = str
+
+try:
+    unichr
+except NameError:
+    # python 3 support
+    # In python 3, unichr was removed as chr can now do the job
+    unichr = chr
+
+try:
+    from shutil import which
+except ImportError:
+    # python2 fallback code
+    from distutils.spawn import find_executable as which
+
+if not PY3:
+    # ConnectionRefusedError does not exist in python2
+    class ConnectionError(OSError):
+        pass
+    class ConnectionRefusedError(ConnectionError):
+        pass
+
+
+def output(message):
+    sys.stdout.write(message + "\n")
+    sys.stdout.flush()
+
+def check_args_type(args, type, verbose_type):
+    """NOTE: This function looks like to not be used."""
+    for arg in args:
+        if S3Uri(arg).type != type:
+            raise ParameterError("Expecting %s instead of '%s'" % (verbose_type, arg))
+
+def cmd_du(args):
+    s3 = S3(Config())
+    if len(args) > 0:
+        uri = S3Uri(args[0])
+        if uri.type == "s3" and uri.has_bucket():
+            subcmd_bucket_usage(s3, uri)
+            return EX_OK
+    subcmd_bucket_usage_all(s3)
+    return EX_OK
+
+def subcmd_bucket_usage_all(s3):
+    """
+    Returns: sum of bucket sizes as integer
+    Raises: S3Error
+    """
+    cfg = Config()
+    response = s3.list_all_buckets()
+
+    buckets_size = 0
+    for bucket in response["list"]:
+        size = subcmd_bucket_usage(s3, S3Uri("s3://" + bucket["Name"]))
+        if size != None:
+            buckets_size += size
+    total_size, size_coeff = formatSize(buckets_size, cfg.human_readable_sizes)
+    total_size_str = str(total_size) + size_coeff
+    output(u"".rjust(12, "-"))
+    output(u"%s Total" % (total_size_str.ljust(12)))
+    return size
+
+def subcmd_bucket_usage(s3, uri):
+    """
+    Returns: bucket size as integer
+    Raises: S3Error
+    """
+    bucket_size = 0
+    object_count = 0
+    extra_info = u''
+
+    bucket = uri.bucket()
+    prefix = uri.object()
+    try:
+        for _, _, objects in s3.bucket_list_streaming(bucket, prefix=prefix, recursive=True):
+            for obj in objects:
+                bucket_size += int(obj["Size"])
+                object_count += 1
+
+    except S3Error as e:
+        if e.info["Code"] in S3.codes:
+            error(S3.codes[e.info["Code"]] % bucket)
+        raise
+
+    except KeyboardInterrupt as e:
+        extra_info = u' [interrupted]'
+
+    total_size_str = u"%d%s" % formatSize(bucket_size,
+                                          Config().human_readable_sizes)
+    if Config().human_readable_sizes:
+        total_size_str = total_size_str.rjust(5)
+    else:
+        total_size_str = total_size_str.rjust(12)
+    output(u"%s %7s objects %s%s" % (total_size_str, object_count, uri,
+                                     extra_info))
+    return bucket_size
+
+def cmd_ls(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    if len(args) > 0:
+        uri = S3Uri(args[0])
+        if uri.type == "s3" and uri.has_bucket():
+            subcmd_bucket_list(s3, uri, cfg.limit)
+            return EX_OK
+
+    # If not a s3 type uri or no bucket was provided, list all the buckets
+    subcmd_all_buckets_list(s3)
+    return EX_OK
+
+def subcmd_all_buckets_list(s3):
+
+    response = s3.list_all_buckets()
+
+    for bucket in sorted(response["list"], key=lambda b:b["Name"]):
+        output(u"%s  s3://%s" % (formatDateTime(bucket["CreationDate"]),
+                                 bucket["Name"]))
+
+def cmd_all_buckets_list_all_content(args):
+    cfg = Config()
+    s3 = S3(cfg)
+
+    response = s3.list_all_buckets()
+
+    for bucket in response["list"]:
+        subcmd_bucket_list(s3, S3Uri("s3://" + bucket["Name"]), cfg.limit)
+        output(u"")
+    return EX_OK
+
+def subcmd_bucket_list(s3, uri, limit):
+    cfg = Config()
+
+    bucket = uri.bucket()
+    prefix = uri.object()
+
+    debug(u"Bucket 's3://%s':" % bucket)
+    if prefix.endswith('*'):
+        prefix = prefix[:-1]
+    try:
+        response = s3.bucket_list(bucket, prefix = prefix, limit = limit)
+    except S3Error as e:
+        if e.info["Code"] in S3.codes:
+            error(S3.codes[e.info["Code"]] % bucket)
+        raise
+
+    # md5 are 32 char long, but for multipart there could be a suffix
+    if Config().human_readable_sizes:
+        # %(size)5s%(coeff)1s
+        format_size = u"%5d%1s"
+        dir_str = u"DIR".rjust(6)
+    else:
+        format_size = u"%12d%s"
+        dir_str = u"DIR".rjust(12)
+    if cfg.long_listing:
+        format_string = u"%(timestamp)16s %(size)s  %(md5)-35s  %(storageclass)-11s  %(uri)s"
+    elif cfg.list_md5:
+        format_string = u"%(timestamp)16s %(size)s  %(md5)-35s  %(uri)s"
+    else:
+        format_string = u"%(timestamp)16s %(size)s  %(uri)s"
+
+    for prefix in response['common_prefixes']:
+        output(format_string % {
+            "timestamp": "",
+            "size": dir_str,
+            "md5": "",
+            "storageclass": "",
+            "uri": uri.compose_uri(bucket, prefix["Prefix"])})
+
+    for object in response["list"]:
+        md5 = object.get('ETag', '').strip('"\'')
+        storageclass = object.get('StorageClass','')
+
+        if cfg.list_md5:
+            if '-' in md5: # need to get md5 from the object
+                object_uri = uri.compose_uri(bucket, object["Key"])
+                info_response = s3.object_info(S3Uri(object_uri))
+                try:
+                    md5 = info_response['s3cmd-attrs']['md5']
+                except KeyError:
+                    pass
+
+        size_and_coeff = formatSize(object["Size"],
+                                    Config().human_readable_sizes)
+        output(format_string % {
+            "timestamp": formatDateTime(object["LastModified"]),
+            "size" : format_size % size_and_coeff,
+            "md5" : md5,
+            "storageclass" : storageclass,
+            "uri": uri.compose_uri(bucket, object["Key"]),
+            })
+
+    if response["truncated"]:
+        warning(u"The list is truncated because the settings limit was reached.")
+
+def cmd_bucket_create(args):
+    cfg = Config()
+    s3 = S3(cfg)
+
+    for arg in args:
+        uri = S3Uri(arg)
+        if not uri.type == "s3" or not uri.has_bucket() or uri.has_object():
+            raise ParameterError("Expecting S3 URI with just the bucket name set instead of '%s'" % arg)
+        try:
+            response = s3.bucket_create(uri.bucket(), cfg.bucket_location, cfg.extra_headers)
+            output(u"Bucket '%s' created" % uri.uri())
+        except S3Error as e:
+            if e.info["Code"] in S3.codes:
+                error(S3.codes[e.info["Code"]] % uri.bucket())
+            raise
+    return EX_OK
+
+def cmd_website_info(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    for arg in args:
+        uri = S3Uri(arg)
+        if not uri.type == "s3" or not uri.has_bucket() or uri.has_object():
+            raise ParameterError("Expecting S3 URI with just the bucket name set instead of '%s'" % arg)
+        try:
+            response = s3.website_info(uri, cfg.bucket_location)
+            if response:
+                output(u"Bucket %s: Website configuration" % uri.uri())
+                output(u"Website endpoint: %s" % response['website_endpoint'])
+                output(u"Index document:   %s" % response['index_document'])
+                output(u"Error document:   %s" % response['error_document'])
+            else:
+                output(u"Bucket %s: Unable to receive website configuration." % (uri.uri()))
+        except S3Error as e:
+            if e.info["Code"] in S3.codes:
+                error(S3.codes[e.info["Code"]] % uri.bucket())
+            raise
+    return EX_OK
+
+def cmd_website_create(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    for arg in args:
+        uri = S3Uri(arg)
+        if not uri.type == "s3" or not uri.has_bucket() or uri.has_object():
+            raise ParameterError("Expecting S3 URI with just the bucket name set instead of '%s'" % arg)
+        try:
+            response = s3.website_create(uri, cfg.bucket_location)
+            output(u"Bucket '%s': website configuration created." % (uri.uri()))
+        except S3Error as e:
+            if e.info["Code"] in S3.codes:
+                error(S3.codes[e.info["Code"]] % uri.bucket())
+            raise
+    return EX_OK
+
+def cmd_website_delete(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    for arg in args:
+        uri = S3Uri(arg)
+        if not uri.type == "s3" or not uri.has_bucket() or uri.has_object():
+            raise ParameterError("Expecting S3 URI with just the bucket name set instead of '%s'" % arg)
+        try:
+            response = s3.website_delete(uri, cfg.bucket_location)
+            output(u"Bucket '%s': website configuration deleted." % (uri.uri()))
+        except S3Error as e:
+            if e.info["Code"] in S3.codes:
+                error(S3.codes[e.info["Code"]] % uri.bucket())
+            raise
+    return EX_OK
+
+def cmd_expiration_set(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    for arg in args:
+        uri = S3Uri(arg)
+        if not uri.type == "s3" or not uri.has_bucket() or uri.has_object():
+            raise ParameterError("Expecting S3 URI with just the bucket name set instead of '%s'" % arg)
+        try:
+            response = s3.expiration_set(uri, cfg.bucket_location)
+            if response["status"] == 200:
+                output(u"Bucket '%s': expiration configuration is set." % (uri.uri()))
+            elif response["status"] == 204:
+                output(u"Bucket '%s': expiration configuration is deleted." % (uri.uri()))
+        except S3Error as e:
+            if e.info["Code"] in S3.codes:
+                error(S3.codes[e.info["Code"]] % uri.bucket())
+            raise
+    return EX_OK
+
+def cmd_bucket_delete(args):
+    cfg = Config()
+    s3 = S3(cfg)
+
+    def _bucket_delete_one(uri, retry=True):
+        try:
+            response = s3.bucket_delete(uri.bucket())
+            output(u"Bucket '%s' removed" % uri.uri())
+        except S3Error as e:
+            if e.info['Code'] == 'NoSuchBucket':
+                if cfg.force:
+                    return EX_OK
+                else:
+                    raise
+            if e.info['Code'] == 'BucketNotEmpty' and retry and (cfg.force or cfg.recursive):
+                warning(u"Bucket is not empty. Removing all the objects from it first. This may take some time...")
+                rc = subcmd_batch_del(uri_str = uri.uri())
+                if rc == EX_OK:
+                    return _bucket_delete_one(uri, False)
+                else:
+                    output(u"Bucket was not removed")
+            elif e.info["Code"] in S3.codes:
+                error(S3.codes[e.info["Code"]] % uri.bucket())
+            raise
+        return EX_OK
+
+    for arg in args:
+        uri = S3Uri(arg)
+        if not uri.type == "s3" or not uri.has_bucket() or uri.has_object():
+            raise ParameterError("Expecting S3 URI with just the bucket name set instead of '%s'" % arg)
+        rc = _bucket_delete_one(uri)
+        if rc != EX_OK:
+            return rc
+    return EX_OK
+
+def cmd_object_put(args):
+    cfg = Config()
+    s3 = S3(cfg)
+
+    if len(args) == 0:
+        raise ParameterError("Nothing to upload. Expecting a local file or directory and a S3 URI destination.")
+
+    ## Normalize URI to convert s3://bkt to s3://bkt/ (trailing slash)
+    destination_base_uri = S3Uri(args.pop())
+    if destination_base_uri.type != 's3':
+        raise ParameterError("Destination must be S3Uri. Got: %s" % destination_base_uri)
+    destination_base = destination_base_uri.uri()
+
+    if len(args) == 0:
+        raise ParameterError("Nothing to upload. Expecting a local file or directory.")
+
+    local_list, single_file_local, exclude_list, total_size_local = fetch_local_list(args, is_src = True)
+
+    local_count = len(local_list)
+
+    info(u"Summary: %d local files to upload" % local_count)
+
+    if local_count == 0:
+        raise ParameterError("Nothing to upload.")
+
+    if local_count > 0:
+        if not single_file_local and '-' in local_list.keys():
+            raise ParameterError("Cannot specify multiple local files if uploading from '-' (ie stdin)")
+        elif single_file_local and local_list.keys()[0] == "-" and destination_base.endswith("/"):
+            raise ParameterError("Destination S3 URI must not end with '/' when uploading from stdin.")
+        elif not destination_base.endswith("/"):
+            if not single_file_local:
+                raise ParameterError("Destination S3 URI must end with '/' (ie must refer to a directory on the remote side).")
+            local_list[local_list.keys()[0]]['remote_uri'] = destination_base
+        else:
+            for key in local_list:
+                local_list[key]['remote_uri'] = destination_base + key
+
+    if cfg.dry_run:
+        for key in exclude_list:
+            output(u"exclude: %s" % key)
+        for key in local_list:
+            if key != "-":
+                nicekey = local_list[key]['full_name']
+            else:
+                nicekey = "<stdin>"
+            output(u"upload: '%s' -> '%s'" % (nicekey, local_list[key]['remote_uri']))
+
+        warning(u"Exiting now because of --dry-run")
+        return EX_OK
+
+    seq = 0
+    ret = EX_OK
+    for key in local_list:
+        seq += 1
+
+        uri_final = S3Uri(local_list[key]['remote_uri'])
+        try:
+            src_md5 = local_list.get_md5(key)
+        except IOError:
+            src_md5 = None
+
+        extra_headers = copy(cfg.extra_headers)
+        full_name_orig = local_list[key]['full_name']
+        full_name = full_name_orig
+        seq_label = "[%d of %d]" % (seq, local_count)
+        if Config().encrypt:
+            gpg_exitcode, full_name, extra_headers["x-amz-meta-s3tools-gpgenc"] = gpg_encrypt(full_name_orig)
+        attr_header = _build_attr_header(local_list[key], key, src_md5)
+        debug(u"attr_header: %s" % attr_header)
+        extra_headers.update(attr_header)
+        try:
+            response = s3.object_put(full_name, uri_final, extra_headers, extra_label = seq_label)
+        except S3UploadError as exc:
+            error(u"Upload of '%s' failed too many times (Last reason: %s)" % (full_name_orig, exc))
+            if cfg.stop_on_error:
+                ret = EX_DATAERR
+                error(u"Exiting now because of --stop-on-error")
+                break
+            ret = EX_PARTIAL
+            continue
+        except InvalidFileError as exc:
+            error(u"Upload of '%s' is not possible (Reason: %s)" % (full_name_orig, exc))
+            ret = EX_PARTIAL
+            if cfg.stop_on_error:
+                ret = EX_OSFILE
+                error(u"Exiting now because of --stop-on-error")
+                break
+            continue
+        if response is not None:
+            speed_fmt = formatSize(response["speed"], human_readable = True, floating_point = True)
+            if not Config().progress_meter:
+                if full_name_orig != "-":
+                    nicekey = full_name_orig
+                else:
+                    nicekey = "<stdin>"
+                output(u"upload: '%s' -> '%s' (%d bytes in %0.1f seconds, %0.2f %sB/s) %s" %
+                       (nicekey, uri_final, response["size"], response["elapsed"],
+                        speed_fmt[0], speed_fmt[1], seq_label))
+        if Config().acl_public:
+            output(u"Public URL of the object is: %s" %
+                   (uri_final.public_url()))
+        if Config().encrypt and full_name != full_name_orig:
+            debug(u"Removing temporary encrypted file: %s" % full_name)
+            os.remove(deunicodise(full_name))
+    return ret
+
+def cmd_object_get(args):
+    cfg = Config()
+    s3 = S3(cfg)
+
+    ## Check arguments:
+    ## if not --recursive:
+    ##   - first N arguments must be S3Uri
+    ##   - if the last one is S3 make current dir the destination_base
+    ##   - if the last one is a directory:
+    ##       - take all 'basenames' of the remote objects and
+    ##         make the destination name be 'destination_base'+'basename'
+    ##   - if the last one is a file or not existing:
+    ##       - if the number of sources (N, above) == 1 treat it
+    ##         as a filename and save the object there.
+    ##       - if there's more sources -> Error
+    ## if --recursive:
+    ##   - first N arguments must be S3Uri
+    ##       - for each Uri get a list of remote objects with that Uri as a prefix
+    ##       - apply exclude/include rules
+    ##       - each list item will have MD5sum, Timestamp and pointer to S3Uri
+    ##         used as a prefix.
+    ##   - the last arg may be '-' (stdout)
+    ##   - the last arg may be a local directory - destination_base
+    ##   - if the last one is S3 make current dir the destination_base
+    ##   - if the last one doesn't exist check remote list:
+    ##       - if there is only one item and its_prefix==its_name
+    ##         download that item to the name given in last arg.
+    ##       - if there are more remote items use the last arg as a destination_base
+    ##         and try to create the directory (incl. all parents).
+    ##
+    ## In both cases we end up with a list mapping remote object names (keys) to local file names.
+
+    ## Each item will be a dict with the following attributes
+    # {'remote_uri', 'local_filename'}
+    download_list = []
+
+    if len(args) == 0:
+        raise ParameterError("Nothing to download. Expecting S3 URI.")
+
+    if S3Uri(args[-1]).type == 'file':
+        destination_base = args.pop()
+    else:
+        destination_base = "."
+
+    if len(args) == 0:
+        raise ParameterError("Nothing to download. Expecting S3 URI.")
+
+    try:
+        remote_list, exclude_list, remote_total_size = fetch_remote_list(
+            args, require_attribs = True)
+    except S3Error as exc:
+        if exc.code == 'NoSuchKey':
+            raise ParameterError("Source object '%s' does not exist." % exc.resource)
+        raise
+
+    remote_count = len(remote_list)
+
+    info(u"Summary: %d remote files to download" % remote_count)
+
+    if remote_count > 0:
+        if destination_base == "-":
+            ## stdout is ok for multiple remote files!
+            for key in remote_list:
+                remote_list[key]['local_filename'] = "-"
+        elif not os.path.isdir(deunicodise(destination_base)):
+            ## We were either given a file name (existing or not)
+            if remote_count > 1:
+                raise ParameterError("Destination must be a directory or stdout when downloading multiple sources.")
+            remote_list[remote_list.keys()[0]]['local_filename'] = destination_base
+        else:
+            if destination_base[-1] != os.path.sep:
+                destination_base += os.path.sep
+            for key in remote_list:
+                local_filename = destination_base + key
+                if os.path.sep != "/":
+                    local_filename = os.path.sep.join(local_filename.split("/"))
+                remote_list[key]['local_filename'] = local_filename
+
+    if cfg.dry_run:
+        for key in exclude_list:
+            output(u"exclude: %s" % key)
+        for key in remote_list:
+            output(u"download: '%s' -> '%s'" % (remote_list[key]['object_uri_str'], remote_list[key]['local_filename']))
+
+        warning(u"Exiting now because of --dry-run")
+        return EX_OK
+
+    seq = 0
+    ret = EX_OK
+    for key in remote_list:
+        seq += 1
+        item = remote_list[key]
+        uri = S3Uri(item['object_uri_str'])
+        ## Encode / Decode destination with "replace" to make sure it's compatible with current encoding
+        destination = unicodise_safe(item['local_filename'])
+        seq_label = "[%d of %d]" % (seq, remote_count)
+
+        start_position = 0
+
+        if destination == "-":
+            ## stdout
+            dst_stream = io.open(sys.__stdout__.fileno(), mode='wb', closefd=False)
+            dst_stream.stream_name = u'<stdout>'
+            file_exists = True
+        else:
+            ## File
+            try:
+                file_exists = os.path.exists(deunicodise(destination))
+                try:
+                    dst_stream = io.open(deunicodise(destination), mode='ab')
+                    dst_stream.stream_name = destination
+                except IOError as e:
+                    if e.errno != errno.ENOENT:
+                        raise
+                    basename = destination[:destination.rindex(os.path.sep)]
+                    info(u"Creating directory: %s" % basename)
+                    os.makedirs(deunicodise(basename))
+                    dst_stream = io.open(deunicodise(destination), mode='ab')
+                    dst_stream.stream_name = destination
+
+                if file_exists:
+                    force = False
+                    skip = False
+                    if Config().get_continue:
+                        start_position = dst_stream.tell()
+                        item_size = item['size']
+                        if start_position == item_size:
+                            skip = True
+                        elif start_position > item_size:
+                            info(u"Download forced for '%s' as source is "
+                                 "smaller than local file" % destination)
+                            force = True
+                    elif Config().force:
+                        force = True
+                    elif Config().skip_existing:
+                        skip = True
+                    else:
+                        dst_stream.close()
+                        raise ParameterError(
+                            u"File '%s' already exists. Use either of --force /"
+                            " --continue / --skip-existing or give it a new"
+                            " name." % destination
+                        )
+
+                    if skip:
+                        dst_stream.close()
+                        info(u"Skipping over existing file: '%s'" % destination)
+                        continue
+
+                    if force:
+                        start_position = 0
+                        dst_stream.seek(0)
+                        dst_stream.truncate()
+
+            except IOError as e:
+                error(u"Creation of file '%s' failed (Reason: %s)"
+                      % (destination, e.strerror))
+                if cfg.stop_on_error:
+                    error(u"Exiting now because of --stop-on-error")
+                    raise
+                ret = EX_PARTIAL
+                continue
+
+        try:
+            try:
+                response = s3.object_get(uri, dst_stream, destination, start_position = start_position, extra_label = seq_label)
+            finally:
+                dst_stream.close()
+        except S3DownloadError as e:
+            error(u"Download of '%s' failed (Reason: %s)" % (destination, e))
+            # Delete, only if file didn't exist before!
+            if not file_exists:
+                debug(u"object_get failed for '%s', deleting..." % (destination,))
+                os.unlink(deunicodise(destination))
+            if cfg.stop_on_error:
+                error(u"Exiting now because of --stop-on-error")
+                raise
+            ret = EX_PARTIAL
+            continue
+        except S3Error as e:
+            error(u"Download of '%s' failed (Reason: %s)" % (destination, e))
+            if not file_exists: # Delete, only if file didn't exist before!
+                debug(u"object_get failed for '%s', deleting..." % (destination,))
+                os.unlink(deunicodise(destination))
+            raise
+
+        if "x-amz-meta-s3tools-gpgenc" in response["headers"]:
+            gpg_decrypt(destination, response["headers"]["x-amz-meta-s3tools-gpgenc"])
+            response["size"] = os.stat(deunicodise(destination))[6]
+        if "last-modified" in response["headers"] and destination != "-":
+            last_modified = time.mktime(time.strptime(response["headers"]["last-modified"], "%a, %d %b %Y %H:%M:%S GMT"))
+            os.utime(deunicodise(destination), (last_modified, last_modified))
+            debug("set mtime to %s" % last_modified)
+        if not Config().progress_meter and destination != "-":
+            speed_fmt = formatSize(response["speed"], human_readable = True, floating_point = True)
+            output(u"download: '%s' -> '%s' (%d bytes in %0.1f seconds, %0.2f %sB/s)" %
+                (uri, destination, response["size"], response["elapsed"], speed_fmt[0], speed_fmt[1]))
+        if Config().delete_after_fetch:
+            s3.object_delete(uri)
+            output(u"File '%s' removed after fetch" % (uri))
+    return ret
+
+def cmd_object_del(args):
+    cfg = Config()
+    recursive = cfg.recursive
+    for uri_str in args:
+        uri = S3Uri(uri_str)
+        if uri.type != "s3":
+            raise ParameterError("Expecting S3 URI instead of '%s'" % uri_str)
+        if not uri.has_object():
+            if recursive and not cfg.force:
+                raise ParameterError("Please use --force to delete ALL contents of %s" % uri_str)
+            elif not recursive:
+                raise ParameterError("File name required, not only the bucket name. Alternatively use --recursive")
+
+        if not recursive:
+            rc = subcmd_object_del_uri(uri_str)
+        elif cfg.exclude or cfg.include or cfg.max_delete > 0:
+            # subcmd_batch_del_iterative does not support file exclusion and can't
+            # accurately know how many total files will be deleted, so revert to batch delete.
+            rc = subcmd_batch_del(uri_str = uri_str)
+        else:
+            rc = subcmd_batch_del_iterative(uri_str = uri_str)
+        if not rc:
+            return rc
+    return EX_OK
+
+def subcmd_batch_del_iterative(uri_str = None, bucket = None):
+    """ Streaming version of batch deletion (doesn't realize whole list in memory before deleting).
+
+    Differences from subcmd_batch_del:
+      - Does not obey --exclude directives or obey cfg.max_delete (use subcmd_batch_del in those cases)
+    """
+    if bucket and uri_str:
+        raise ValueError("Pass only one of uri_str or bucket")
+    if bucket: # bucket specified
+        uri_str = "s3://%s" % bucket
+    cfg = Config()
+    s3 = S3(cfg)
+    uri = S3Uri(uri_str)
+    bucket = uri.bucket()
+
+    deleted_bytes = deleted_count = 0
+
+    for _, _, to_delete in s3.bucket_list_streaming(bucket, prefix=uri.object(), recursive=True):
+        if not to_delete:
+            continue
+        if not cfg.dry_run:
+            response = s3.object_batch_delete_uri_strs([uri.compose_uri(bucket, item['Key']) for item in to_delete])
+        deleted_bytes += sum(int(item["Size"]) for item in to_delete)
+        deleted_count += len(to_delete)
+        output(u'\n'.join(u"delete: '%s'" % uri.compose_uri(bucket, p['Key']) for p in to_delete))
+
+    if deleted_count:
+        # display summary data of deleted files
+        if cfg.stats:
+            stats_info = StatsInfo()
+            stats_info.files_deleted = deleted_count
+            stats_info.size_deleted = deleted_bytes
+            output(stats_info.format_output())
+        else:
+            total_size, size_coeff = formatSize(deleted_bytes, Config().human_readable_sizes)
+            total_size_str = str(total_size) + size_coeff
+            info(u"Deleted %s objects (%s) from %s" % (deleted_count, total_size_str, uri))
+    else:
+        warning(u"Remote list is empty.")
+
+    return EX_OK
+
+def subcmd_batch_del(uri_str = None, bucket = None, remote_list = None):
+    """
+    Returns: EX_OK
+    Raises: ValueError
+    """
+    cfg = Config()
+    s3 = S3(cfg)
+    def _batch_del(remote_list):
+        to_delete = remote_list[:1000]
+        remote_list = remote_list[1000:]
+        while len(to_delete):
+            debug(u"Batch delete %d, remaining %d" % (len(to_delete), len(remote_list)))
+            if not cfg.dry_run:
+                response = s3.object_batch_delete(to_delete)
+            output(u'\n'.join((u"delete: '%s'" % to_delete[p]['object_uri_str']) for p in to_delete))
+            to_delete = remote_list[:1000]
+            remote_list = remote_list[1000:]
+
+    if remote_list is not None and len(remote_list) == 0:
+        return False
+
+    if len([item for item in [uri_str, bucket, remote_list] if item]) != 1:
+        raise ValueError("One and only one of 'uri_str', 'bucket', 'remote_list' can be specified.")
+
+    if bucket: # bucket specified
+        uri_str = "s3://%s" % bucket
+    if remote_list is None: # uri_str specified
+        remote_list, exclude_list, remote_total_size = fetch_remote_list(uri_str, require_attribs = False)
+
+    if len(remote_list) == 0:
+        warning(u"Remote list is empty.")
+        return EX_OK
+
+    if cfg.max_delete > 0 and len(remote_list) > cfg.max_delete:
+        warning(u"delete: maximum requested number of deletes would be exceeded, none performed.")
+        return EX_OK
+
+    _batch_del(remote_list)
+
+    if cfg.dry_run:
+        warning(u"Exiting now because of --dry-run")
+    return EX_OK
+
+def subcmd_object_del_uri(uri_str, recursive = None):
+    """
+    Returns: True if XXX, False if XXX
+    Raises: ValueError
+    """
+    cfg = Config()
+    s3 = S3(cfg)
+
+    if recursive is None:
+        recursive = cfg.recursive
+
+    remote_list, exclude_list, remote_total_size = fetch_remote_list(uri_str, require_attribs = False, recursive = recursive)
+
+    remote_count = len(remote_list)
+
+    info(u"Summary: %d remote files to delete" % remote_count)
+    if cfg.max_delete > 0 and remote_count > cfg.max_delete:
+        warning(u"delete: maximum requested number of deletes would be exceeded, none performed.")
+        return False
+
+    if cfg.dry_run:
+        for key in exclude_list:
+            output(u"exclude: %s" % key)
+        for key in remote_list:
+            output(u"delete: %s" % remote_list[key]['object_uri_str'])
+
+        warning(u"Exiting now because of --dry-run")
+        return True
+
+    for key in remote_list:
+        item = remote_list[key]
+        response = s3.object_delete(S3Uri(item['object_uri_str']))
+        output(u"delete: '%s'" % item['object_uri_str'])
+    return True
+
+def cmd_object_restore(args):
+    cfg = Config()
+    s3 = S3(cfg)
+
+    if cfg.restore_days < 1:
+        raise ParameterError("You must restore a file for 1 or more days")
+
+    # accept case-insensitive argument but fix it to match S3 API
+    if cfg.restore_priority.title() not in ['Standard', 'Expedited', 'Bulk']:
+        raise ParameterError("Valid restoration priorities: bulk, standard, expedited")
+    else:
+        cfg.restore_priority = cfg.restore_priority.title()
+
+    remote_list, exclude_list, remote_total_size = fetch_remote_list(args, require_attribs = False, recursive = cfg.recursive)
+
+    remote_count = len(remote_list)
+
+    info(u"Summary: Restoring %d remote files for %d days at %s priority" % (remote_count, cfg.restore_days, cfg.restore_priority))
+
+    if cfg.dry_run:
+        for key in exclude_list:
+            output(u"exclude: %s" % key)
+        for key in remote_list:
+            output(u"restore: '%s'" % remote_list[key]['object_uri_str'])
+
+        warning(u"Exiting now because of --dry-run")
+        return EX_OK
+
+    for key in remote_list:
+        item = remote_list[key]
+
+        uri = S3Uri(item['object_uri_str'])
+        if not item['object_uri_str'].endswith("/"):
+            try:
+                response = s3.object_restore(S3Uri(item['object_uri_str']))
+                output(u"restore: '%s'" % item['object_uri_str'])
+            except S3Error as e:
+                if e.code == "RestoreAlreadyInProgress":
+                    warning("%s: %s" % (e.message, item['object_uri_str']))
+                else:
+                    raise e
+        else:
+            debug(u"Skipping directory since only files may be restored")
+    return EX_OK
+
+
+def subcmd_cp_mv(args, process_fce, action_str, message):
+    cfg = Config()
+    if action_str == 'modify':
+        if len(args) < 1:
+            raise ParameterError("Expecting one or more S3 URIs for "
+                                 + action_str)
+        destination_base = None
+    else:
+        if len(args) < 2:
+            raise ParameterError("Expecting two or more S3 URIs for "
+                                 + action_str)
+        dst_base_uri = S3Uri(args.pop())
+        if dst_base_uri.type != "s3":
+            raise ParameterError("Destination must be S3 URI. To download a "
+                                 "file use 'get' or 'sync'.")
+        destination_base = dst_base_uri.uri()
+
+    scoreboard = ExitScoreboard()
+
+    remote_list, exclude_list, remote_total_size = \
+        fetch_remote_list(args, require_attribs=False)
+
+    remote_count = len(remote_list)
+
+    info(u"Summary: %d remote files to %s" % (remote_count, action_str))
+
+    if destination_base:
+        # Trying to mv dir1/ to dir2 will not pass a test in S3.FileLists,
+        # so we don't need to test for it here.
+        if not destination_base.endswith('/') \
+           and (len(remote_list) > 1 or cfg.recursive):
+            raise ParameterError("Destination must be a directory and end with"
+                                 " '/' when acting on a folder content or on "
+                                 "multiple sources.")
+
+        if cfg.recursive:
+            for key in remote_list:
+                remote_list[key]['dest_name'] = destination_base + key
+        else:
+            for key in remote_list:
+                if destination_base.endswith("/"):
+                    remote_list[key]['dest_name'] = destination_base + key
+                else:
+                    remote_list[key]['dest_name'] = destination_base
+    else:
+        for key in remote_list:
+            remote_list[key]['dest_name'] = remote_list[key]['object_uri_str']
+
+    if cfg.dry_run:
+        for key in exclude_list:
+            output(u"exclude: %s" % key)
+        for key in remote_list:
+            output(u"%s: '%s' -> '%s'" % (action_str,
+                                          remote_list[key]['object_uri_str'],
+                                          remote_list[key]['dest_name']))
+
+        warning(u"Exiting now because of --dry-run")
+        return EX_OK
+
+    seq = 0
+    for key in remote_list:
+        seq += 1
+        seq_label = "[%d of %d]" % (seq, remote_count)
+
+        item = remote_list[key]
+        src_uri = S3Uri(item['object_uri_str'])
+        dst_uri = S3Uri(item['dest_name'])
+        src_size = item.get('size')
+
+        extra_headers = copy(cfg.extra_headers)
+        try:
+            response = process_fce(src_uri, dst_uri, extra_headers,
+                                   src_size=src_size,
+                                   extra_label=seq_label)
+            output(message % {"src": src_uri, "dst": dst_uri,
+                              "extra": seq_label})
+            if Config().acl_public:
+                info(u"Public URL is: %s" % dst_uri.public_url())
+            scoreboard.success()
+        except (S3Error, S3UploadError) as exc:
+            if isinstance(exc, S3Error) and exc.code == "NoSuchKey":
+                scoreboard.notfound()
+                warning(u"Key not found %s" % item['object_uri_str'])
+            else:
+                scoreboard.failed()
+                error(u"Copy failed for: '%s' (%s)", item['object_uri_str'],
+                      exc)
+
+            if cfg.stop_on_error:
+                break
+    return scoreboard.rc()
+
+def cmd_cp(args):
+    s3 = S3(Config())
+    return subcmd_cp_mv(args, s3.object_copy, "copy",
+                        u"remote copy: '%(src)s' -> '%(dst)s'  %(extra)s")
+
+def cmd_modify(args):
+    s3 = S3(Config())
+    return subcmd_cp_mv(args, s3.object_modify, "modify",
+                        u"modify: '%(src)s'  %(extra)s")
+
+def cmd_mv(args):
+    s3 = S3(Config())
+    return subcmd_cp_mv(args, s3.object_move, "move",
+                        u"move: '%(src)s' -> '%(dst)s'  %(extra)s")
+
+def cmd_info(args):
+    cfg = Config()
+    s3 = S3(cfg)
+
+    while (len(args)):
+        uri_arg = args.pop(0)
+        uri = S3Uri(uri_arg)
+        if uri.type != "s3" or not uri.has_bucket():
+            raise ParameterError("Expecting S3 URI instead of '%s'" % uri_arg)
+
+        try:
+            if uri.has_object():
+                info = s3.object_info(uri)
+                output(u"%s (object):" % uri.uri())
+                output(u"   File size: %s" % info['headers']['content-length'])
+                output(u"   Last mod:  %s" % info['headers']['last-modified'])
+                output(u"   MIME type: %s" % info['headers'].get('content-type', 'none'))
+                output(u"   Storage:   %s" % info['headers'].get('x-amz-storage-class', 'STANDARD'))
+                md5 = info['headers'].get('etag', '').strip('"\'')
+                try:
+                    md5 = info['s3cmd-attrs']['md5']
+                except KeyError:
+                    pass
+                output(u"   MD5 sum:   %s" % md5)
+                if 'x-amz-server-side-encryption' in info['headers']:
+                    output(u"   SSE:       %s" % info['headers']['x-amz-server-side-encryption'])
+                else:
+                    output(u"   SSE:       none")
+
+            else:
+                info = s3.bucket_info(uri)
+                output(u"%s (bucket):" % uri.uri())
+                output(u"   Location:  %s" % (info['bucket-location']
+                                              or 'none'))
+                output(u"   Payer:     %s" % (info['requester-pays']
+                                              or 'none'))
+                expiration = s3.expiration_info(uri, cfg.bucket_location)
+                if expiration and expiration['prefix'] is not None:
+                    expiration_desc = "Expiration Rule: "
+                    if expiration['prefix'] == "":
+                        expiration_desc += "all objects in this bucket "
+                    elif expiration['prefix'] is not None:
+                        expiration_desc += "objects with key prefix '" + expiration['prefix'] + "' "
+                    expiration_desc += "will expire in '"
+                    if expiration['days']:
+                        expiration_desc += expiration['days'] + "' day(s) after creation"
+                    elif expiration['date']:
+                        expiration_desc += expiration['date'] + "' "
+                    output(u"   %s" % expiration_desc)
+                else:
+                    output(u"   Expiration Rule: none")
+
+            try:
+                policy = s3.get_policy(uri)
+                output(u"   Policy:    %s" % policy)
+            except S3Error as exc:
+                # Ignore the exception and don't fail the info
+                # if the server doesn't support setting ACLs
+                if exc.status == 403:
+                    output(u"   Policy:    Not available: GetPolicy permission is needed to read the policy")
+                elif exc.status == 405:
+                    output(u"   Policy:    Not available: Only the bucket owner can read the policy")
+                elif exc.status not in [404, 501]:
+                    raise exc
+                else:
+                    output(u"   Policy:    none")
+
+            try:
+                cors = s3.get_cors(uri)
+                output(u"   CORS:      %s" % cors)
+            except S3Error as exc:
+                # Ignore the exception and don't fail the info
+                # if the server doesn't support setting ACLs
+                if exc.status not in [404, 501]:
+                    raise exc
+                output(u"   CORS:      none")
+
+            try:
+                acl = s3.get_acl(uri)
+                acl_grant_list = acl.getGrantList()
+                for grant in acl_grant_list:
+                    output(u"   ACL:       %s: %s" % (grant['grantee'], grant['permission']))
+                if acl.isAnonRead():
+                    output(u"   URL:       %s" % uri.public_url())
+            except S3Error as exc:
+                # Ignore the exception and don't fail the info
+                # if the server doesn't support setting ACLs
+                if exc.status not in [404, 501]:
+                    raise exc
+                else:
+                    output(u"   ACL:       none")
+
+            if uri.has_object():
+                # Temporary hack for performance + python3 compatibility
+                if PY3:
+                    info_headers_iter = info['headers'].items()
+                else:
+                    info_headers_iter = info['headers'].iteritems()
+                for header, value in info_headers_iter:
+                    if header.startswith('x-amz-meta-'):
+                        output(u"   %s: %s" % (header, value))
+
+        except S3Error as e:
+            if e.info["Code"] in S3.codes:
+                error(S3.codes[e.info["Code"]] % uri.bucket())
+            raise
+    return EX_OK
+
+def filedicts_to_keys(*args):
+    keys = set()
+    for a in args:
+        keys.update(a.keys())
+    keys = list(keys)
+    keys.sort()
+    return keys
+
+def cmd_sync_remote2remote(args):
+    cfg = Config()
+    s3 = S3(cfg)
+
+    # Normalise s3://uri (e.g. assert trailing slash)
+    destination_base = S3Uri(args[-1]).uri()
+
+    destbase_with_source_list = set()
+    for source_arg in args[:-1]:
+        if source_arg.endswith('/'):
+            destbase_with_source_list.add(destination_base)
+        else:
+            destbase_with_source_list.add(os.path.join(destination_base,
+                                                  os.path.basename(source_arg)))
+
+    stats_info = StatsInfo()
+
+    src_list, src_exclude_list, remote_total_size = fetch_remote_list(args[:-1], recursive = True, require_attribs = True)
+    dst_list, dst_exclude_list, _ = fetch_remote_list(destbase_with_source_list, recursive = True, require_attribs = True)
+
+    src_count = len(src_list)
+    orig_src_count = src_count
+    dst_count = len(dst_list)
+    deleted_count = 0
+
+    info(u"Found %d source files, %d destination files" % (src_count, dst_count))
+
+    src_list, dst_list, update_list, copy_pairs = compare_filelists(src_list, dst_list, src_remote = True, dst_remote = True)
+
+    src_count = len(src_list)
+    update_count = len(update_list)
+    dst_count = len(dst_list)
+
+    print(u"Summary: %d source files to copy, %d files at destination to delete" % (src_count + update_count, dst_count))
+
+    ### Populate 'target_uri' only if we've got something to sync from src to dst
+    for key in src_list:
+        src_list[key]['target_uri'] = destination_base + key
+    for key in update_list:
+        update_list[key]['target_uri'] = destination_base + key
+
+    if cfg.dry_run:
+        keys = filedicts_to_keys(src_exclude_list, dst_exclude_list)
+        for key in keys:
+            output(u"exclude: %s" % key)
+        if cfg.delete_removed:
+            for key in dst_list:
+                output(u"delete: '%s'" % dst_list[key]['object_uri_str'])
+        for key in src_list:
+            output(u"remote copy: '%s' -> '%s'" % (src_list[key]['object_uri_str'], src_list[key]['target_uri']))
+        for key in update_list:
+            output(u"remote copy: '%s' -> '%s'" % (update_list[key]['object_uri_str'], update_list[key]['target_uri']))
+        warning(u"Exiting now because of --dry-run")
+        return EX_OK
+
+    # if there are copy pairs, we can't do delete_before, on the chance
+    # we need one of the to-be-deleted files as a copy source.
+    if len(copy_pairs) > 0:
+        cfg.delete_after = True
+
+    if cfg.delete_removed and orig_src_count == 0 and len(dst_list) and not cfg.force:
+        warning(u"delete: cowardly refusing to delete because no source files were found.  Use --force to override.")
+        cfg.delete_removed = False
+
+    # Delete items in destination that are not in source
+    if cfg.delete_removed and not cfg.delete_after:
+        subcmd_batch_del(remote_list = dst_list)
+        deleted_count = len(dst_list)
+
+    def _upload(src_list, seq, src_count):
+        file_list = src_list.keys()
+        file_list.sort()
+        ret = EX_OK
+        total_nb_files = 0
+        total_size = 0
+        for file in file_list:
+            seq += 1
+            item = src_list[file]
+            src_uri = S3Uri(item['object_uri_str'])
+            dst_uri = S3Uri(item['target_uri'])
+            src_size = item.get('size')
+            seq_label = "[%d of %d]" % (seq, src_count)
+            extra_headers = copy(cfg.extra_headers)
+            try:
+                response = s3.object_copy(src_uri, dst_uri, extra_headers,
+                                          src_size=src_size,
+                                          extra_label=seq_label)
+                output(u"remote copy: '%s' -> '%s'  %s" %
+                       (src_uri, dst_uri, seq_label))
+                total_nb_files += 1
+                total_size += item.get(u'size', 0)
+            except (S3Error, S3UploadError) as exc:
+                ret = EX_PARTIAL
+                error(u"File '%s' could not be copied: %s", src_uri, exc)
+                if cfg.stop_on_error:
+                    raise
+        return ret, seq, total_nb_files, total_size
+
+    # Perform the synchronization of files
+    timestamp_start = time.time()
+    seq = 0
+    ret, seq, nb_files, size = _upload(src_list, seq, src_count + update_count)
+    total_files_copied = nb_files
+    total_size_copied = size
+
+    status, seq, nb_files, size = _upload(update_list, seq, src_count + update_count)
+    if ret == EX_OK:
+        ret = status
+    total_files_copied += nb_files
+    total_size_copied += size
+
+    n_copied, bytes_saved, failed_copy_files = remote_copy(
+        s3, copy_pairs, destination_base, None, False)
+    total_files_copied += n_copied
+    total_size_copied += bytes_saved
+
+    #process files not copied
+    debug("Process files that were not remotely copied")
+    failed_copy_count = len(failed_copy_files)
+    for key in failed_copy_files:
+        failed_copy_files[key]['target_uri'] = destination_base + key
+    status, seq, nb_files, size = _upload(failed_copy_files, seq, src_count + update_count + failed_copy_count)
+    if ret == EX_OK:
+        ret = status
+    total_files_copied += nb_files
+    total_size_copied += size
+
+    # Delete items in destination that are not in source
+    if cfg.delete_removed and cfg.delete_after:
+        subcmd_batch_del(remote_list = dst_list)
+        deleted_count = len(dst_list)
+
+    stats_info.files = orig_src_count
+    stats_info.size = remote_total_size
+    stats_info.files_copied = total_files_copied
+    stats_info.size_copied = total_size_copied
+    stats_info.files_deleted = deleted_count
+
+    total_elapsed = max(1.0, time.time() - timestamp_start)
+    outstr = "Done. Copied %d files in %0.1f seconds, %0.2f files/s." % (total_files_copied, total_elapsed, seq / total_elapsed)
+    if cfg.stats:
+        outstr += stats_info.format_output()
+        output(outstr)
+    elif seq > 0:
+        output(outstr)
+    else:
+        info(outstr)
+
+    return ret
+
+def cmd_sync_remote2local(args):
+    cfg = Config()
+    s3 = S3(cfg)
+
+    def _do_deletes(local_list):
+        total_size = 0
+        if cfg.max_delete > 0 and len(local_list) > cfg.max_delete:
+            warning(u"delete: maximum requested number of deletes would be exceeded, none performed.")
+            return total_size
+        for key in local_list:
+            os.unlink(deunicodise(local_list[key]['full_name']))
+            output(u"delete: '%s'" % local_list[key]['full_name'])
+            total_size += local_list[key].get(u'size', 0)
+        return len(local_list), total_size
+
+    destination_base = args[-1]
+    source_args = args[:-1]
+    fetch_source_args = args[:-1]
+
+    if not destination_base.endswith(os.path.sep):
+        if fetch_source_args[0].endswith(u'/') or len(fetch_source_args) > 1:
+            raise ParameterError("Destination must be a directory and end with '/' when downloading multiple sources.")
+
+    stats_info = StatsInfo()
+
+    remote_list, src_exclude_list, remote_total_size = fetch_remote_list(fetch_source_args, recursive = True, require_attribs = True)
+
+
+    # - The source path is either like "/myPath/my_src_folder" and
+    # the user want to download this single folder and Optionally only delete
+    # things that have been removed inside this folder. For this case, we only
+    # have to look inside destination_base/my_src_folder and not at the root of
+    # destination_base.
+    # - Or like "/myPath/my_src_folder/" and the user want to have the sync
+    # with the content of this folder
+    destbase_with_source_list = set()
+    for source_arg in fetch_source_args:
+        if source_arg.endswith('/'):
+            if destination_base.endswith(os.path.sep):
+                destbase_with_source_list.add(destination_base)
+            else:
+                destbase_with_source_list.add(destination_base + os.path.sep)
+        else:
+            destbase_with_source_list.add(os.path.join(destination_base,
+                                                      os.path.basename(source_arg)))
+    local_list, single_file_local, dst_exclude_list, local_total_size = fetch_local_list(destbase_with_source_list, is_src = False, recursive = True)
+
+    local_count = len(local_list)
+    remote_count = len(remote_list)
+    orig_remote_count = remote_count
+
+    info(u"Found %d remote files, %d local files" % (remote_count, local_count))
+
+    remote_list, local_list, update_list, copy_pairs = compare_filelists(remote_list, local_list, src_remote = True, dst_remote = False)
+
+    local_count = len(local_list)
+    remote_count = len(remote_list)
+    update_count = len(update_list)
+    copy_pairs_count = len(copy_pairs)
+
+    info(u"Summary: %d remote files to download, %d local files to delete, %d local files to hardlink" % (remote_count + update_count, local_count, copy_pairs_count))
+
+    def _set_local_filename(remote_list, destination_base, source_args):
+        if len(remote_list) == 0:
+            return
+
+        if destination_base.endswith(os.path.sep):
+            if not os.path.exists(deunicodise(destination_base)):
+                if not cfg.dry_run:
+                    os.makedirs(deunicodise(destination_base))
+            if not os.path.isdir(deunicodise(destination_base)):
+                raise ParameterError("Destination is not an existing directory")
+        elif len(remote_list) == 1 and \
+             source_args[0] == remote_list[remote_list.keys()[0]].get(u'object_uri_str', ''):
+            if os.path.isdir(deunicodise(destination_base)):
+                raise ParameterError("Destination already exists and is a directory")
+            remote_list[remote_list.keys()[0]]['local_filename'] = destination_base
+            return
+
+        if destination_base[-1] != os.path.sep:
+            destination_base += os.path.sep
+        for key in remote_list:
+            local_filename = destination_base + key
+            if os.path.sep != "/":
+                local_filename = os.path.sep.join(local_filename.split("/"))
+            remote_list[key]['local_filename'] = local_filename
+
+    _set_local_filename(remote_list, destination_base, source_args)
+    _set_local_filename(update_list, destination_base, source_args)
+
+    if cfg.dry_run:
+        keys = filedicts_to_keys(src_exclude_list, dst_exclude_list)
+        for key in keys:
+            output(u"exclude: %s" % key)
+        if cfg.delete_removed:
+            for key in local_list:
+                output(u"delete: '%s'" % local_list[key]['full_name'])
+        for key in remote_list:
+            output(u"download: '%s' -> '%s'" % (remote_list[key]['object_uri_str'], remote_list[key]['local_filename']))
+        for key in update_list:
+            output(u"download: '%s' -> '%s'" % (update_list[key]['object_uri_str'], update_list[key]['local_filename']))
+
+        warning(u"Exiting now because of --dry-run")
+        return EX_OK
+
+    # if there are copy pairs, we can't do delete_before, on the chance
+    # we need one of the to-be-deleted files as a copy source.
+    if len(copy_pairs) > 0:
+        cfg.delete_after = True
+
+    if cfg.delete_removed and orig_remote_count == 0 and len(local_list) and not cfg.force:
+        warning(u"delete: cowardly refusing to delete because no source files were found.  Use --force to override.")
+        cfg.delete_removed = False
+
+    if cfg.delete_removed and not cfg.delete_after:
+        deleted_count, deleted_size = _do_deletes(local_list)
+    else:
+        deleted_count, deleted_size = (0, 0)
+
+    def _download(remote_list, seq, total, total_size, dir_cache):
+        original_umask = os.umask(0)
+        os.umask(original_umask)
+        file_list = remote_list.keys()
+        file_list.sort()
+        ret = EX_OK
+        for file in file_list:
+            seq += 1
+            item = remote_list[file]
+            uri = S3Uri(item['object_uri_str'])
+            dst_file = item['local_filename']
+            is_empty_directory = dst_file.endswith('/')
+            seq_label = "[%d of %d]" % (seq, total)
+
+            dst_dir = unicodise(os.path.dirname(deunicodise(dst_file)))
+            if not dst_dir in dir_cache:
+                dir_cache[dst_dir] = Utils.mkdir_with_parents(dst_dir)
+            if dir_cache[dst_dir] == False:
+                if cfg.stop_on_error:
+                    error(u"Exiting now because of --stop-on-error")
+                    raise OSError("Download of '%s' failed (Reason: %s destination directory is not writable)" % (file, dst_dir))
+                error(u"Download of '%s' failed (Reason: %s destination directory is not writable)" % (file, dst_dir))
+                ret = EX_PARTIAL
+                continue
+
+            try:
+                chkptfname_b = ''
+                if not is_empty_directory: # ignore empty directory at S3:
+                    debug(u"dst_file=%s" % dst_file)
+                    # create temporary files (of type .s3cmd.XXXX.tmp) in the same directory
+                    # for downloading and then rename once downloaded
+                    # unicode provided to mkstemp argument
+                    chkptfd, chkptfname_b = tempfile.mkstemp(u".tmp", u".s3cmd.",
+                                                           os.path.dirname(dst_file))
+                    with io.open(chkptfd, mode='wb') as dst_stream:
+                        dst_stream.stream_name = unicodise(chkptfname_b)
+                        debug(u"created chkptfname=%s" % dst_stream.stream_name)
+                        response = s3.object_get(uri, dst_stream, dst_file, extra_label = seq_label)
+
+                    # download completed, rename the file to destination
+                    if os.name == "nt":
+                        # Windows is really a bad OS. Rename can't overwrite an existing file
+                        try:
+                            os.unlink(deunicodise(dst_file))
+                        except OSError:
+                            pass
+                    os.rename(chkptfname_b, deunicodise(dst_file))
+                    debug(u"renamed chkptfname=%s to dst_file=%s" % (dst_stream.stream_name, dst_file))
+            except OSError as exc:
+                allow_partial = True
+
+                if exc.errno == errno.EISDIR:
+                    error(u"Download of '%s' failed (Reason: %s is a directory)" % (file, dst_file))
+                elif os.name != "nt" and exc.errno == errno.ETXTBSY:
+                    error(u"Download of '%s' failed (Reason: %s is currently open for execute, cannot be overwritten)" % (file, dst_file))
+                elif exc.errno == errno.EPERM or exc.errno == errno.EACCES:
+                    error(u"Download of '%s' failed (Reason: %s permission denied)" % (file, dst_file))
+                elif exc.errno == errno.EBUSY:
+                    error(u"Download of '%s' failed (Reason: %s is busy)" % (file, dst_file))
+                elif exc.errno == errno.EFBIG:
+                    error(u"Download of '%s' failed (Reason: %s is too big)" % (file, dst_file))
+                elif exc.errno == errno.ENAMETOOLONG:
+                    error(u"Download of '%s' failed (Reason: File Name is too long)" % file)
+
+                elif (exc.errno == errno.ENOSPC or (os.name != "nt" and exc.errno == errno.EDQUOT)):
+                    error(u"Download of '%s' failed (Reason: No space left)" % file)
+                    allow_partial = False
+                else:
+                    error(u"Download of '%s' failed (Reason: Unknown OsError %d)" % (file, exc.errno or 0))
+                    allow_partial = False
+
+                try:
+                    # Try to remove the temp file if it exists
+                    if chkptfname_b:
+                        os.unlink(chkptfname_b)
+                except Exception:
+                    pass
+
+                if allow_partial and not cfg.stop_on_error:
+                    ret = EX_PARTIAL
+                    continue
+
+                ret = EX_OSFILE
+                if allow_partial:
+                    error(u"Exiting now because of --stop-on-error")
+                else:
+                    error(u"Exiting now because of fatal error")
+                raise
+            except S3DownloadError as exc:
+                error(u"Download of '%s' failed too many times (Last Reason: %s). "
+                      "This is usually a transient error, please try again "
+                      "later." % (file, exc))
+                try:
+                    os.unlink(chkptfname_b)
+                except Exception as sub_exc:
+                    warning(u"Error deleting temporary file %s (Reason: %s)",
+                            (dst_stream.stream_name, sub_exc))
+                if cfg.stop_on_error:
+                    ret = EX_DATAERR
+                    error(u"Exiting now because of --stop-on-error")
+                    raise
+                ret = EX_PARTIAL
+                continue
+            except S3Error as exc:
+                warning(u"Remote file '%s'. S3Error: %s" % (exc.resource, exc))
+                try:
+                    os.unlink(chkptfname_b)
+                except Exception as sub_exc:
+                    warning(u"Error deleting temporary file %s (Reason: %s)",
+                            (dst_stream.stream_name, sub_exc))
+                if cfg.stop_on_error:
+                    raise
+                ret = EX_PARTIAL
+                continue
+
+            try:
+                # set permissions on destination file
+                if not is_empty_directory: # a normal file
+                    mode = 0o777 - original_umask
+                else:
+                    # an empty directory, make them readable/executable
+                    mode = 0o775
+                debug(u"mode=%s" % oct(mode))
+                os.chmod(deunicodise(dst_file), mode)
+            except:
+                raise
+
+            # because we don't upload empty directories,
+            # we can continue the loop here, we won't be setting stat info.
+            # if we do start to upload empty directories, we'll have to reconsider this.
+            if is_empty_directory:
+                continue
+
+            try:
+                if 's3cmd-attrs' in response and cfg.preserve_attrs:
+                    attrs = response['s3cmd-attrs']
+                    if 'mode' in attrs:
+                        os.chmod(deunicodise(dst_file), int(attrs['mode']))
+                    if 'mtime' in attrs or 'atime' in attrs:
+                        mtime = ('mtime' in attrs) and int(attrs['mtime']) or int(time.time())
+                        atime = ('atime' in attrs) and int(attrs['atime']) or int(time.time())
+                        os.utime(deunicodise(dst_file), (atime, mtime))
+                    if 'uid' in attrs and 'gid' in attrs:
+                        uid = int(attrs['uid'])
+                        gid = int(attrs['gid'])
+                        os.lchown(deunicodise(dst_file),uid,gid)
+                elif 'last-modified' in response['headers']:
+                    last_modified = time.mktime(time.strptime(response["headers"]["last-modified"], "%a, %d %b %Y %H:%M:%S GMT"))
+                    os.utime(deunicodise(dst_file), (last_modified, last_modified))
+                    debug("set mtime to %s" % last_modified)
+            except OSError as e:
+                ret = EX_PARTIAL
+                if e.errno == errno.EEXIST:
+                    warning(u"%s exists - not overwriting" % dst_file)
+                    continue
+                if e.errno in (errno.EPERM, errno.EACCES):
+                    warning(u"%s not writable: %s" % (dst_file, e.strerror))
+                    if cfg.stop_on_error:
+                        raise e
+                    continue
+                raise e
+            except KeyboardInterrupt:
+                warning(u"Exiting after keyboard interrupt")
+                return
+            except Exception as e:
+                ret = EX_PARTIAL
+                error(u"%s: %s" % (file, e))
+                if cfg.stop_on_error:
+                    raise OSError(e)
+                continue
+            finally:
+                try:
+                    os.remove(chkptfname_b)
+                except Exception:
+                    pass
+
+            speed_fmt = formatSize(response["speed"], human_readable = True, floating_point = True)
+            if not Config().progress_meter:
+                output(u"download: '%s' -> '%s' (%d bytes in %0.1f seconds, %0.2f %sB/s) %s" %
+                    (uri, dst_file, response["size"], response["elapsed"], speed_fmt[0], speed_fmt[1],
+                    seq_label))
+            total_size += response["size"]
+            if Config().delete_after_fetch:
+                s3.object_delete(uri)
+                output(u"File '%s' removed after syncing" % (uri))
+        return ret, seq, total_size
+
+    size_transferred = 0
+    total_elapsed = 0.0
+    timestamp_start = time.time()
+    dir_cache = {}
+    seq = 0
+    ret, seq, size_transferred = _download(remote_list, seq, remote_count + update_count, size_transferred, dir_cache)
+    status, seq, size_transferred = _download(update_list, seq, remote_count + update_count, size_transferred, dir_cache)
+    if ret == EX_OK:
+        ret = status
+
+    n_copies, size_copies, failed_copy_list = local_copy(copy_pairs, destination_base)
+    _set_local_filename(failed_copy_list, destination_base, source_args)
+    status, seq, size_transferred = _download(failed_copy_list, seq, len(failed_copy_list) + remote_count + update_count, size_transferred, dir_cache)
+    if ret == EX_OK:
+        ret = status
+
+    if cfg.delete_removed and cfg.delete_after:
+        deleted_count, deleted_size = _do_deletes(local_list)
+
+    total_elapsed = max(1.0, time.time() - timestamp_start)
+    speed_fmt = formatSize(size_transferred / total_elapsed, human_readable = True, floating_point = True)
+
+    stats_info.files = orig_remote_count
+    stats_info.size = remote_total_size
+    stats_info.files_transferred = len(failed_copy_list) + remote_count + update_count
+    stats_info.size_transferred = size_transferred
+    stats_info.files_copied = n_copies
+    stats_info.size_copied = size_copies
+    stats_info.files_deleted = deleted_count
+    stats_info.size_deleted = deleted_size
+
+    # Only print out the result if any work has been done or
+    # if the user asked for verbose output
+    outstr = "Done. Downloaded %d bytes in %0.1f seconds, %0.2f %sB/s." % (size_transferred, total_elapsed, speed_fmt[0], speed_fmt[1])
+    if cfg.stats:
+        outstr += stats_info.format_output()
+        output(outstr)
+    elif size_transferred > 0:
+        output(outstr)
+    else:
+        info(outstr)
+
+    return ret
+
+def local_copy(copy_pairs, destination_base):
+    # Do NOT hardlink local files by default, that'd be silly
+    # For instance all empty files would become hardlinked together!
+    saved_bytes = 0
+    failed_copy_list = FileDict()
+    for (src_obj, dst1, relative_file, md5) in copy_pairs:
+        src_file = os.path.join(destination_base, dst1)
+        dst_file = os.path.join(destination_base, relative_file)
+        dst_dir = os.path.dirname(deunicodise(dst_file))
+        try:
+            if not os.path.isdir(deunicodise(dst_dir)):
+                debug("MKDIR %s" % dst_dir)
+                os.makedirs(deunicodise(dst_dir))
+            debug(u"Copying %s to %s" % (src_file, dst_file))
+            shutil.copy2(deunicodise(src_file), deunicodise(dst_file))
+            saved_bytes += src_obj.get(u'size', 0)
+        except (IOError, OSError) as e:
+            warning(u'Unable to copy or hardlink files %s -> %s (Reason: %s)' % (src_file, dst_file, e))
+            failed_copy_list[relative_file] = src_obj
+    return len(copy_pairs), saved_bytes, failed_copy_list
+
+def remote_copy(s3, copy_pairs, destination_base, uploaded_objects_list=None,
+                metadata_update=False):
+    cfg = Config()
+    saved_bytes = 0
+    failed_copy_list = FileDict()
+    seq = 0
+    src_count = len(copy_pairs)
+    for (src_obj, dst1, dst2, src_md5) in copy_pairs:
+        seq += 1
+        debug(u"Remote Copying from %s to %s" % (dst1, dst2))
+        dst1_uri = S3Uri(destination_base + dst1)
+        dst2_uri = S3Uri(destination_base + dst2)
+        src_obj_size = src_obj.get(u'size', 0)
+        seq_label = "[%d of %d]" % (seq, src_count)
+        extra_headers = copy(cfg.extra_headers)
+        if metadata_update:
+            # source is a real local file with its own personal metadata
+            attr_header = _build_attr_header(src_obj, dst2, src_md5)
+            debug(u"attr_header: %s" % attr_header)
+            extra_headers.update(attr_header)
+            extra_headers['content-type'] = \
+                s3.content_type(filename=src_obj['full_name'])
+        try:
+            s3.object_copy(dst1_uri, dst2_uri, extra_headers,
+                           src_size=src_obj_size,
+                           extra_label=seq_label)
+            output(u"remote copy: '%s' -> '%s'  %s" % (dst1, dst2, seq_label))
+            saved_bytes += src_obj_size
+            if uploaded_objects_list is not None:
+                uploaded_objects_list.append(dst2)
+        except Exception:
+            warning(u"Unable to remote copy files '%s' -> '%s'" % (dst1_uri, dst2_uri))
+            failed_copy_list[dst2] = src_obj
+    return (len(copy_pairs), saved_bytes, failed_copy_list)
+
+def _build_attr_header(src_obj, src_relative_name, md5=None):
+    cfg = Config()
+    attrs = {}
+    if cfg.preserve_attrs:
+        for attr in cfg.preserve_attrs_list:
+            val = None
+            if attr == 'uname':
+                try:
+                    val = Utils.urlencode_string(Utils.getpwuid_username(src_obj['uid']), unicode_output=True)
+                except (KeyError, TypeError):
+                    attr = "uid"
+                    val = src_obj.get('uid')
+                    if val:
+                        warning(u"%s: Owner username not known. Storing UID=%d instead." % (src_relative_name, val))
+            elif attr == 'gname':
+                try:
+                    val = Utils.urlencode_string(Utils.getgrgid_grpname(src_obj.get('gid')), unicode_output=True)
+                except (KeyError, TypeError):
+                    attr = "gid"
+                    val = src_obj.get('gid')
+                    if val:
+                        warning(u"%s: Owner groupname not known. Storing GID=%d instead." % (src_relative_name, val))
+            elif attr != "md5":
+                try:
+                    val = getattr(src_obj['sr'], 'st_' + attr)
+                except Exception:
+                    val = None
+            if val is not None:
+                attrs[attr] = val
+
+    if 'md5' in cfg.preserve_attrs_list and md5:
+        attrs['md5'] = md5
+
+    if attrs:
+        attr_str_list = []
+        for k in sorted(attrs.keys()):
+            attr_str_list.append(u"%s:%s" % (k, attrs[k]))
+        attr_header = {'x-amz-meta-s3cmd-attrs': u'/'.join(attr_str_list)}
+    else:
+        attr_header = {}
+
+    return attr_header
+
+def cmd_sync_local2remote(args):
+    cfg = Config()
+    s3 = S3(cfg)
+
+    def _single_process(source_args):
+        for dest in destinations:
+            ## Normalize URI to convert s3://bkt to s3://bkt/ (trailing slash)
+            destination_base_uri = S3Uri(dest)
+            if destination_base_uri.type != 's3':
+                raise ParameterError("Destination must be S3Uri. Got: %s" % destination_base_uri)
+            destination_base = destination_base_uri.uri()
+        return _child(destination_base, source_args)
+
+    def _parent(source_args):
+        # Now that we've done all the disk I/O to look at the local file system and
+        # calculate the md5 for each file, fork for each destination to upload to them separately
+        # and in parallel
+        child_pids = []
+        ret = EX_OK
+
+        for dest in destinations:
+            ## Normalize URI to convert s3://bkt to s3://bkt/ (trailing slash)
+            destination_base_uri = S3Uri(dest)
+            if destination_base_uri.type != 's3':
+                raise ParameterError("Destination must be S3Uri. Got: %s" % destination_base_uri)
+            destination_base = destination_base_uri.uri()
+            child_pid = os.fork()
+            if child_pid == 0:
+                os._exit(_child(destination_base, source_args))
+            else:
+                child_pids.append(child_pid)
+
+        while len(child_pids):
+            (pid, status) = os.wait()
+            child_pids.remove(pid)
+            if ret == EX_OK:
+                ret = os.WEXITSTATUS(status)
+
+        return ret
+
+    def _child(destination_base, source_args):
+        def _set_remote_uri(local_list, destination_base, single_file_local):
+            if len(local_list) > 0:
+                ## Populate 'remote_uri' only if we've got something to upload
+                if not destination_base.endswith("/"):
+                    if not single_file_local:
+                        raise ParameterError("Destination S3 URI must end with '/' (ie must refer to a directory on the remote side).")
+                    local_list[local_list.keys()[0]]['remote_uri'] = destination_base
+                else:
+                    for key in local_list:
+                        local_list[key]['remote_uri'] = destination_base + key
+
+        def _upload(local_list, seq, total, total_size):
+            file_list = local_list.keys()
+            file_list.sort()
+            ret = EX_OK
+            for file in file_list:
+                seq += 1
+                item = local_list[file]
+                src = item['full_name']
+                try:
+                    src_md5 = local_list.get_md5(file)
+                except IOError:
+                    src_md5 = None
+                uri = S3Uri(item['remote_uri'])
+                seq_label = "[%d of %d]" % (seq, total)
+                extra_headers = copy(cfg.extra_headers)
+                try:
+                    attr_header = _build_attr_header(local_list[file],
+                                                     file, src_md5)
+                    debug(u"attr_header: %s" % attr_header)
+                    extra_headers.update(attr_header)
+                    response = s3.object_put(src, uri, extra_headers, extra_label = seq_label)
+                except S3UploadError as exc:
+                    error(u"Upload of '%s' failed too many times (Last reason: %s)" % (item['full_name'], exc))
+                    if cfg.stop_on_error:
+                        ret = EX_DATAERR
+                        error(u"Exiting now because of --stop-on-error")
+                        raise
+                    ret = EX_PARTIAL
+                    continue
+                except InvalidFileError as exc:
+                    error(u"Upload of '%s' is not possible (Reason: %s)" % (item['full_name'], exc))
+                    if cfg.stop_on_error:
+                        ret = EX_OSFILE
+                        error(u"Exiting now because of --stop-on-error")
+                        raise
+                    ret = EX_PARTIAL
+                    continue
+                speed_fmt = formatSize(response["speed"], human_readable = True, floating_point = True)
+                if not cfg.progress_meter:
+                    output(u"upload: '%s' -> '%s' (%d bytes in %0.1f seconds, %0.2f %sB/s) %s" %
+                        (item['full_name'], uri, response["size"], response["elapsed"],
+                        speed_fmt[0], speed_fmt[1], seq_label))
+                total_size += response["size"]
+                uploaded_objects_list.append(uri.object())
+            return ret, seq, total_size
+
+
+        stats_info = StatsInfo()
+
+        local_list, single_file_local, src_exclude_list, local_total_size = fetch_local_list(args[:-1], is_src = True, recursive = True)
+
+        # - The source path is either like "/myPath/my_src_folder" and
+        # the user want to upload this single folder and optionally only delete
+        # things that have been removed inside this folder. For this case,
+        # we only have to look inside destination_base/my_src_folder and not at
+        # the root of destination_base.
+        # - Or like "/myPath/my_src_folder/" and the user want to have the sync
+        # with the content of this folder
+        # Special case, "." for current folder.
+        destbase_with_source_list = set()
+        for source_arg in source_args:
+            if not source_arg.endswith('/') and os.path.basename(source_arg) != '.' \
+               and not single_file_local:
+                destbase_with_source_list.add(os.path.join(destination_base,
+                                                    os.path.basename(source_arg)))
+            else:
+                destbase_with_source_list.add(destination_base)
+
+        remote_list, dst_exclude_list, remote_total_size = fetch_remote_list(destbase_with_source_list, recursive = True, require_attribs = True)
+
+        local_count = len(local_list)
+        orig_local_count = local_count
+        remote_count = len(remote_list)
+
+        info(u"Found %d local files, %d remote files" % (local_count, remote_count))
+
+        if single_file_local and len(local_list) == 1 and len(remote_list) == 1:
+            ## Make remote_key same as local_key for comparison if we're dealing with only one file
+            remote_list_entry = remote_list[remote_list.keys()[0]]
+            # Flush remote_list, by the way
+            remote_list = FileDict()
+            remote_list[local_list.keys()[0]] =  remote_list_entry
+
+        local_list, remote_list, update_list, copy_pairs = compare_filelists(local_list, remote_list, src_remote = False, dst_remote = True)
+
+        local_count = len(local_list)
+        update_count = len(update_list)
+        copy_count = len(copy_pairs)
+        remote_count = len(remote_list)
+        upload_count = local_count + update_count
+
+        info(u"Summary: %d local files to upload, %d files to remote copy, %d remote files to delete" % (upload_count, copy_count, remote_count))
+
+        _set_remote_uri(local_list, destination_base, single_file_local)
+        _set_remote_uri(update_list, destination_base, single_file_local)
+
+        if cfg.dry_run:
+            keys = filedicts_to_keys(src_exclude_list, dst_exclude_list)
+            for key in keys:
+                output(u"exclude: %s" % key)
+            for key in local_list:
+                output(u"upload: '%s' -> '%s'" % (local_list[key]['full_name'], local_list[key]['remote_uri']))
+            for key in update_list:
+                output(u"upload: '%s' -> '%s'" % (update_list[key]['full_name'], update_list[key]['remote_uri']))
+            for (src_obj, dst1, dst2, md5) in copy_pairs:
+                output(u"remote copy: '%s' -> '%s'" % (dst1, dst2))
+            if cfg.delete_removed:
+                for key in remote_list:
+                    output(u"delete: '%s'" % remote_list[key]['object_uri_str'])
+
+            warning(u"Exiting now because of --dry-run")
+            return EX_OK
+
+        # if there are copy pairs, we can't do delete_before, on the chance
+        # we need one of the to-be-deleted files as a copy source.
+        if len(copy_pairs) > 0:
+            cfg.delete_after = True
+
+        if cfg.delete_removed and orig_local_count == 0 and len(remote_list) and not cfg.force:
+            warning(u"delete: cowardly refusing to delete because no source files were found.  Use --force to override.")
+            cfg.delete_removed = False
+
+        if cfg.delete_removed and not cfg.delete_after and remote_list:
+            subcmd_batch_del(remote_list = remote_list)
+
+        size_transferred = 0
+        total_elapsed = 0.0
+        timestamp_start = time.time()
+        ret, n, size_transferred = _upload(local_list, 0, upload_count, size_transferred)
+        status, n, size_transferred = _upload(update_list, n, upload_count, size_transferred)
+        if ret == EX_OK:
+            ret = status
+        # uploaded_objects_list reference is passed so it can be filled with
+        # destination object of succcessful copies so that they can be
+        # invalidated by cf
+        n_copies, saved_bytes, failed_copy_files  = remote_copy(
+            s3, copy_pairs, destination_base, uploaded_objects_list, True)
+
+        #upload file that could not be copied
+        debug("Process files that were not remotely copied")
+        failed_copy_count = len(failed_copy_files)
+        _set_remote_uri(failed_copy_files, destination_base, single_file_local)
+        status, n, size_transferred = _upload(failed_copy_files, n, upload_count + failed_copy_count, size_transferred)
+        if ret == EX_OK:
+            ret = status
+
+        if cfg.delete_removed and cfg.delete_after and remote_list:
+            subcmd_batch_del(remote_list = remote_list)
+        total_elapsed = max(1.0, time.time() - timestamp_start)
+        total_speed = total_elapsed and size_transferred / total_elapsed or 0.0
+        speed_fmt = formatSize(total_speed, human_readable = True, floating_point = True)
+
+
+        stats_info.files = orig_local_count
+        stats_info.size = local_total_size
+        stats_info.files_transferred = upload_count + failed_copy_count
+        stats_info.size_transferred = size_transferred
+        stats_info.files_copied = n_copies
+        stats_info.size_copied = saved_bytes
+        stats_info.files_deleted = remote_count
+
+
+        # Only print out the result if any work has been done or
+        # if the user asked for verbose output
+        outstr = "Done. Uploaded %d bytes in %0.1f seconds, %0.2f %sB/s." % (size_transferred, total_elapsed, speed_fmt[0], speed_fmt[1])
+        if cfg.stats:
+            outstr += stats_info.format_output()
+            output(outstr)
+        elif size_transferred + saved_bytes > 0:
+            output(outstr)
+        else:
+            info(outstr)
+
+        return ret
+
+    def _invalidate_on_cf(destination_base_uri):
+        cf = CloudFront(cfg)
+        default_index_file = None
+        if cfg.invalidate_default_index_on_cf or cfg.invalidate_default_index_root_on_cf:
+            info_response = s3.website_info(destination_base_uri, cfg.bucket_location)
+            if info_response:
+              default_index_file = info_response['index_document']
+              if len(default_index_file) < 1:
+                  default_index_file = None
+
+        results = cf.InvalidateObjects(destination_base_uri, uploaded_objects_list, default_index_file, cfg.invalidate_default_index_on_cf, cfg.invalidate_default_index_root_on_cf)
+        for result in results:
+            if result['status'] == 201:
+                output(u"Created invalidation request for %d paths" % len(uploaded_objects_list))
+                output(u"Check progress with: s3cmd cfinvalinfo cf://%s/%s" % (result['dist_id'], result['request_id']))
+
+    # main execution
+    uploaded_objects_list = []
+
+    if cfg.encrypt:
+        error(u"S3cmd 'sync' doesn't yet support GPG encryption, sorry.")
+        error(u"Either use unconditional 's3cmd put --recursive'")
+        error(u"or disable encryption with --no-encrypt parameter.")
+        sys.exit(EX_USAGE)
+
+    for arg in args[:-1]:
+        if not os.path.exists(deunicodise(arg)):
+            raise ParameterError("Invalid source: '%s' is not an existing file or directory" % arg)
+
+    destinations = [args[-1]]
+    if cfg.additional_destinations:
+        destinations = destinations + cfg.additional_destinations
+
+    if 'fork' not in os.__all__ or len(destinations) < 2:
+        ret = _single_process(args[:-1])
+        destination_base_uri = S3Uri(destinations[-1])
+        if cfg.invalidate_on_cf:
+            if len(uploaded_objects_list) == 0:
+                info("Nothing to invalidate in CloudFront")
+            else:
+                _invalidate_on_cf(destination_base_uri)
+    else:
+        ret = _parent(args[:-1])
+        if cfg.invalidate_on_cf:
+            error(u"You cannot use both --cf-invalidate and --add-destination.")
+            return(EX_USAGE)
+
+    return ret
+
+def cmd_sync(args):
+    cfg = Config()
+    if (len(args) < 2):
+        syntax_msg = ''
+        commands_list = get_commands_list()
+        for cmd in commands_list:
+            if cmd.get('cmd') == 'sync':
+                syntax_msg = cmd.get('param', '')
+                break
+        raise ParameterError("Too few parameters! Expected: %s" % syntax_msg)
+    if cfg.delay_updates:
+        warning(u"`delay-updates` is obsolete.")
+
+    for arg in args:
+        if arg == u'-':
+            raise ParameterError("Stdin or stdout ('-') can't be used for a source or a destination with the sync command.")
+
+    if S3Uri(args[0]).type == "file" and S3Uri(args[-1]).type == "s3":
+        return cmd_sync_local2remote(args)
+    if S3Uri(args[0]).type == "s3" and S3Uri(args[-1]).type == "file":
+        return cmd_sync_remote2local(args)
+    if S3Uri(args[0]).type == "s3" and S3Uri(args[-1]).type == "s3":
+        return cmd_sync_remote2remote(args)
+    raise ParameterError("Invalid source/destination: '%s'" % "' '".join(args))
+
+def cmd_setacl(args):
+    cfg = Config()
+    s3 = S3(cfg)
+
+    set_to_acl = cfg.acl_public and "Public" or "Private"
+
+    if not cfg.recursive:
+        old_args = args
+        args = []
+        for arg in old_args:
+            uri = S3Uri(arg)
+            if not uri.has_object():
+                if cfg.acl_public != None:
+                    info("Setting bucket-level ACL for %s to %s" % (uri.uri(), set_to_acl))
+                else:
+                    info("Setting bucket-level ACL for %s" % (uri.uri()))
+                if not cfg.dry_run:
+                    update_acl(s3, uri)
+            else:
+                args.append(arg)
+
+    remote_list, exclude_list, _ = fetch_remote_list(args)
+
+    remote_count = len(remote_list)
+
+    info(u"Summary: %d remote files to update" % remote_count)
+
+    if cfg.dry_run:
+        for key in exclude_list:
+            output(u"exclude: %s" % key)
+        for key in remote_list:
+            output(u"setacl: '%s'" % remote_list[key]['object_uri_str'])
+
+        warning(u"Exiting now because of --dry-run")
+        return EX_OK
+
+    seq = 0
+    for key in remote_list:
+        seq += 1
+        seq_label = "[%d of %d]" % (seq, remote_count)
+        uri = S3Uri(remote_list[key]['object_uri_str'])
+        update_acl(s3, uri, seq_label)
+    return EX_OK
+
+def cmd_setpolicy(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    uri = S3Uri(args[1])
+    policy_file = args[0]
+
+    with open(deunicodise(policy_file), 'r') as fp:
+        policy = fp.read()
+
+    if cfg.dry_run:
+        return EX_OK
+
+    response = s3.set_policy(uri, policy)
+
+    #if retsponse['status'] == 200:
+    debug(u"response - %s" % response['status'])
+    if response['status'] == 204:
+        output(u"%s: Policy updated" % uri)
+    return EX_OK
+
+def cmd_delpolicy(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    uri = S3Uri(args[0])
+    if cfg.dry_run: return EX_OK
+
+    response = s3.delete_policy(uri)
+
+    #if retsponse['status'] == 200:
+    debug(u"response - %s" % response['status'])
+    output(u"%s: Policy deleted" % uri)
+    return EX_OK
+
+def cmd_setcors(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    uri = S3Uri(args[1])
+    cors_file = args[0]
+
+    with open(deunicodise(cors_file), 'r') as fp:
+        cors = fp.read()
+
+    if cfg.dry_run:
+        return EX_OK
+
+    response = s3.set_cors(uri, cors)
+
+    #if retsponse['status'] == 200:
+    debug(u"response - %s" % response['status'])
+    if response['status'] == 204:
+        output(u"%s: CORS updated" % uri)
+    return EX_OK
+
+def cmd_delcors(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    uri = S3Uri(args[0])
+    if cfg.dry_run: return EX_OK
+
+    response = s3.delete_cors(uri)
+
+    #if retsponse['status'] == 200:
+    debug(u"response - %s" % response['status'])
+    output(u"%s: CORS deleted" % uri)
+    return EX_OK
+
+def cmd_set_payer(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    uri = S3Uri(args[0])
+
+    if cfg.dry_run: return EX_OK
+
+    response = s3.set_payer(uri)
+    if response['status'] == 200:
+        output(u"%s: Payer updated" % uri)
+        return EX_OK
+    else:
+        output(u"%s: Payer NOT updated" % uri)
+        return EX_CONFLICT
+
+def cmd_setlifecycle(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    uri = S3Uri(args[1])
+    lifecycle_policy_file = args[0]
+
+    with open(deunicodise(lifecycle_policy_file), 'r') as fp:
+        lifecycle_policy = fp.read()
+
+    if cfg.dry_run:
+        return EX_OK
+
+    response = s3.set_lifecycle_policy(uri, lifecycle_policy)
+
+    debug(u"response - %s" % response['status'])
+    if response['status'] == 200:
+        output(u"%s: Lifecycle Policy updated" % uri)
+    return EX_OK
+
+def cmd_getlifecycle(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    uri = S3Uri(args[0])
+
+    response = s3.get_lifecycle_policy(uri)
+
+    output(u"%s" % getPrettyFromXml(response['data']))
+    return EX_OK
+
+def cmd_dellifecycle(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    uri = S3Uri(args[0])
+    if cfg.dry_run: return EX_OK
+
+    response = s3.delete_lifecycle_policy(uri)
+
+    debug(u"response - %s" % response['status'])
+    output(u"%s: Lifecycle Policy deleted" % uri)
+    return EX_OK
+
+def cmd_setnotification(args):
+    s3 = S3(Config())
+    uri = S3Uri(args[1])
+    notification_policy_file = args[0]
+
+    with open(deunicodise(notification_policy_file), 'r') as fp:
+        notification_policy = fp.read()
+
+    response = s3.set_notification_policy(uri, notification_policy)
+
+    debug(u"response - %s" % response['status'])
+    if response['status'] == 200:
+        output(u"%s: Notification Policy updated" % uri)
+    return EX_OK
+
+def cmd_getnotification(args):
+    s3 = S3(Config())
+    uri = S3Uri(args[0])
+
+    response = s3.get_notification_policy(uri)
+
+    output(getPrettyFromXml(response['data']))
+    return EX_OK
+
+def cmd_delnotification(args):
+    s3 = S3(Config())
+    uri = S3Uri(args[0])
+
+    response = s3.delete_notification_policy(uri)
+
+    debug(u"response - %s" % response['status'])
+    output(u"%s: Notification Policy deleted" % uri)
+    return EX_OK
+
+def cmd_multipart(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    uri = S3Uri(args[0])
+
+    #id = ''
+    #if(len(args) > 1): id = args[1]
+
+    upload_list = s3.get_multipart(uri)
+    output(u"%s" % uri)
+    debug(upload_list)
+    output(u"Initiated\tPath\tId")
+    for mpupload in upload_list:
+        try:
+            output(u"%s\t%s\t%s" % (
+                mpupload['Initiated'],
+                "s3://" + uri.bucket() + "/" + mpupload['Key'],
+                mpupload['UploadId']))
+        except KeyError:
+            pass
+    return EX_OK
+
+def cmd_abort_multipart(args):
+    '''{"cmd":"abortmp",   "label":"abort a multipart upload", "param":"s3://BUCKET Id", "func":cmd_abort_multipart, "argc":2},'''
+    cfg = Config()
+    s3 = S3(cfg)
+    uri = S3Uri(args[0])
+    id = args[1]
+    response = s3.abort_multipart(uri, id)
+    debug(u"response - %s" % response['status'])
+    output(u"%s" % uri)
+    return EX_OK
+
+def cmd_list_multipart(args):
+    '''{"cmd":"abortmp",   "label":"list a multipart upload", "param":"s3://BUCKET Id", "func":cmd_list_multipart, "argc":2},'''
+    cfg = Config()
+    s3 = S3(cfg)
+    uri = S3Uri(args[0])
+    id = args[1]
+
+    part_list = s3.list_multipart(uri, id)
+    output(u"LastModified\t\t\tPartNumber\tETag\tSize")
+    for mpupload in part_list:
+        try:
+            output(u"%s\t%s\t%s\t%s" % (mpupload['LastModified'],
+                                        mpupload['PartNumber'],
+                                        mpupload['ETag'],
+                                        mpupload['Size']))
+        except KeyError:
+            pass
+    return EX_OK
+
+def cmd_accesslog(args):
+    cfg = Config()
+    s3 = S3(cfg)
+    bucket_uri = S3Uri(args.pop())
+    if bucket_uri.object():
+        raise ParameterError("Only bucket name is required for [accesslog] command")
+    if cfg.log_target_prefix == False:
+        accesslog, response = s3.set_accesslog(bucket_uri, enable = False)
+    elif cfg.log_target_prefix:
+        log_target_prefix_uri = S3Uri(cfg.log_target_prefix)
+        if log_target_prefix_uri.type != "s3":
+            raise ParameterError("--log-target-prefix must be a S3 URI")
+        accesslog, response = s3.set_accesslog(bucket_uri, enable = True, log_target_prefix_uri = log_target_prefix_uri, acl_public = cfg.acl_public)
+    else:   # cfg.log_target_prefix == None
+        accesslog = s3.get_accesslog(bucket_uri)
+
+    output(u"Access logging for: %s" % bucket_uri.uri())
+    output(u"   Logging Enabled: %s" % accesslog.isLoggingEnabled())
+    if accesslog.isLoggingEnabled():
+        output(u"     Target prefix: %s" % accesslog.targetPrefix().uri())
+        #output(u"   Public Access:   %s" % accesslog.isAclPublic())
+    return EX_OK
+
+def cmd_sign(args):
+    string_to_sign = args.pop()
+    debug(u"string-to-sign: %r" % string_to_sign)
+    signature = Crypto.sign_string_v2(encode_to_s3(string_to_sign))
+    output(u"Signature: %s" % decode_from_s3(signature))
+    return EX_OK
+
+def cmd_signurl(args):
+    expiry = args.pop()
+    url_to_sign = S3Uri(args.pop())
+    if url_to_sign.type != 's3':
+        raise ParameterError("Must be S3Uri. Got: %s" % url_to_sign)
+    debug("url to sign: %r" % url_to_sign)
+    signed_url = Crypto.sign_url_v2(url_to_sign, expiry)
+    output(signed_url)
+    return EX_OK
+
+def cmd_fixbucket(args):
+    def _unescape(text):
+        ##
+        # Removes HTML or XML character references and entities from a text string.
+        #
+        # @param text The HTML (or XML) source text.
+        # @return The plain text, as a Unicode string, if necessary.
+        #
+        # From: http://effbot.org/zone/re-sub.htm#unescape-html
+        def _unescape_fixup(m):
+            text = m.group(0)
+            if not 'apos' in htmlentitydefs.name2codepoint:
+                htmlentitydefs.name2codepoint['apos'] = ord("'")
+            if text[:2] == "&#":
+                # character reference
+                try:
+                    if text[:3] == "&#x":
+                        return unichr(int(text[3:-1], 16))
+                    else:
+                        return unichr(int(text[2:-1]))
+                except ValueError:
+                    pass
+            else:
+                # named entity
+                try:
+                    text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
+                except KeyError:
+                    pass
+            return text # leave as is
+            text = text.encode('ascii', 'xmlcharrefreplace')
+        return re.sub(r"&#?\w+;", _unescape_fixup, text)
+
+    cfg = Config()
+    cfg.urlencoding_mode = "fixbucket"
+    s3 = S3(cfg)
+
+    count = 0
+    for arg in args:
+        culprit = S3Uri(arg)
+        if culprit.type != "s3":
+            raise ParameterError("Expecting S3Uri instead of: %s" % arg)
+        response = s3.bucket_list_noparse(culprit.bucket(), culprit.object(), recursive = True)
+        r_xent = re.compile(r"&#x[\da-fA-F]+;")
+        data = decode_from_s3(response['data'])
+        keys = re.findall("<Key>(.*?)</Key>", data, re.MULTILINE | re.UNICODE)
+        debug("Keys: %r" % keys)
+        for key in keys:
+            if r_xent.search(key):
+                info("Fixing: %s" % key)
+                debug("Step 1: Transforming %s" % key)
+                key_bin = _unescape(key)
+                debug("Step 2:       ... to %s" % key_bin)
+                key_new = replace_nonprintables(key_bin)
+                debug("Step 3:  ... then to %s" % key_new)
+                src = S3Uri("s3://%s/%s" % (culprit.bucket(), key_bin))
+                dst = S3Uri("s3://%s/%s" % (culprit.bucket(), key_new))
+                if cfg.dry_run:
+                    output(u"[--dry-run] File %r would be renamed to %s" % (key_bin, key_new))
+                    continue
+                try:
+                    resp_move = s3.object_move(src, dst)
+                    if resp_move['status'] == 200:
+                        output(u"File '%r' renamed to '%s'" % (key_bin, key_new))
+                        count += 1
+                    else:
+                        error(u"Something went wrong for: %r" % key)
+                        error(u"Please report the problem to s3tools-bugs@lists.sourceforge.net")
+                except S3Error:
+                    error(u"Something went wrong for: %r" % key)
+                    error(u"Please report the problem to s3tools-bugs@lists.sourceforge.net")
+
+    if count > 0:
+        warning(u"Fixed %d files' names. Their ACL were reset to Private." % count)
+        warning(u"Use 's3cmd setacl --acl-public s3://...' to make")
+        warning(u"them publicly readable if required.")
+    return EX_OK
+
+def resolve_list(lst, args):
+    retval = []
+    for item in lst:
+        retval.append(item % args)
+    return retval
+
+def gpg_command(command, passphrase = ""):
+    debug(u"GPG command: " + " ".join(command))
+    command = [deunicodise(cmd_entry) for cmd_entry in command]
+    p = subprocess.Popen(command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.STDOUT,
+                         close_fds = True)
+    p_stdout, p_stderr = p.communicate(deunicodise(passphrase + "\n"))
+    debug(u"GPG output:")
+    for line in unicodise(p_stdout).split("\n"):
+        debug(u"GPG: " + line)
+    p_exitcode = p.wait()
+    return p_exitcode
+
+def gpg_encrypt(filename):
+    cfg = Config()
+    tmp_filename = Utils.mktmpfile()
+    args = {
+        "gpg_command" : cfg.gpg_command,
+        "passphrase_fd" : "0",
+        "input_file" : filename,
+        "output_file" : tmp_filename,
+    }
+    info(u"Encrypting file %s to %s..." % (filename, tmp_filename))
+    command = resolve_list(cfg.gpg_encrypt.split(" "), args)
+    code = gpg_command(command, cfg.gpg_passphrase)
+    return (code, tmp_filename, "gpg")
+
+def gpg_decrypt(filename, gpgenc_header = "", in_place = True):
+    cfg = Config()
+    tmp_filename = Utils.mktmpfile(filename)
+    args = {
+        "gpg_command" : cfg.gpg_command,
+        "passphrase_fd" : "0",
+        "input_file" : filename,
+        "output_file" : tmp_filename,
+    }
+    info(u"Decrypting file %s to %s..." % (filename, tmp_filename))
+    command = resolve_list(cfg.gpg_decrypt.split(" "), args)
+    code = gpg_command(command, cfg.gpg_passphrase)
+    if code == 0 and in_place:
+        debug(u"Renaming %s to %s" % (tmp_filename, filename))
+        os.unlink(deunicodise(filename))
+        os.rename(deunicodise(tmp_filename), deunicodise(filename))
+        tmp_filename = filename
+    return (code, tmp_filename)
+
+def run_configure(config_file, args):
+    cfg = Config()
+    options = [
+        ("access_key", "Access Key", "Access key and Secret key are your identifiers for Amazon S3. Leave them empty for using the env variables."),
+        ("secret_key", "Secret Key"),
+        ("bucket_location", "Default Region"),
+        ("host_base", "S3 Endpoint", "Use \"s3.amazonaws.com\" for S3 Endpoint and not modify it to the target Amazon S3."),
+        ("host_bucket", "DNS-style bucket+hostname:port template for accessing a bucket", "Use \"%(bucket)s.s3.amazonaws.com\" to the target Amazon S3. \"%(bucket)s\" and \"%(location)s\" vars can be used\nif the target S3 system supports dns based buckets."),
+        ("gpg_passphrase", "Encryption password", "Encryption password is used to protect your files from reading\nby unauthorized persons while in transfer to S3"),
+        ("gpg_command", "Path to GPG program"),
+        ("use_https", "Use HTTPS protocol", "When using secure HTTPS protocol all communication with Amazon S3\nservers is protected from 3rd party eavesdropping. This method is\nslower than plain HTTP, and can only be proxied with Python 2.7 or newer"),
+        ("proxy_host", "HTTP Proxy server name", "On some networks all internet access must go through a HTTP proxy.\nTry setting it here if you can't connect to S3 directly"),
+        ("proxy_port", "HTTP Proxy server port"),
+        ]
+    ## Option-specfic defaults
+    if getattr(cfg, "gpg_command") == "":
+        setattr(cfg, "gpg_command", which("gpg"))
+
+    if getattr(cfg, "proxy_host") == "" and os.getenv("http_proxy"):
+        autodetected_encoding = locale.getpreferredencoding() or "UTF-8"
+        re_match=re.match(r"(http://)?([^:]+):(\d+)",
+                          unicodise_s(os.getenv("http_proxy"), autodetected_encoding))
+        if re_match:
+            setattr(cfg, "proxy_host", re_match.groups()[1])
+            setattr(cfg, "proxy_port", re_match.groups()[2])
+
+    try:
+        # Support for python3
+        # raw_input only exists in py2 and was renamed to input in py3
+        global input
+        input = raw_input
+    except NameError:
+        pass
+
+    try:
+        while True:
+            output(u"\nEnter new values or accept defaults in brackets with Enter.")
+            output(u"Refer to user manual for detailed description of all options.")
+            for option in options:
+                prompt = option[1]
+                ## Option-specific handling
+                if option[0] == 'proxy_host' and getattr(cfg, 'use_https') == True and sys.hexversion < 0x02070000:
+                    setattr(cfg, option[0], "")
+                    continue
+                if option[0] == 'proxy_port' and getattr(cfg, 'proxy_host') == "":
+                    setattr(cfg, option[0], 0)
+                    continue
+
+                try:
+                    val = getattr(cfg, option[0])
+                    if type(val) is bool:
+                        val = val and "Yes" or "No"
+                    if val not in (None, ""):
+                        prompt += " [%s]" % val
+                except AttributeError:
+                    pass
+
+                if len(option) >= 3:
+                    output(u"\n%s" % option[2])
+
+                val = unicodise_s(input(prompt + ": "))
+                if val != "":
+                    if type(getattr(cfg, option[0])) is bool:
+                        # Turn 'Yes' into True, everything else into False
+                        val = val.lower().startswith('y')
+                    setattr(cfg, option[0], val)
+            output(u"\nNew settings:")
+            for option in options:
+                output(u"  %s: %s" % (option[1], getattr(cfg, option[0])))
+            val = input("\nTest access with supplied credentials? [Y/n] ")
+            if val.lower().startswith("y") or val == "":
+                try:
+                    # Default, we try to list 'all' buckets which requires
+                    # ListAllMyBuckets permission
+                    if len(args) == 0:
+                        output(u"Please wait, attempting to list all buckets...")
+                        S3(Config()).bucket_list("", "")
+                    else:
+                        # If user specified a bucket name directly, we check it and only it.
+                        # Thus, access check can succeed even if user only has access to
+                        # to a single bucket and not ListAllMyBuckets permission.
+                        output(u"Please wait, attempting to list bucket: " + args[0])
+                        uri = S3Uri(args[0])
+                        if uri.type == "s3" and uri.has_bucket():
+                            S3(Config()).bucket_list(uri.bucket(), "")
+                        else:
+                            raise Exception(u"Invalid bucket uri: " + args[0])
+
+                    output(u"Success. Your access key and secret key worked fine :-)")
+
+                    output(u"\nNow verifying that encryption works...")
+                    if not getattr(cfg, "gpg_command") or not getattr(cfg, "gpg_passphrase"):
+                        output(u"Not configured. Never mind.")
+                    else:
+                        if not getattr(cfg, "gpg_command"):
+                            raise Exception("Path to GPG program not set")
+                        if not os.path.isfile(deunicodise(getattr(cfg, "gpg_command"))):
+                            raise Exception("GPG program not found")
+                        filename = Utils.mktmpfile()
+                        with open(deunicodise(filename), "w") as fp:
+                            fp.write(os.sys.copyright)
+                        ret_enc = gpg_encrypt(filename)
+                        ret_dec = gpg_decrypt(ret_enc[1], ret_enc[2], False)
+                        hash = [
+                            Utils.hash_file_md5(filename),
+                            Utils.hash_file_md5(ret_enc[1]),
+                            Utils.hash_file_md5(ret_dec[1]),
+                        ]
+                        os.unlink(deunicodise(filename))
+                        os.unlink(deunicodise(ret_enc[1]))
+                        os.unlink(deunicodise(ret_dec[1]))
+                        if hash[0] == hash[2] and hash[0] != hash[1]:
+                            output(u"Success. Encryption and decryption worked fine :-)")
+                        else:
+                            raise Exception("Encryption verification error.")
+
+                except S3Error as e:
+                    error(u"Test failed: %s" % (e))
+                    if e.code == "AccessDenied":
+                        error(u"Are you sure your keys have s3:ListAllMyBuckets permissions?")
+                    val = input("\nRetry configuration? [Y/n] ")
+                    if val.lower().startswith("y") or val == "":
+                        continue
+                except Exception as e:
+                    error(u"Test failed: %s" % (e))
+                    val = input("\nRetry configuration? [Y/n] ")
+                    if val.lower().startswith("y") or val == "":
+                        continue
+
+
+            val = input("\nSave settings? [y/N] ")
+            if val.lower().startswith("y"):
+                break
+            val = input("Retry configuration? [Y/n] ")
+            if val.lower().startswith("n"):
+                raise EOFError()
+
+        ## Overwrite existing config file, make it user-readable only
+        old_mask = os.umask(0o077)
+        try:
+            os.remove(deunicodise(config_file))
+        except OSError as e:
+            if e.errno != errno.ENOENT:
+                raise
+        try:
+            with io.open(deunicodise(config_file), "w", encoding=cfg.encoding) as fp:
+                cfg.dump_config(fp)
+        finally:
+            os.umask(old_mask)
+        output(u"Configuration saved to '%s'" % config_file)
+
+    except (EOFError, KeyboardInterrupt):
+        output(u"\nConfiguration aborted. Changes were NOT saved.")
+        return
+
+    except IOError as e:
+        error(u"Writing config file failed: %s: %s" % (config_file, e.strerror))
+        sys.exit(EX_IOERR)
+
+def process_patterns_from_file(fname, patterns_list):
+    try:
+        with open(deunicodise(fname), "rt") as fn:
+            for pattern in fn:
+                pattern = unicodise(pattern).strip()
+                if re.match("^#", pattern) or re.match(r"^\s*$", pattern):
+                    continue
+                debug(u"%s: adding rule: %s" % (fname, pattern))
+                patterns_list.append(pattern)
+    except IOError as e:
+        error(e)
+        sys.exit(EX_IOERR)
+
+    return patterns_list
+
+def process_patterns(patterns_list, patterns_from, is_glob, option_txt = ""):
+    r"""
+    process_patterns(patterns, patterns_from, is_glob, option_txt = "")
+    Process --exclude / --include GLOB and REGEXP patterns.
+    'option_txt' is 'exclude' / 'include' / 'rexclude' / 'rinclude'
+    Returns: patterns_compiled, patterns_text
+    Note: process_patterns_from_file will ignore lines starting with # as these
+    are comments. To target escape the initial #, to use it in a file name, one
+    can use: "[#]" (for exclude) or "\#" (for rexclude).
+    """
+
+    patterns_compiled = []
+    patterns_textual = {}
+
+    if patterns_list is None:
+        patterns_list = []
+
+    if patterns_from:
+        ## Append patterns from glob_from
+        for fname in patterns_from:
+            debug(u"processing --%s-from %s" % (option_txt, fname))
+            patterns_list = process_patterns_from_file(fname, patterns_list)
+
+    for pattern in patterns_list:
+        debug(u"processing %s rule: %s" % (option_txt, patterns_list))
+        if is_glob:
+            pattern = glob.fnmatch.translate(pattern)
+        r = re.compile(pattern)
+        patterns_compiled.append(r)
+        patterns_textual[r] = pattern
+
+    return patterns_compiled, patterns_textual
+
+def get_commands_list():
+    return [
+    {"cmd":"mb", "label":"Make bucket", "param":"s3://BUCKET", "func":cmd_bucket_create, "argc":1},
+    {"cmd":"rb", "label":"Remove bucket", "param":"s3://BUCKET", "func":cmd_bucket_delete, "argc":1},
+    {"cmd":"ls", "label":"List objects or buckets", "param":"[s3://BUCKET[/PREFIX]]", "func":cmd_ls, "argc":0},
+    {"cmd":"la", "label":"List all object in all buckets", "param":"", "func":cmd_all_buckets_list_all_content, "argc":0},
+    {"cmd":"put", "label":"Put file into bucket", "param":"FILE [FILE...] s3://BUCKET[/PREFIX]", "func":cmd_object_put, "argc":2},
+    {"cmd":"get", "label":"Get file from bucket", "param":"s3://BUCKET/OBJECT LOCAL_FILE", "func":cmd_object_get, "argc":1},
+    {"cmd":"del", "label":"Delete file from bucket", "param":"s3://BUCKET/OBJECT", "func":cmd_object_del, "argc":1},
+    {"cmd":"rm", "label":"Delete file from bucket (alias for del)", "param":"s3://BUCKET/OBJECT", "func":cmd_object_del, "argc":1},
+    #{"cmd":"mkdir", "label":"Make a virtual S3 directory", "param":"s3://BUCKET/path/to/dir", "func":cmd_mkdir, "argc":1},
+    {"cmd":"restore", "label":"Restore file from Glacier storage", "param":"s3://BUCKET/OBJECT", "func":cmd_object_restore, "argc":1},
+    {"cmd":"sync", "label":"Synchronize a directory tree to S3 (checks files freshness using size and md5 checksum, unless overridden by options, see below)", "param":"LOCAL_DIR s3://BUCKET[/PREFIX] or s3://BUCKET[/PREFIX] LOCAL_DIR or s3://BUCKET[/PREFIX] s3://BUCKET[/PREFIX]", "func":cmd_sync, "argc":2},
+    {"cmd":"du", "label":"Disk usage by buckets", "param":"[s3://BUCKET[/PREFIX]]", "func":cmd_du, "argc":0},
+    {"cmd":"info", "label":"Get various information about Buckets or Files", "param":"s3://BUCKET[/OBJECT]", "func":cmd_info, "argc":1},
+    {"cmd":"cp", "label":"Copy object", "param":"s3://BUCKET1/OBJECT1 s3://BUCKET2[/OBJECT2]", "func":cmd_cp, "argc":2},
+    {"cmd":"modify", "label":"Modify object metadata", "param":"s3://BUCKET1/OBJECT", "func":cmd_modify, "argc":1},
+    {"cmd":"mv", "label":"Move object", "param":"s3://BUCKET1/OBJECT1 s3://BUCKET2[/OBJECT2]", "func":cmd_mv, "argc":2},
+    {"cmd":"setacl", "label":"Modify Access control list for Bucket or Files", "param":"s3://BUCKET[/OBJECT]", "func":cmd_setacl, "argc":1},
+
+    {"cmd":"setpolicy", "label":"Modify Bucket Policy", "param":"FILE s3://BUCKET", "func":cmd_setpolicy, "argc":2},
+    {"cmd":"delpolicy", "label":"Delete Bucket Policy", "param":"s3://BUCKET", "func":cmd_delpolicy, "argc":1},
+    {"cmd":"setcors", "label":"Modify Bucket CORS", "param":"FILE s3://BUCKET", "func":cmd_setcors, "argc":2},
+    {"cmd":"delcors", "label":"Delete Bucket CORS", "param":"s3://BUCKET", "func":cmd_delcors, "argc":1},
+
+    {"cmd":"payer",     "label":"Modify Bucket Requester Pays policy", "param":"s3://BUCKET", "func":cmd_set_payer, "argc":1},
+    {"cmd":"multipart", "label":"Show multipart uploads", "param":"s3://BUCKET [Id]", "func":cmd_multipart, "argc":1},
+    {"cmd":"abortmp",   "label":"Abort a multipart upload", "param":"s3://BUCKET/OBJECT Id", "func":cmd_abort_multipart, "argc":2},
+
+    {"cmd":"listmp",    "label":"List parts of a multipart upload", "param":"s3://BUCKET/OBJECT Id", "func":cmd_list_multipart, "argc":2},
+
+    {"cmd":"accesslog", "label":"Enable/disable bucket access logging", "param":"s3://BUCKET", "func":cmd_accesslog, "argc":1},
+    {"cmd":"sign", "label":"Sign arbitrary string using the secret key", "param":"STRING-TO-SIGN", "func":cmd_sign, "argc":1},
+    {"cmd":"signurl", "label":"Sign an S3 URL to provide limited public access with expiry", "param":"s3://BUCKET/OBJECT <expiry_epoch|+expiry_offset>", "func":cmd_signurl, "argc":2},
+    {"cmd":"fixbucket", "label":"Fix invalid file names in a bucket", "param":"s3://BUCKET[/PREFIX]", "func":cmd_fixbucket, "argc":1},
+
+    ## Website commands
+    {"cmd":"ws-create", "label":"Create Website from bucket", "param":"s3://BUCKET", "func":cmd_website_create, "argc":1},
+    {"cmd":"ws-delete", "label":"Delete Website", "param":"s3://BUCKET", "func":cmd_website_delete, "argc":1},
+    {"cmd":"ws-info", "label":"Info about Website", "param":"s3://BUCKET", "func":cmd_website_info, "argc":1},
+
+    ## Lifecycle commands
+    {"cmd":"expire", "label":"Set or delete expiration rule for the bucket", "param":"s3://BUCKET", "func":cmd_expiration_set, "argc":1},
+    {"cmd":"setlifecycle", "label":"Upload a lifecycle policy for the bucket", "param":"FILE s3://BUCKET", "func":cmd_setlifecycle, "argc":2},
+    {"cmd":"getlifecycle", "label":"Get a lifecycle policy for the bucket",    "param":"s3://BUCKET", "func":cmd_getlifecycle, "argc":1},
+    {"cmd":"dellifecycle", "label":"Remove a lifecycle policy for the bucket", "param":"s3://BUCKET", "func":cmd_dellifecycle, "argc":1},
+
+    ## Notification commands
+    {"cmd":"setnotification", "label":"Upload a notification policy for the bucket", "param":"FILE s3://BUCKET", "func":cmd_setnotification, "argc":2},
+    {"cmd":"getnotification", "label":"Get a notification policy for the bucket",    "param":"s3://BUCKET", "func":cmd_getnotification, "argc":1},
+    {"cmd":"delnotification", "label":"Remove a notification policy for the bucket", "param":"s3://BUCKET", "func":cmd_delnotification, "argc":1},
+
+    ## CloudFront commands
+    {"cmd":"cflist", "label":"List CloudFront distribution points", "param":"", "func":CfCmd.info, "argc":0},
+    {"cmd":"cfinfo", "label":"Display CloudFront distribution point parameters", "param":"[cf://DIST_ID]", "func":CfCmd.info, "argc":0},
+    {"cmd":"cfcreate", "label":"Create CloudFront distribution point", "param":"s3://BUCKET", "func":CfCmd.create, "argc":1},
+    {"cmd":"cfdelete", "label":"Delete CloudFront distribution point", "param":"cf://DIST_ID", "func":CfCmd.delete, "argc":1},
+    {"cmd":"cfmodify", "label":"Change CloudFront distribution point parameters", "param":"cf://DIST_ID", "func":CfCmd.modify, "argc":1},
+    #{"cmd":"cfinval", "label":"Invalidate CloudFront objects", "param":"s3://BUCKET/OBJECT [s3://BUCKET/OBJECT ...]", "func":CfCmd.invalidate, "argc":1},
+    {"cmd":"cfinvalinfo", "label":"Display CloudFront invalidation request(s) status", "param":"cf://DIST_ID[/INVAL_ID]", "func":CfCmd.invalinfo, "argc":1},
+    ]
+
+def format_commands(progname, commands_list):
+    help = "Commands:\n"
+    for cmd in commands_list:
+        help += "  %s\n      %s %s %s\n" % (cmd["label"], progname, cmd["cmd"], cmd["param"])
+    return help
+
+
+def update_acl(s3, uri, seq_label=""):
+    cfg = Config()
+    something_changed = False
+    acl = s3.get_acl(uri)
+    debug(u"acl: %s - %r" % (uri, acl.grantees))
+    if cfg.acl_public == True:
+        if acl.isAnonRead():
+            info(u"%s: already Public, skipping %s" % (uri, seq_label))
+        else:
+            acl.grantAnonRead()
+            something_changed = True
+    elif cfg.acl_public == False:  # we explicitely check for False, because it could be None
+        if not acl.isAnonRead() and not acl.isAnonWrite():
+            info(u"%s: already Private, skipping %s" % (uri, seq_label))
+        else:
+            acl.revokeAnonRead()
+            acl.revokeAnonWrite()
+            something_changed = True
+
+    # update acl with arguments
+    # grant first and revoke later, because revoke has priority
+    if cfg.acl_grants:
+        something_changed = True
+        for grant in cfg.acl_grants:
+            acl.grant(**grant)
+
+    if cfg.acl_revokes:
+        something_changed = True
+        for revoke in cfg.acl_revokes:
+            acl.revoke(**revoke)
+
+    if not something_changed:
+        return
+
+    retsponse = s3.set_acl(uri, acl)
+    if retsponse['status'] == 200:
+        if cfg.acl_public in (True, False):
+            set_to_acl = cfg.acl_public and "Public" or "Private"
+            output(u"%s: ACL set to %s  %s" % (uri, set_to_acl, seq_label))
+        else:
+            output(u"%s: ACL updated" % uri)
+
+class OptionMimeType(Option):
+    def check_mimetype(self, opt, value):
+        if re.compile(r"^[a-z0-9]+/[a-z0-9+\.-]+(;.*)?$", re.IGNORECASE).match(value):
+            return value
+        raise OptionValueError("option %s: invalid MIME-Type format: %r" % (opt, value))
+
+class OptionS3ACL(Option):
+    def check_s3acl(self, opt, value):
+        permissions = ('read', 'write', 'read_acp', 'write_acp', 'full_control', 'all')
+        try:
+            permission, grantee = re.compile(r"^(\w+):(.+)$", re.IGNORECASE).match(value).groups()
+            if not permission or not grantee:
+                raise OptionValueError("option %s: invalid S3 ACL format: %r" % (opt, value))
+            if permission in permissions:
+                return { 'name' : grantee, 'permission' : permission.upper() }
+            else:
+                raise OptionValueError("option %s: invalid S3 ACL permission: %s (valid values: %s)" %
+                    (opt, permission, ", ".join(permissions)))
+        except OptionValueError:
+            raise
+        except Exception:
+            raise OptionValueError("option %s: invalid S3 ACL format: %r" % (opt, value))
+
+class OptionAll(OptionMimeType, OptionS3ACL):
+    TYPE_CHECKER = copy(Option.TYPE_CHECKER)
+    TYPE_CHECKER["mimetype"] = OptionMimeType.check_mimetype
+    TYPE_CHECKER["s3acl"] = OptionS3ACL.check_s3acl
+    TYPES = Option.TYPES + ("mimetype", "s3acl")
+
+class MyHelpFormatter(IndentedHelpFormatter):
+    def format_epilog(self, epilog):
+        if epilog:
+            return "\n" + epilog + "\n"
+        else:
+            return ""
+
+def main():
+    cfg = Config()
+    commands_list = get_commands_list()
+    commands = {}
+
+    ## Populate "commands" from "commands_list"
+    for cmd in commands_list:
+        if 'cmd' in cmd:
+            commands[cmd['cmd']] = cmd
+
+    optparser = OptionParser(option_class=OptionAll, formatter=MyHelpFormatter())
+    #optparser.disable_interspersed_args()
+
+    autodetected_encoding = locale.getpreferredencoding() or "UTF-8"
+
+    config_file = None
+    if os.getenv("S3CMD_CONFIG"):
+        config_file = unicodise_s(os.getenv("S3CMD_CONFIG"),
+                                  autodetected_encoding)
+    elif os.name == "nt" and os.getenv("USERPROFILE"):
+        config_file = os.path.join(
+            unicodise_s(os.getenv("USERPROFILE"), autodetected_encoding),
+            os.getenv("APPDATA")
+               and unicodise_s(os.getenv("APPDATA"), autodetected_encoding)
+               or 'Application Data',
+            "s3cmd.ini")
+    else:
+        from os.path import expanduser
+        config_file = os.path.join(expanduser("~"), ".s3cfg")
+
+    optparser.set_defaults(config = config_file)
+
+    optparser.add_option(      "--configure", dest="run_configure", action="store_true", help="Invoke interactive (re)configuration tool. Optionally use as '--configure s3://some-bucket' to test access to a specific bucket instead of attempting to list them all.")
+    optparser.add_option("-c", "--config", dest="config", metavar="FILE", help="Config file name. Defaults to $HOME/.s3cfg")
+    optparser.add_option(      "--dump-config", dest="dump_config", action="store_true", help="Dump current configuration after parsing config files and command line options and exit.")
+    optparser.add_option(      "--access_key", dest="access_key", help="AWS Access Key")
+    optparser.add_option(      "--secret_key", dest="secret_key", help="AWS Secret Key")
+    optparser.add_option(      "--access_token", dest="access_token", help="AWS Access Token")
+
+    optparser.add_option("-n", "--dry-run", dest="dry_run", action="store_true", help="Only show what should be uploaded or downloaded but don't actually do it. May still perform S3 requests to get bucket listings and other information though (only for file transfer commands)")
+
+    optparser.add_option("-s", "--ssl", dest="use_https", action="store_true", help="Use HTTPS connection when communicating with S3. (default)")
+    optparser.add_option(      "--no-ssl", dest="use_https", action="store_false", help="Don't use HTTPS.")
+    optparser.add_option("-e", "--encrypt", dest="encrypt", action="store_true", help="Encrypt files before uploading to S3.")
+    optparser.add_option(      "--no-encrypt", dest="encrypt", action="store_false", help="Don't encrypt files.")
+    optparser.add_option("-f", "--force", dest="force", action="store_true", help="Force overwrite and other dangerous operations.")
+    optparser.add_option(      "--continue", dest="get_continue", action="store_true", help="Continue getting a partially downloaded file (only for [get] command).")
+    optparser.add_option(      "--continue-put", dest="put_continue", action="store_true", help="Continue uploading partially uploaded files or multipart upload parts.  Restarts parts/files that don't have matching size and md5.  Skips files/parts that do.  Note: md5sum checks are not always sufficient to check (part) file equality.  Enable this at your own risk.")
+    optparser.add_option(      "--upload-id", dest="upload_id", help="UploadId for Multipart Upload, in case you want continue an existing upload (equivalent to --continue-put) and there are multiple partial uploads.  Use s3cmd multipart [URI] to see what UploadIds are associated with the given URI.")
+    optparser.add_option(      "--skip-existing", dest="skip_existing", action="store_true", help="Skip over files that exist at the destination (only for [get] and [sync] commands).")
+    optparser.add_option("-r", "--recursive", dest="recursive", action="store_true", help="Recursive upload, download or removal.")
+    optparser.add_option(      "--check-md5", dest="check_md5", action="store_true", help="Check MD5 sums when comparing files for [sync]. (default)")
+    optparser.add_option(      "--no-check-md5", dest="check_md5", action="store_false", help="Do not check MD5 sums when comparing files for [sync]. Only size will be compared. May significantly speed up transfer but may also miss some changed files.")
+    optparser.add_option("-P", "--acl-public", dest="acl_public", action="store_true", help="Store objects with ACL allowing read for anyone.")
+    optparser.add_option(      "--acl-private", dest="acl_public", action="store_false", help="Store objects with default ACL allowing access for you only.")
+    optparser.add_option(      "--acl-grant", dest="acl_grants", type="s3acl", action="append", metavar="PERMISSION:EMAIL or USER_CANONICAL_ID", help="Grant stated permission to a given amazon user. Permission is one of: read, write, read_acp, write_acp, full_control, all")
+    optparser.add_option(      "--acl-revoke", dest="acl_revokes", type="s3acl", action="append", metavar="PERMISSION:USER_CANONICAL_ID", help="Revoke stated permission for a given amazon user. Permission is one of: read, write, read_acp, write_acp, full_control, all")
+
+    optparser.add_option("-D", "--restore-days", dest="restore_days", action="store", help="Number of days to keep restored file available (only for 'restore' command). Default is 1 day.", metavar="NUM")
+    optparser.add_option(      "--restore-priority", dest="restore_priority", action="store", choices=['standard', 'expedited', 'bulk'], help="Priority for restoring files from S3 Glacier (only for 'restore' command). Choices available: bulk, standard, expedited")
+
+    optparser.add_option(      "--delete-removed", dest="delete_removed", action="store_true", help="Delete destination objects with no corresponding source file [sync]")
+    optparser.add_option(      "--no-delete-removed", dest="delete_removed", action="store_false", help="Don't delete destination objects [sync]")
+    optparser.add_option(      "--delete-after", dest="delete_after", action="store_true", help="Perform deletes AFTER new uploads when delete-removed is enabled [sync]")
+    optparser.add_option(      "--delay-updates", dest="delay_updates", action="store_true", help="*OBSOLETE* Put all updated files into place at end [sync]")  # OBSOLETE
+    optparser.add_option(      "--max-delete", dest="max_delete", action="store", help="Do not delete more than NUM files. [del] and [sync]", metavar="NUM")
+    optparser.add_option(      "--limit", dest="limit", action="store", help="Limit number of objects returned in the response body (only for [ls] and [la] commands)", metavar="NUM")
+    optparser.add_option(      "--add-destination", dest="additional_destinations", action="append", help="Additional destination for parallel uploads, in addition to last arg.  May be repeated.")
+    optparser.add_option(      "--delete-after-fetch", dest="delete_after_fetch", action="store_true", help="Delete remote objects after fetching to local file (only for [get] and [sync] commands).")
+    optparser.add_option("-p", "--preserve", dest="preserve_attrs", action="store_true", help="Preserve filesystem attributes (mode, ownership, timestamps). Default for [sync] command.")
+    optparser.add_option(      "--no-preserve", dest="preserve_attrs", action="store_false", help="Don't store FS attributes")
+    optparser.add_option(      "--exclude", dest="exclude", action="append", metavar="GLOB", help="Filenames and paths matching GLOB will be excluded from sync")
+    optparser.add_option(      "--exclude-from", dest="exclude_from", action="append", metavar="FILE", help="Read --exclude GLOBs from FILE")
+    optparser.add_option(      "--rexclude", dest="rexclude", action="append", metavar="REGEXP", help="Filenames and paths matching REGEXP (regular expression) will be excluded from sync")
+    optparser.add_option(      "--rexclude-from", dest="rexclude_from", action="append", metavar="FILE", help="Read --rexclude REGEXPs from FILE")
+    optparser.add_option(      "--include", dest="include", action="append", metavar="GLOB", help="Filenames and paths matching GLOB will be included even if previously excluded by one of --(r)exclude(-from) patterns")
+    optparser.add_option(      "--include-from", dest="include_from", action="append", metavar="FILE", help="Read --include GLOBs from FILE")
+    optparser.add_option(      "--rinclude", dest="rinclude", action="append", metavar="REGEXP", help="Same as --include but uses REGEXP (regular expression) instead of GLOB")
+    optparser.add_option(      "--rinclude-from", dest="rinclude_from", action="append", metavar="FILE", help="Read --rinclude REGEXPs from FILE")
+
+    optparser.add_option(      "--files-from", dest="files_from", action="append", metavar="FILE", help="Read list of source-file names from FILE. Use - to read from stdin.")
+    optparser.add_option(      "--region", "--bucket-location", metavar="REGION", dest="bucket_location", help="Region to create bucket in. As of now the regions are: us-east-1, us-west-1, us-west-2, eu-west-1, eu-central-1, ap-northeast-1, ap-southeast-1, ap-southeast-2, sa-east-1")
+    optparser.add_option(      "--host", metavar="HOSTNAME", dest="host_base", help="HOSTNAME:PORT for S3 endpoint (default: %s, alternatives such as s3-eu-west-1.amazonaws.com). You should also set --host-bucket." % (cfg.host_base))
+    optparser.add_option(      "--host-bucket", dest="host_bucket", help="DNS-style bucket+hostname:port template for accessing a bucket (default: %s)" % (cfg.host_bucket))
+    optparser.add_option(      "--reduced-redundancy", "--rr", dest="reduced_redundancy", action="store_true", help="Store object with 'Reduced redundancy'. Lower per-GB price. [put, cp, mv]")
+    optparser.add_option(      "--no-reduced-redundancy", "--no-rr", dest="reduced_redundancy", action="store_false", help="Store object without 'Reduced redundancy'. Higher per-GB price. [put, cp, mv]")
+    optparser.add_option(      "--storage-class", dest="storage_class", action="store", metavar="CLASS", help="Store object with specified CLASS (STANDARD, STANDARD_IA, ONEZONE_IA, INTELLIGENT_TIERING, GLACIER or DEEP_ARCHIVE). [put, cp, mv]")
+    optparser.add_option(      "--access-logging-target-prefix", dest="log_target_prefix", help="Target prefix for access logs (S3 URI) (for [cfmodify] and [accesslog] commands)")
+    optparser.add_option(      "--no-access-logging", dest="log_target_prefix", action="store_false", help="Disable access logging (for [cfmodify] and [accesslog] commands)")
+
+    optparser.add_option(      "--default-mime-type", dest="default_mime_type", type="mimetype", action="store", help="Default MIME-type for stored objects. Application default is binary/octet-stream.")
+    optparser.add_option("-M", "--guess-mime-type", dest="guess_mime_type", action="store_true", help="Guess MIME-type of files by their extension or mime magic. Fall back to default MIME-Type as specified by --default-mime-type option")
+    optparser.add_option(      "--no-guess-mime-type", dest="guess_mime_type", action="store_false", help="Don't guess MIME-type and use the default type instead.")
+    optparser.add_option(      "--no-mime-magic", dest="use_mime_magic", action="store_false", help="Don't use mime magic when guessing MIME-type.")
+    optparser.add_option("-m", "--mime-type", dest="mime_type", type="mimetype", metavar="MIME/TYPE", help="Force MIME-type. Override both --default-mime-type and --guess-mime-type.")
+
+    optparser.add_option(      "--add-header", dest="add_header", action="append", metavar="NAME:VALUE", help="Add a given HTTP header to the upload request. Can be used multiple times. For instance set 'Expires' or 'Cache-Control' headers (or both) using this option.")
+    optparser.add_option(      "--remove-header", dest="remove_headers", action="append", metavar="NAME", help="Remove a given HTTP header.  Can be used multiple times.  For instance, remove 'Expires' or 'Cache-Control' headers (or both) using this option. [modify]")
+
+    optparser.add_option(      "--server-side-encryption", dest="server_side_encryption", action="store_true", help="Specifies that server-side encryption will be used when putting objects. [put, sync, cp, modify]")
+    optparser.add_option(      "--server-side-encryption-kms-id", dest="kms_key", action="store", help="Specifies the key id used for server-side encryption with AWS KMS-Managed Keys (SSE-KMS) when putting objects. [put, sync, cp, modify]")
+
+    optparser.add_option(      "--encoding", dest="encoding", metavar="ENCODING", help="Override autodetected terminal and filesystem encoding (character set). Autodetected: %s" % autodetected_encoding)
+    optparser.add_option(      "--add-encoding-exts", dest="add_encoding_exts", metavar="EXTENSIONs", help="Add encoding to these comma delimited extensions i.e. (css,js,html) when uploading to S3 )")
+    optparser.add_option(      "--verbatim", dest="urlencoding_mode", action="store_const", const="verbatim", help="Use the S3 name as given on the command line. No pre-processing, encoding, etc. Use with caution!")
+
+    optparser.add_option(      "--disable-multipart", dest="enable_multipart", action="store_false", help="Disable multipart upload on files bigger than --multipart-chunk-size-mb")
+    optparser.add_option(      "--multipart-chunk-size-mb", dest="multipart_chunk_size_mb", type="int", action="store", metavar="SIZE", help="Size of each chunk of a multipart upload. Files bigger than SIZE are automatically uploaded as multithreaded-multipart, smaller files are uploaded using the traditional method. SIZE is in Mega-Bytes, default chunk size is 15MB, minimum allowed chunk size is 5MB, maximum is 5GB.")
+
+    optparser.add_option(      "--list-md5", dest="list_md5", action="store_true", help="Include MD5 sums in bucket listings (only for 'ls' command).")
+
+    optparser.add_option(      "--list-allow-unordered", dest="list_allow_unordered", action="store_true", help="Not an AWS standard. Allow the listing results to be returned in unsorted order. This may be faster when listing very large buckets.")
+
+    optparser.add_option("-H", "--human-readable-sizes", dest="human_readable_sizes", action="store_true", help="Print sizes in human readable form (eg 1kB instead of 1234).")
+
+    optparser.add_option(      "--ws-index", dest="website_index", action="store", help="Name of index-document (only for [ws-create] command)")
+    optparser.add_option(      "--ws-error", dest="website_error", action="store", help="Name of error-document (only for [ws-create] command)")
+
+    optparser.add_option(      "--expiry-date", dest="expiry_date", action="store", help="Indicates when the expiration rule takes effect. (only for [expire] command)")
+    optparser.add_option(      "--expiry-days", dest="expiry_days", action="store", help="Indicates the number of days after object creation the expiration rule takes effect. (only for [expire] command)")
+    optparser.add_option(      "--expiry-prefix", dest="expiry_prefix", action="store", help="Identifying one or more objects with the prefix to which the expiration rule applies. (only for [expire] command)")
+
+    optparser.add_option(      "--progress", dest="progress_meter", action="store_true", help="Display progress meter (default on TTY).")
+    optparser.add_option(      "--no-progress", dest="progress_meter", action="store_false", help="Don't display progress meter (default on non-TTY).")
+    optparser.add_option(      "--stats", dest="stats", action="store_true", help="Give some file-transfer stats.")
+    optparser.add_option(      "--enable", dest="enable", action="store_true", help="Enable given CloudFront distribution (only for [cfmodify] command)")
+    optparser.add_option(      "--disable", dest="enable", action="store_false", help="Disable given CloudFront distribution (only for [cfmodify] command)")
+    optparser.add_option(      "--cf-invalidate", dest="invalidate_on_cf", action="store_true", help="Invalidate the uploaded filed in CloudFront. Also see [cfinval] command.")
+    # joseprio: adding options to invalidate the default index and the default
+    # index root
+    optparser.add_option(      "--cf-invalidate-default-index", dest="invalidate_default_index_on_cf", action="store_true", help="When using Custom Origin and S3 static website, invalidate the default index file.")
+    optparser.add_option(      "--cf-no-invalidate-default-index-root", dest="invalidate_default_index_root_on_cf", action="store_false", help="When using Custom Origin and S3 static website, don't invalidate the path to the default index file.")
+    optparser.add_option(      "--cf-add-cname", dest="cf_cnames_add", action="append", metavar="CNAME", help="Add given CNAME to a CloudFront distribution (only for [cfcreate] and [cfmodify] commands)")
+    optparser.add_option(      "--cf-remove-cname", dest="cf_cnames_remove", action="append", metavar="CNAME", help="Remove given CNAME from a CloudFront distribution (only for [cfmodify] command)")
+    optparser.add_option(      "--cf-comment", dest="cf_comment", action="store", metavar="COMMENT", help="Set COMMENT for a given CloudFront distribution (only for [cfcreate] and [cfmodify] commands)")
+    optparser.add_option(      "--cf-default-root-object", dest="cf_default_root_object", action="store", metavar="DEFAULT_ROOT_OBJECT", help="Set the default root object to return when no object is specified in the URL. Use a relative path, i.e. default/index.html instead of /default/index.html or s3://bucket/default/index.html (only for [cfcreate] and [cfmodify] commands)")
+    optparser.add_option("-v", "--verbose", dest="verbosity", action="store_const", const=logging.INFO, help="Enable verbose output.")
+    optparser.add_option("-d", "--debug", dest="verbosity", action="store_const", const=logging.DEBUG, help="Enable debug output.")
+    optparser.add_option(      "--version", dest="show_version", action="store_true", help="Show s3cmd version (%s) and exit." % (PkgInfo.version))
+    optparser.add_option("-F", "--follow-symlinks", dest="follow_symlinks", action="store_true", default=False, help="Follow symbolic links as if they are regular files")
+    optparser.add_option(      "--cache-file", dest="cache_file", action="store", default="",  metavar="FILE", help="Cache FILE containing local source MD5 values")
+    optparser.add_option("-q", "--quiet", dest="quiet", action="store_true", default=False, help="Silence output on stdout")
+    optparser.add_option(      "--ca-certs", dest="ca_certs_file", action="store", default=None, help="Path to SSL CA certificate FILE (instead of system default)")
+    optparser.add_option(      "--ssl-cert", dest="ssl_client_cert_file", action="store", default=None, help="Path to client own SSL certificate CRT_FILE")
+    optparser.add_option(      "--ssl-key", dest="ssl_client_key_file", action="store", default=None, help="Path to client own SSL certificate private key KEY_FILE")
+    optparser.add_option(      "--check-certificate", dest="check_ssl_certificate", action="store_true", help="Check SSL certificate validity")
+    optparser.add_option(      "--no-check-certificate", dest="check_ssl_certificate", action="store_false", help="Do not check SSL certificate validity")
+    optparser.add_option(      "--check-hostname", dest="check_ssl_hostname", action="store_true", help="Check SSL certificate hostname validity")
+    optparser.add_option(      "--no-check-hostname", dest="check_ssl_hostname", action="store_false", help="Do not check SSL certificate hostname validity")
+    optparser.add_option(      "--signature-v2", dest="signature_v2", action="store_true", help="Use AWS Signature version 2 instead of newer signature methods. Helpful for S3-like systems that don't have AWS Signature v4 yet.")
+    optparser.add_option(      "--limit-rate", dest="limitrate", action="store", type="string", help="Limit the upload or download speed to amount bytes per second.  Amount may be expressed in bytes, kilobytes with the k suffix, or megabytes with the m suffix")
+    optparser.add_option(      "--no-connection-pooling", dest="connection_pooling", action="store_false", help="Disable connection re-use")
+    optparser.add_option(      "--requester-pays", dest="requester_pays", action="store_true", help="Set the REQUESTER PAYS flag for operations")
+    optparser.add_option("-l", "--long-listing", dest="long_listing", action="store_true", help="Produce long listing [ls]")
+    optparser.add_option(      "--stop-on-error", dest="stop_on_error", action="store_true", help="stop if error in transfer")
+    optparser.add_option(      "--content-disposition", dest="content_disposition", action="store", help="Provide a Content-Disposition for signed URLs, e.g., \"inline; filename=myvideo.mp4\"")
+    optparser.add_option(      "--content-type", dest="content_type", action="store", help="Provide a Content-Type for signed URLs, e.g., \"video/mp4\"")
+
+    optparser.set_usage(optparser.usage + " COMMAND [parameters]")
+    optparser.set_description('S3cmd is a tool for managing objects in '+
+        'Amazon S3 storage. It allows for making and removing '+
+        '"buckets" and uploading, downloading and removing '+
+        '"objects" from these buckets.')
+    optparser.epilog = format_commands(optparser.get_prog_name(), commands_list)
+    optparser.epilog += ("\nFor more information, updates and news, visit the s3cmd website:\n%s\n" % PkgInfo.url)
+
+    (options, args) = optparser.parse_args()
+
+    ## Some mucking with logging levels to enable
+    ## debugging/verbose output for config file parser on request
+    logging.basicConfig(level=options.verbosity or Config().verbosity,
+                        format='%(levelname)s: %(message)s',
+                        stream = sys.stderr)
+
+    if options.show_version:
+        output(u"s3cmd version %s" % PkgInfo.version)
+        sys.exit(EX_OK)
+    debug(u"s3cmd version %s" % PkgInfo.version)
+
+    if options.quiet:
+        try:
+            f = open("/dev/null", "w")
+            sys.stdout = f
+        except IOError:
+            warning(u"Unable to open /dev/null: --quiet disabled.")
+
+    ## Now finally parse the config file
+    if not options.config:
+        error(u"Can't find a config file. Please use --config option.")
+        sys.exit(EX_CONFIG)
+
+    try:
+        cfg = Config(options.config, options.access_key, options.secret_key, options.access_token)
+    except ValueError as exc:
+        raise ParameterError(unicode(exc))
+    except IOError as e:
+        if options.run_configure:
+            cfg = Config()
+        else:
+            error(u"%s: %s"  % (options.config, e.strerror))
+            error(u"Configuration file not available.")
+            error(u"Consider using --configure parameter to create one.")
+            sys.exit(EX_CONFIG)
+
+    # allow commandline verbosity config to override config file
+    if options.verbosity is not None:
+        cfg.verbosity = options.verbosity
+    logging.root.setLevel(cfg.verbosity)
+    ## Unsupported features on Win32 platform
+    if os.name == "nt":
+        if cfg.preserve_attrs:
+            error(u"Option --preserve is not yet supported on MS Windows platform. Assuming --no-preserve.")
+            cfg.preserve_attrs = False
+        if cfg.progress_meter:
+            error(u"Option --progress is not yet supported on MS Windows platform. Assuming --no-progress.")
+            cfg.progress_meter = False
+
+    ## Pre-process --add-header's and put them to Config.extra_headers SortedDict()
+    if options.add_header:
+        for hdr in options.add_header:
+            try:
+                key, val = unicodise_s(hdr).split(":", 1)
+            except ValueError:
+                raise ParameterError("Invalid header format: %s" % unicodise_s(hdr))
+            # key char restrictions of the http headers name specification
+            key_inval = re.sub(r"[a-zA-Z0-9\-.!#$%&*+^_|]", "", key)
+            if key_inval:
+                key_inval = key_inval.replace(" ", "<space>")
+                key_inval = key_inval.replace("\t", "<tab>")
+                raise ParameterError("Invalid character(s) in header name '%s'"
+                                     ": \"%s\"" % (key, key_inval))
+            debug(u"Updating Config.Config extra_headers[%s] -> %s" %
+                  (key.strip().lower(), val.strip()))
+            cfg.extra_headers[key.strip().lower()] = val.strip()
+
+    # Process --remove-header
+    if options.remove_headers:
+        cfg.remove_headers = options.remove_headers
+
+    ## --acl-grant/--acl-revoke arguments are pre-parsed by OptionS3ACL()
+    if options.acl_grants:
+        for grant in options.acl_grants:
+            cfg.acl_grants.append(grant)
+
+    if options.acl_revokes:
+        for grant in options.acl_revokes:
+            cfg.acl_revokes.append(grant)
+
+    ## Process --(no-)check-md5
+    if options.check_md5 == False:
+        if "md5" in cfg.sync_checks:
+            cfg.sync_checks.remove("md5")
+        if "md5" in cfg.preserve_attrs_list:
+            cfg.preserve_attrs_list.remove("md5")
+    elif options.check_md5 == True:
+        if "md5" not in cfg.sync_checks:
+            cfg.sync_checks.append("md5")
+        if "md5" not in cfg.preserve_attrs_list:
+            cfg.preserve_attrs_list.append("md5")
+
+    ## Update Config with other parameters
+    for option in cfg.option_list():
+        try:
+            value = getattr(options, option)
+            if value != None:
+                if type(value) == type(b''):
+                    value = unicodise_s(value)
+                debug(u"Updating Config.Config %s -> %s" % (option, value))
+                cfg.update_option(option, value)
+        except AttributeError:
+            ## Some Config() options are not settable from command line
+            pass
+
+    ## Special handling for tri-state options (True, False, None)
+    cfg.update_option("enable", options.enable)
+    if options.acl_public is not None:
+        cfg.update_option("acl_public", options.acl_public)
+
+    ## Check multipart chunk constraints
+    if cfg.multipart_chunk_size_mb < MultiPartUpload.MIN_CHUNK_SIZE_MB:
+        raise ParameterError("Chunk size %d MB is too small, must be >= %d MB. Please adjust --multipart-chunk-size-mb" % (cfg.multipart_chunk_size_mb, MultiPartUpload.MIN_CHUNK_SIZE_MB))
+    if cfg.multipart_chunk_size_mb > MultiPartUpload.MAX_CHUNK_SIZE_MB:
+        raise ParameterError("Chunk size %d MB is too large, must be <= %d MB. Please adjust --multipart-chunk-size-mb" % (cfg.multipart_chunk_size_mb, MultiPartUpload.MAX_CHUNK_SIZE_MB))
+
+    ## If an UploadId was provided, set put_continue True
+    if options.upload_id:
+        cfg.upload_id = options.upload_id
+        cfg.put_continue = True
+
+    if cfg.upload_id and not cfg.multipart_chunk_size_mb:
+        raise ParameterError("Must have --multipart-chunk-size-mb if using --put-continue or --upload-id")
+
+    ## CloudFront's cf_enable and Config's enable share the same --enable switch
+    options.cf_enable = options.enable
+
+    ## CloudFront's cf_logging and Config's log_target_prefix share the same --log-target-prefix switch
+    options.cf_logging = options.log_target_prefix
+
+    ## Update CloudFront options if some were set
+    for option in CfCmd.options.option_list():
+        try:
+            value = getattr(options, option)
+            if value != None:
+                if type(value) == type(b''):
+                    value = unicodise_s(value)
+            if value != None:
+                debug(u"Updating CloudFront.Cmd %s -> %s" % (option, value))
+                CfCmd.options.update_option(option, value)
+        except AttributeError:
+            ## Some CloudFront.Cmd.Options() options are not settable from command line
+            pass
+
+    if options.additional_destinations:
+        cfg.additional_destinations = options.additional_destinations
+    if options.files_from:
+        cfg.files_from = options.files_from
+
+    ## Set output and filesystem encoding for printing out filenames.
+    try:
+        # Support for python3
+        # That don't need codecs if output is the
+        # encoding of the system, but just in case, still use it.
+        # For that, we need to use directly the binary buffer
+        # of stdout/stderr
+        sys.stdout = codecs.getwriter(cfg.encoding)(sys.stdout.buffer, "replace")
+        sys.stderr = codecs.getwriter(cfg.encoding)(sys.stderr.buffer, "replace")
+        # getwriter with create an "IObuffer" that have not the encoding attribute
+        # better to add it to not break some functions like "input".
+        sys.stdout.encoding = cfg.encoding
+        sys.stderr.encoding = cfg.encoding
+    except AttributeError:
+        sys.stdout = codecs.getwriter(cfg.encoding)(sys.stdout, "replace")
+        sys.stderr = codecs.getwriter(cfg.encoding)(sys.stderr, "replace")
+
+    ## Process --exclude and --exclude-from
+    patterns_list, patterns_textual = process_patterns(options.exclude, options.exclude_from, is_glob = True, option_txt = "exclude")
+    cfg.exclude.extend(patterns_list)
+    cfg.debug_exclude.update(patterns_textual)
+
+    ## Process --rexclude and --rexclude-from
+    patterns_list, patterns_textual = process_patterns(options.rexclude, options.rexclude_from, is_glob = False, option_txt = "rexclude")
+    cfg.exclude.extend(patterns_list)
+    cfg.debug_exclude.update(patterns_textual)
+
+    ## Process --include and --include-from
+    patterns_list, patterns_textual = process_patterns(options.include, options.include_from, is_glob = True, option_txt = "include")
+    cfg.include.extend(patterns_list)
+    cfg.debug_include.update(patterns_textual)
+
+    ## Process --rinclude and --rinclude-from
+    patterns_list, patterns_textual = process_patterns(options.rinclude, options.rinclude_from, is_glob = False, option_txt = "rinclude")
+    cfg.include.extend(patterns_list)
+    cfg.debug_include.update(patterns_textual)
+
+    ## Set socket read()/write() timeout
+    socket.setdefaulttimeout(cfg.socket_timeout)
+
+    if cfg.encrypt and cfg.gpg_passphrase == "":
+        error(u"Encryption requested but no passphrase set in config file.")
+        error(u"Please re-run 's3cmd --configure' and supply it.")
+        sys.exit(EX_CONFIG)
+
+    if options.dump_config:
+        cfg.dump_config(sys.stdout)
+        sys.exit(EX_OK)
+
+    if options.run_configure:
+        # 'args' may contain the test-bucket URI
+        run_configure(options.config, args)
+        sys.exit(EX_OK)
+
+    ## set config if stop_on_error is set
+    if options.stop_on_error:
+        cfg.stop_on_error = options.stop_on_error
+
+    if options.content_disposition:
+        cfg.content_disposition = options.content_disposition
+
+    if options.content_type:
+        cfg.content_type = options.content_type
+
+    if len(args) < 1:
+        optparser.print_help()
+        sys.exit(EX_USAGE)
+
+    ## Unicodise all remaining arguments:
+    args = [unicodise(arg) for arg in args]
+
+    command = args.pop(0)
+    try:
+        debug(u"Command: %s" % commands[command]["cmd"])
+        ## We must do this lookup in extra step to
+        ## avoid catching all KeyError exceptions
+        ## from inner functions.
+        cmd_func = commands[command]["func"]
+    except KeyError as e:
+        error(u"Invalid command: %s", command)
+        sys.exit(EX_USAGE)
+
+    if len(args) < commands[command]["argc"]:
+        error(u"Not enough parameters for command '%s'" % command)
+        sys.exit(EX_USAGE)
+
+    rc = cmd_func(args)
+    if rc is None: # if we missed any cmd_*() returns
+        rc = EX_GENERAL
+    return rc
+
+def report_exception(e, msg=u''):
+        alert_header = u"""
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+    An unexpected error has occurred.
+  Please try reproducing the error using
+  the latest s3cmd code from the git master
+  branch found at:
+    https://github.com/s3tools/s3cmd
+  and have a look at the known issues list:
+    https://github.com/s3tools/s3cmd/wiki/Common-known-issues-and-their-solutions-(FAQ)
+  If the error persists, please report the
+  %s (removing any private
+  info as necessary) to:
+   s3tools-bugs@lists.sourceforge.net%s
+!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+
+"""
+        sys.stderr.write(alert_header % (u"following lines", u"\n\n" + msg))
+        tb = traceback.format_exc()
+        try:
+            s = u' '.join([unicodise(a) for a in sys.argv])
+        except NameError:
+            # Error happened before Utils module was yet imported to provide
+            # unicodise
+            try:
+                s = u' '.join([(a) for a in sys.argv])
+            except UnicodeDecodeError:
+                s = u'[encoding safe] ' + u' '.join([('%r'%a) for a in sys.argv])
+        sys.stderr.write(u"Invoked as: %s\n" % s)
+
+        e_class = str(e.__class__)
+        e_class = e_class[e_class.rfind(".")+1 : -2]
+        try:
+            sys.stderr.write(u"Problem: %s: %s\n" % (e_class, e))
+        except UnicodeDecodeError:
+            sys.stderr.write(u"Problem: [encoding safe] %r: %r\n"
+                             % (e_class, e))
+        try:
+            sys.stderr.write(u"S3cmd:   %s\n" % PkgInfo.version)
+        except NameError:
+            sys.stderr.write(u"S3cmd:   unknown version."
+                             "Module import problem?\n")
+        sys.stderr.write(u"python:   %s\n" % sys.version)
+        try:
+            sys.stderr.write(u"environment LANG=%s\n"
+                             % unicodise_s(os.getenv("LANG", "NOTSET"),
+                                           'ascii'))
+        except NameError:
+            # Error happened before Utils module was yet imported to provide
+            # unicodise
+            sys.stderr.write(u"environment LANG=%s\n"
+                             % os.getenv("LANG", "NOTSET"))
+        sys.stderr.write(u"\n")
+        if type(tb) == unicode:
+            sys.stderr.write(tb)
+        else:
+            sys.stderr.write(unicode(tb, errors="replace"))
+
+        if type(e) == ImportError:
+            sys.stderr.write("\n")
+            sys.stderr.write("Your sys.path contains these entries:\n")
+            for path in sys.path:
+                sys.stderr.write(u"\t%s\n" % path)
+            sys.stderr.write("Now the question is where have the s3cmd modules"
+                             " been installed?\n")
+
+        sys.stderr.write(alert_header % (u"above lines", u""))
+
+if __name__ == '__main__':
+    try:
+        ## Our modules
+        ## Keep them in try/except block to
+        ## detect any syntax errors in there
+        from S3.ExitCodes import *
+        from S3.Exceptions import *
+        from S3 import PkgInfo
+        from S3.S3 import S3
+        from S3.Config import Config
+        from S3.SortedDict import SortedDict
+        from S3.FileDict import FileDict
+        from S3.S3Uri import S3Uri
+        from S3 import Utils
+        from S3 import Crypto
+        from S3.BaseUtils import (formatDateTime, getPrettyFromXml,
+                                  encode_to_s3, decode_from_s3)
+        from S3.Utils import (formatSize, unicodise_safe, unicodise_s,
+                              unicodise, deunicodise, replace_nonprintables)
+        from S3.Progress import Progress, StatsInfo
+        from S3.CloudFront import Cmd as CfCmd
+        from S3.CloudFront import CloudFront
+        from S3.FileLists import *
+        from S3.MultiPart import MultiPartUpload
+    except Exception as e:
+        report_exception(e, "Error loading some components of s3cmd (Import Error)")
+        # 1 = EX_GENERAL but be safe in that situation
+        sys.exit(1)
+
+    try:
+        rc = main()
+        sys.exit(rc)
+
+    except ImportError as e:
+        report_exception(e)
+        sys.exit(EX_GENERAL)
+
+    except (ParameterError, InvalidFileError) as e:
+        error(u"Parameter problem: %s" % e)
+        sys.exit(EX_USAGE)
+
+    except (S3DownloadError, S3UploadError, S3RequestError) as e:
+        error(u"S3 Temporary Error: %s.  Please try again later." % e)
+        sys.exit(EX_TEMPFAIL)
+
+    except S3Error as e:
+        error(u"S3 error: %s" % e)
+        sys.exit(e.get_error_code())
+
+    except (S3Exception, S3ResponseError, CloudFrontError) as e:
+        report_exception(e)
+        sys.exit(EX_SOFTWARE)
+
+    except SystemExit as e:
+        sys.exit(e.code)
+
+    except KeyboardInterrupt:
+        sys.stderr.write("See ya!\n")
+        sys.exit(EX_BREAK)
+
+    except (S3SSLError, S3SSLCertificateError) as e:
+        # SSLError is a subtype of IOError
+        error("SSL certificate verification failure: %s" % e)
+        sys.exit(EX_ACCESSDENIED)
+
+    except ConnectionRefusedError as e:
+        error(e)
+        sys.exit(EX_CONNECTIONREFUSED)
+        # typically encountered error is:
+        # ERROR: [Errno 111] Connection refused
+
+    except socket.gaierror as e:
+        # gaierror is a subset of IOError
+        # typically encountered error is:
+        # gaierror: [Errno -2] Name or service not known
+        error(e)
+        error("Connection Error: Error resolving a server hostname.\n"
+              "Please check the servers address specified in 'host_base', 'host_bucket', 'cloudfront_host', 'website_endpoint'")
+        sys.exit(EX_IOERR)
+
+    except IOError as e:
+        if e.errno == errno.ECONNREFUSED:
+            # Python2 does not have ConnectionRefusedError
+            error(e)
+            sys.exit(EX_CONNECTIONREFUSED)
+
+        if e.errno == errno.EPIPE:
+            # Fail silently on SIGPIPE. This likely means we wrote to a closed
+            # pipe and user does not care for any more output.
+            sys.exit(EX_IOERR)
+
+        report_exception(e)
+        sys.exit(EX_IOERR)
+
+    except OSError as e:
+        error(e)
+        sys.exit(EX_OSERR)
+
+    except MemoryError:
+        msg = """
+MemoryError!  You have exceeded the amount of memory available for this process.
+This usually occurs when syncing >750,000 files on a 32-bit python instance.
+The solutions to this are:
+1) sync several smaller subtrees; or
+2) use a 64-bit python on a 64-bit OS with >8GB RAM
+        """
+        sys.stderr.write(msg)
+        sys.exit(EX_OSERR)
+
+    except UnicodeEncodeError as e:
+        lang = unicodise_s(os.getenv("LANG", "NOTSET"), 'ascii')
+        msg = """
+You have encountered a UnicodeEncodeError.  Your environment
+variable LANG=%s may not specify a Unicode encoding (e.g. UTF-8).
+Please set LANG=en_US.UTF-8 or similar in your environment before
+invoking s3cmd.
+        """ % lang
+        report_exception(e, msg)
+        sys.exit(EX_GENERAL)
+
+    except Exception as e:
+        report_exception(e)
+        sys.exit(EX_GENERAL)
+
+# vim:et:ts=4:sts=4:ai
diff --git a/csharp/App_backup/Backend/Resources/smtpConfig.json b/csharp/App_backup/Backend/Resources/smtpConfig.json
new file mode 100644
index 000000000..0e22daa3a
--- /dev/null
+++ b/csharp/App_backup/Backend/Resources/smtpConfig.json
@@ -0,0 +1,6 @@
+{
+  "Url": "mail.agenturserver.de",
+  "Port": 587,
+  "Username": "p518526p69",
+  "Password": "i;b*xqm4iB5uhl"
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Resources/urlAndKey.json b/csharp/App_backup/Backend/Resources/urlAndKey.json
new file mode 100644
index 000000000..137c8e690
--- /dev/null
+++ b/csharp/App_backup/Backend/Resources/urlAndKey.json
@@ -0,0 +1,6 @@
+{
+  "ReadOnlyS3Key": "EXO44d2979c8e570eae81ead564",
+  "ReadOnlyS3Secret": "55MAqyO_FqUmh7O64VIO0egq50ERn_WIAWuc2QC44QU" ,
+  "ReadWriteS3Key": "EXO87ca85e29dd412f1238f1cf0", 
+  "ReadWriteS3Secret": "-T9TAqy9a3-0-xj7HKsFFJOCcxfRpcnL6OW5oOrOcWU" 
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Websockets/AlarmOrWarning.cs b/csharp/App_backup/Backend/Websockets/AlarmOrWarning.cs
new file mode 100644
index 000000000..6f737032b
--- /dev/null
+++ b/csharp/App_backup/Backend/Websockets/AlarmOrWarning.cs
@@ -0,0 +1,11 @@
+
+
+namespace InnovEnergy.App.Backend.Websockets;
+
+public class AlarmOrWarning
+{
+    public required String Date                   { get; set; }
+    public required String Time                   { get; set; }
+    public required String Description            { get; set; }
+    public required String CreatedBy              { get; set; }
+} 
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Websockets/InstallationInfo.cs b/csharp/App_backup/Backend/Websockets/InstallationInfo.cs
new file mode 100644
index 000000000..df816607b
--- /dev/null
+++ b/csharp/App_backup/Backend/Websockets/InstallationInfo.cs
@@ -0,0 +1,10 @@
+using System.Net.WebSockets;
+namespace InnovEnergy.App.Backend.Websockets;
+
+public class InstallationInfo
+{
+    public int             Status      { get; set; }
+    public DateTime        Timestamp   { get; set; }
+    public int             Product     { get; set; }
+    public List<WebSocket> Connections { get; } = new List<WebSocket>();
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Websockets/RabbitMQManager.cs b/csharp/App_backup/Backend/Websockets/RabbitMQManager.cs
new file mode 100644
index 000000000..7dd73116c
--- /dev/null
+++ b/csharp/App_backup/Backend/Websockets/RabbitMQManager.cs
@@ -0,0 +1,179 @@
+using System.Text;
+using System.Text.Json;
+using InnovEnergy.App.Backend.Database;
+using InnovEnergy.App.Backend.DataTypes;
+using InnovEnergy.Lib.Utils;
+using RabbitMQ.Client;
+using RabbitMQ.Client.Events;
+
+namespace InnovEnergy.App.Backend.Websockets;
+
+public static class RabbitMqManager
+{
+    public static ConnectionFactory Factory = null!;
+    public static IConnection Connection = null!;
+    public static IModel Channel = null!;
+    
+    //This function will be called from the Backend/Program.cs
+    public static void InitializeEnvironment()
+    {
+        string vpnServerIp   = "10.2.0.11";
+        
+        //Subscribe to RabbitMq queue as a consumer
+        Factory = new ConnectionFactory
+        {
+            HostName = vpnServerIp,
+            Port = 5672,
+            VirtualHost = "/",
+            UserName = "consumer",
+            Password = "faceaddb5005815199f8366d3d15ff8a",
+            
+        };
+        
+        Connection = Factory.CreateConnection();
+        Channel = Connection.CreateModel();
+        Console.WriteLine("Middleware subscribed to RabbitMQ queue, ready for receiving messages");
+        Channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
+    }
+    
+    public static async Task StartRabbitMqConsumer()
+    {
+        //Wait to receive a message from an installation
+        var consumer = new EventingBasicConsumer(Channel);
+        consumer.Received += (_, ea) =>
+        {
+            var body = ea.Body.ToArray();
+            var message = Encoding.UTF8.GetString(body);
+            //A message can be an alarm, a warning or a heartbit
+            StatusMessage? receivedStatusMessage = JsonSerializer.Deserialize<StatusMessage>(message);
+            
+            lock (WebsocketManager.InstallationConnections)
+            {
+                //Consumer received a message
+                if (receivedStatusMessage != null)
+                {
+                    Installation installation = Db.Installations.FirstOrDefault(f => f.Product == receivedStatusMessage.Product && f.S3BucketId == receivedStatusMessage.InstallationId);
+                    int installationId = (int)installation.Id;
+
+                    //This is a heartbit message, just update the timestamp for this installation.
+                    //There is no need to notify the corresponding front-ends.
+                    //Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue.
+                    if (receivedStatusMessage.Type == MessageType.Heartbit)
+                    {
+                        //Do not do anything here, just for debugging purposes.
+                    }
+                    else
+                    {
+                        //Traverse the Warnings list, and store each of them to the database
+                        if (receivedStatusMessage.Warnings != null)
+                        {
+                            foreach (var warning in receivedStatusMessage.Warnings)
+                            {
+                                Warning newWarning = new Warning
+                                {
+                                    InstallationId = installationId,
+                                    Description = warning.Description,
+                                    Date = warning.Date,
+                                    Time = warning.Time,
+                                    DeviceCreatedTheMessage = warning.CreatedBy,
+                                    Seen = false
+                                };
+                                //Create a new warning and add it to the database
+                                //Console.WriteLine("Add a warning for installation "+installationId);
+                                Db.HandleWarning(newWarning, installationId);
+                            }
+                        }
+
+
+                        //Traverse the Alarm list, and store each of them to the database
+                        if (receivedStatusMessage.Alarms != null)
+                        {
+                            
+                            string monitorLink;
+                            if (installation.Product == (int)ProductType.Salimax)
+                            {
+                                monitorLink =
+                                    $"https://monitor.innov.energy/installations/list/installation/{installation.S3BucketId}/batteryview";
+                            }
+                            else
+                            {
+                                monitorLink =
+                                    $"https://monitor.innov.energy/salidomo_installations/list/installation/{installation.S3BucketId}/batteryview";
+                            }
+                           
+                            foreach (var alarm in receivedStatusMessage.Alarms)
+                            {
+                                Error newError = new Error
+                                {
+                                    InstallationId = installation.Id,
+                                    Description = alarm.Description,
+                                    Date = alarm.Date,
+                                    Time = alarm.Time,
+                                    DeviceCreatedTheMessage = alarm.CreatedBy,
+                                    Seen = false
+                                }; 
+                                
+                                //Console.WriteLine("Add an alarm for installation "+installationId);
+                                
+                                // Send replace battery email to support team if this alarm is "NeedToReplaceBattery"
+                                if (alarm.Description == "2 or more string are disabled")
+                                {
+                                    Console.WriteLine("Send replace battery email to the support team for installation "+installationId);
+                                    string recipient = "support@innov.energy";
+                                    string subject = $"Battery Alarm from {installation.InstallationName}: 2 or more strings broken";
+                                    string text = $"Dear InnovEnergy Support Team,\n" +
+                                                  $"\n"+
+                                                  $"Installation Name: {installation.InstallationName}\n"+
+                                                  $"\n"+
+                                                  $"Installation Monitor Link: {monitorLink}\n"+
+                                                  $"\n"+
+                                                  $"Please exchange: {alarm.CreatedBy}\n"+
+                                                  $"\n"+
+                                                  $"Error created date and time: {alarm.Date}  {alarm.Time}\n"+
+                                                  $"\n"+
+                                                  $"Thank you for your great support:)";
+                                    //Disable this function now
+                                    //Mailer.Send("InnovEnergy Support Team", recipient, subject, text);
+                                }
+                                //Create a new error and add it to the database
+                                Db.HandleError(newError, installationId);
+                            }
+                        }
+                    }
+
+                    Int32 prevStatus;
+
+                    //This installation id does not exist in our in-memory data structure, add it.
+                    if (!WebsocketManager.InstallationConnections.ContainsKey(installationId))
+                    {
+                        prevStatus = -2;
+                        //Console.WriteLine("Create new empty list for installation: " + installationId);
+                        WebsocketManager.InstallationConnections[installationId] = new InstallationInfo
+                        {
+                            Status = receivedStatusMessage.Status,
+                            Timestamp = DateTime.Now,
+                            Product = installation.Product
+                        };
+                    }
+                    else
+                    {
+                        prevStatus = WebsocketManager.InstallationConnections[installationId].Status;
+                        WebsocketManager.InstallationConnections[installationId].Status = receivedStatusMessage.Status;
+                        WebsocketManager.InstallationConnections[installationId].Timestamp = DateTime.Now;
+                    }
+                    
+                    installation.Status = receivedStatusMessage.Status;
+                    installation.Apply(Db.Update);
+
+                    //Console.WriteLine("----------------------------------------------");
+                    //If the status has changed, update all the connected front-ends regarding this installation
+                    if(prevStatus != receivedStatusMessage.Status && WebsocketManager.InstallationConnections[installationId].Connections.Count > 0)
+                    {
+                        WebsocketManager.InformWebsocketsForInstallation(installationId);
+                    }
+                }
+            }
+        };
+        Channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer);
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/Websockets/StatusMessage.cs b/csharp/App_backup/Backend/Websockets/StatusMessage.cs
new file mode 100644
index 000000000..0d2571811
--- /dev/null
+++ b/csharp/App_backup/Backend/Websockets/StatusMessage.cs
@@ -0,0 +1,19 @@
+
+namespace InnovEnergy.App.Backend.Websockets;
+
+public class StatusMessage
+{
+    public required Int32                    InstallationId  { get; set; }
+    public required Int32                    Product         { get; set; }
+    public required Int32                    Status          { get; set; }
+    public required MessageType              Type            { get; set; }
+    public List<AlarmOrWarning>?             Warnings        { get; set; }
+    public List<AlarmOrWarning>?             Alarms          { get; set; }
+    public Int32                             Timestamp       { get; set; }
+}
+
+public enum MessageType
+{
+    AlarmOrWarning,
+    Heartbit
+}
diff --git a/csharp/App_backup/Backend/Websockets/WebsockerManager.cs b/csharp/App_backup/Backend/Websockets/WebsockerManager.cs
new file mode 100644
index 000000000..5568f66c7
--- /dev/null
+++ b/csharp/App_backup/Backend/Websockets/WebsockerManager.cs
@@ -0,0 +1,238 @@
+using System.Net;
+using System.Net.Sockets;
+using System.Net.WebSockets;
+using System.Text;
+using System.Text.Json;
+using InnovEnergy.App.Backend.Database;
+using InnovEnergy.App.Backend.DataTypes;
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.Backend.Websockets;
+
+public static class WebsocketManager
+{
+    public static Dictionary<Int64, InstallationInfo> InstallationConnections = new Dictionary<Int64, InstallationInfo>();
+
+    //Every 1 minute, check the timestamp of the latest received message for every installation.
+    //If the difference between the two timestamps is more than two minutes, we consider this Salimax installation unavailable.
+    public static async Task MonitorSalimaxInstallationTable()
+    {
+        while (true){
+            lock (InstallationConnections){
+                Console.WriteLine("MONITOR SALIMAX INSTALLATIONS\n");
+                foreach (var installationConnection in InstallationConnections){
+                    
+                    if (installationConnection.Value.Product==(int)ProductType.Salimax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)){
+                        
+                        // Console.WriteLine("Installation ID is "+installationConnection.Key);
+                        // Console.WriteLine("installationConnection.Value.Timestamp is "+installationConnection.Value.Timestamp);
+                        // Console.WriteLine("diff is  "+(DateTime.Now-installationConnection.Value.Timestamp));
+                        
+                        installationConnection.Value.Status = (int)StatusType.Offline;
+                        Installation installation = Db.Installations.FirstOrDefault(f => f.Product == (int)ProductType.Salimax && f.Id == installationConnection.Key);
+                        installation.Status = (int)StatusType.Offline;
+                        installation.Apply(Db.Update);
+                        if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);}
+                    }
+                }
+                Console.WriteLine("FINISHED MONITORING SALIMAX INSTALLATIONS\n");
+            }
+            
+            await Task.Delay(TimeSpan.FromMinutes(1));
+        }
+    }
+    
+    //Every 1 minute, check the timestamp of the latest received message for every installation.
+    //If the difference between the two timestamps is more than 1 hour, we consider this Salidomo installation unavailable.
+    public static async Task MonitorSalidomoInstallationTable()
+    {
+        while (true){
+            Console.WriteLine("TRY TO LOCK FOR MONITOR SALIDOMO INSTALLATIONS\n");
+            lock (InstallationConnections){
+                Console.WriteLine("MONITOR SALIDOMO INSTALLATIONS\n");
+                foreach (var installationConnection in InstallationConnections)
+                {
+                    //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("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");
+            }
+            await Task.Delay(TimeSpan.FromMinutes(1));
+        }
+    }
+
+    //Inform all the connected websockets regarding installation "installationId"
+    public static void InformWebsocketsForInstallation(Int64 installationId)
+    {
+        var installation = Db.GetInstallationById(installationId);
+        var installationConnection = InstallationConnections[installationId];
+        Console.WriteLine("Update all the connected websockets for installation " + installation.Name);
+        
+        var jsonObject = new
+        {
+            id = installationId,
+            status = installationConnection.Status,
+            testingMode = installation.TestingMode
+        };
+
+        string jsonString = JsonSerializer.Serialize(jsonObject);
+        byte[] dataToSend = Encoding.UTF8.GetBytes(jsonString);
+
+        foreach (var connection in installationConnection.Connections)
+        {
+            connection.SendAsync(
+                new ArraySegment<byte>(dataToSend, 0, dataToSend.Length),
+                WebSocketMessageType.Text,
+                true, // Indicates that this is the end of the message
+                CancellationToken.None
+            );
+        }
+    }
+
+    public static async Task HandleWebSocketConnection(WebSocket currentWebSocket)
+    {
+        var buffer = new byte[4096];
+        try
+        {
+            while (currentWebSocket.State == WebSocketState.Open)
+            {
+                //Listen for incoming messages on this WebSocket
+                var result = await currentWebSocket.ReceiveAsync(buffer, CancellationToken.None);
+                
+                if (result.MessageType != WebSocketMessageType.Text)
+                    continue;
+                
+                var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
+                var installationIds = JsonSerializer.Deserialize<int[]>(message);
+
+                //This is a ping message to keep the connection alive, reply with a pong
+                if (installationIds[0] == -1)
+                {
+                    var jsonObject = new
+                    {
+                        id = -1,
+                        status = -1
+                    };
+
+                    var jsonString = JsonSerializer.Serialize(jsonObject); 
+                    var dataToSend = Encoding.UTF8.GetBytes(jsonString);
+                    currentWebSocket.SendAsync(dataToSend,
+                        WebSocketMessageType.Text,
+                        true, 
+                        CancellationToken.None
+                    );
+                    
+                    continue;
+                }
+               
+                //Received a new message from this websocket.
+                //We have a HandleWebSocketConnection per connected frontend
+                lock (InstallationConnections)
+                {
+                    List<WebsocketMessage> dataToSend = new List<WebsocketMessage>();
+                    
+                    //Each front-end will send the list of the installations it wants to access
+                    //If this is a new key (installation id), initialize the list for this key and then add the websocket object for this client
+                    //Then, report the status of each requested installation to the front-end that created the websocket connection
+                    foreach (var installationId in installationIds)
+                    {
+                        var installation = Db.GetInstallationById(installationId);
+                        if (!InstallationConnections.ContainsKey(installationId))
+                        {
+                            //Since we keep all the changes to the database, in case that the backend reboots, we need to update the in-memory data structure.
+                            //Thus, if the status is -1, we put an old timestamp, otherwise, we put the most recent timestamp.
+                            //We store everything to the database, because when the backend reboots, we do not want to wait until all the installations send the heartbit messages.
+                            //We want the in memory data structure to be up to date immediately.
+                            InstallationConnections[installationId] = new InstallationInfo
+                            {
+                                Status = installation.Status,
+                                Timestamp = installation.Status==(int)StatusType.Offline ? DateTime.Now.AddDays(-1) :  DateTime.Now,
+                                Product = installation.Product
+                            };
+                        }
+
+                        InstallationConnections[installationId].Connections.Add(currentWebSocket);
+
+                        var jsonObject = new WebsocketMessage
+                        {
+                            id = installationId,
+                            status = InstallationConnections[installationId].Status,
+                            testingMode = installation.TestingMode
+                        };
+                        
+                        dataToSend.Add(jsonObject);
+                        
+                    }
+                    var jsonString = JsonSerializer.Serialize(dataToSend);
+                    var encodedDataToSend = Encoding.UTF8.GetBytes(jsonString);
+
+
+                     currentWebSocket.SendAsync(encodedDataToSend,
+                         WebSocketMessageType.Text,
+                         true, // Indicates that this is the end of the message
+                         CancellationToken.None
+                     );
+                    
+
+                    Console.WriteLine("Printing installation connection list");
+                    Console.WriteLine("----------------------------------------------");
+                    foreach (var installationConnection in InstallationConnections)
+                    {
+                        Console.WriteLine("Installation ID: " + installationConnection.Key + " Number of Connections: " + installationConnection.Value.Connections.Count);
+                    }
+                    Console.WriteLine("----------------------------------------------");
+                }
+            }
+
+            lock (InstallationConnections)
+            {
+                //When the front-end terminates the connection, the following code will be executed
+                Console.WriteLine("The connection has been terminated");
+                foreach (var installationConnection in InstallationConnections)
+                {
+                    if (installationConnection.Value.Connections.Contains(currentWebSocket))
+                    {
+                        installationConnection.Value.Connections.Remove(currentWebSocket);
+                    }
+                }
+            }
+
+            await currentWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Connection closed by server", CancellationToken.None);
+            lock (InstallationConnections)
+            {
+                //Print the installationConnections dictionary after deleting a websocket
+                Console.WriteLine("Print the installation connections list after deleting a websocket");
+                Console.WriteLine("----------------------------------------------");
+                foreach (var installationConnection in InstallationConnections)
+                {
+                    Console.WriteLine("Installation ID: " + installationConnection.Key + " Number of Connections: " + installationConnection.Value.Connections.Count);
+                }
+
+                Console.WriteLine("----------------------------------------------");
+            }
+        }
+        catch (Exception ex)
+        {
+            Console.WriteLine("WebSocket error: " + ex.Message);
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Backend/db-1733849565.sqlite b/csharp/App_backup/Backend/db-1733849565.sqlite
new file mode 100644
index 000000000..f3b3e33a3
Binary files /dev/null and b/csharp/App_backup/Backend/db-1733849565.sqlite differ
diff --git a/csharp/App_backup/Backend/deploy.sh b/csharp/App_backup/Backend/deploy.sh
new file mode 100755
index 000000000..ff3675a7d
--- /dev/null
+++ b/csharp/App_backup/Backend/deploy.sh
@@ -0,0 +1,5 @@
+#To deploy to the monitor server, uncomment the following line
+dotnet publish Backend.csproj -c Release -r linux-x64 --self-contained true -p:PublishTrimmed=false && rsync -av bin/Release/net6.0/linux-x64/publish/ ubuntu@194.182.190.208:~/backend && ssh ubuntu@194.182.190.208 'sudo systemctl restart backend'
+
+#To deploy to the stage server, uncomment the following line
+#dotnet publish Backend.csproj -c Release -r linux-x64 --self-contained true -p:PublishTrimmed=false && rsync -av bin/Release/net6.0/linux-x64/publish/ ubuntu@91.92.154.141:~/backend && ssh ubuntu@91.92.154.141 'sudo systemctl restart backend'
diff --git a/csharp/App_backup/Backend/deploy_stage.sh b/csharp/App_backup/Backend/deploy_stage.sh
new file mode 100755
index 000000000..2b4c454c1
--- /dev/null
+++ b/csharp/App_backup/Backend/deploy_stage.sh
@@ -0,0 +1 @@
+dotnet publish Backend.csproj -c Release -r linux-x64 --self-contained true -p:PublishTrimmed=false && rsync -av bin/Release/net6.0/linux-x64/publish/ ubuntu@91.92.154.141:~/backend && ssh ubuntu@91.92.154.141 'sudo systemctl restart backend'
diff --git a/csharp/App_backup/BmsTunnel/BatteryTty.cs b/csharp/App_backup/BmsTunnel/BatteryTty.cs
new file mode 100644
index 000000000..b1ef964fd
--- /dev/null
+++ b/csharp/App_backup/BmsTunnel/BatteryTty.cs
@@ -0,0 +1,95 @@
+using CliWrap;
+using CliWrap.Buffered;
+using InnovEnergy.Lib.Utils;
+using static InnovEnergy.App.BmsTunnel.CliPrograms;
+
+namespace InnovEnergy.App.BmsTunnel;
+
+using Nodes = IReadOnlyList<Byte>;
+
+public static class BatteryTty
+{
+    const String DevDir = "/dev";
+    
+    public static async Task<String?> GetTty()
+    {
+        Console.WriteLine("searching battery connection...");
+
+        return await GetTtyFromDBus() 
+            ?? await GetTtyFromDev();
+    }
+
+    private static async Task<String?> GetTtyFromDev()
+    {
+        var ttys = await FileSystem
+                        .Local
+                        .GetFiles(DevDir, FileType.CharacterDevice);
+
+        var candidateTtys = ttys
+                           .Where(f => f.StartsWith(DevDir + "/ttyUSB"))
+                           .NotNull()
+                           .ToList();
+
+        if (!candidateTtys.Any())
+        {
+            Console.WriteLine("no USB converter cable attached!");
+            return null;
+        }
+
+        if (candidateTtys.Count == 1)
+            return candidateTtys[0];
+
+        return "Select TTY:".ChooseFrom(candidateTtys);
+    }
+
+    private static async Task<String?> GetTtyFromDBus()
+    {
+        var tty = await LsDBus
+                       .ExecuteBufferedAsync()
+                       .Select(ParseBatteryTty);
+
+        if (tty == null)
+            return null;
+        
+        Console.WriteLine("found battery on DBus");
+
+        return $"{DevDir}/{tty}";
+    }
+
+    private static CommandTask<Nodes> GetNodes(String tty)
+    {
+        const String defaultArgs = "--system --print-reply --type=method_call / com.victronenergy.BusItem.GetValue";
+        
+        return DBusSend
+              .AppendArgument($"--dest=com.victronenergy.battery.{tty}")  
+              .AppendArgument(defaultArgs)  
+              .ExecuteBufferedAsync()
+              .Select(ParseBatteryNodes);
+    }
+
+    private static Nodes ParseBatteryNodes(BufferedCommandResult result)
+    {
+        return result
+              .StandardOutput
+              .Split(Environment.NewLine)
+              .Where(l => l.Contains("_Battery/"))
+              .Select(l => l.Split('/')[1])
+              .Where(n => Byte.TryParse(n, out _))
+              .Select(Byte.Parse)
+              .Distinct()
+              .OrderBy(n => n)
+              .ToList();
+    }
+
+    private static String? ParseBatteryTty(BufferedCommandResult result)
+    {
+        return result
+              .StandardOutput
+              .Split(Environment.NewLine)
+              .Where(l => l.Contains("com.victronenergy.battery."))
+              .SelectMany(l => l.Split('.'))
+              .LastOrDefault();
+    }
+
+}
+
diff --git a/csharp/App_backup/BmsTunnel/BmsTunnel.cs b/csharp/App_backup/BmsTunnel/BmsTunnel.cs
new file mode 100644
index 000000000..d7f455996
--- /dev/null
+++ b/csharp/App_backup/BmsTunnel/BmsTunnel.cs
@@ -0,0 +1,191 @@
+using System.IO.Ports;
+using System.Text;
+using CliWrap.Buffered;
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.BmsTunnel;
+
+
+public class BmsTunnel : IDisposable
+{
+    private SerialPort SerialPort { get; }
+    
+    public String Tty  { get; }
+    public Byte   Node { get; set; }
+
+    private const Int32    BaudRate = 115200;
+    private const Int32    DataBits = 8;
+    private const Parity   Parity   = System.IO.Ports.Parity.Even;
+    private const StopBits StopBits = System.IO.Ports.StopBits.One;
+
+    private const Int32 CrcLength = 2; 
+    
+    private const Byte TunnelCode = 0x41;
+    private const String CrcError = "?? CRC FAILED";
+
+    public BmsTunnel(String tty, Byte node)
+    {
+        Tty  = tty;
+        Node = node;
+
+        StopSerialStarter();
+
+        SerialPort = new SerialPort(Tty, BaudRate, Parity, DataBits, StopBits);
+        SerialPort.ReadTimeout = 100;
+        
+        SerialPort.Open();
+    }
+
+
+    private IEnumerable<Byte> Header
+    {
+        get
+        {
+            yield return Node;
+            yield return TunnelCode;
+        }
+    }
+    
+    private static IEnumerable<Byte> NewLine
+    {
+        get
+        {
+            yield return 0x0D;
+        }
+    }
+
+
+    public IEnumerable<String> SendCommand(String command)
+    {
+        var reply = SendSingleCommand(command);
+
+        while (!reply.StartsWith("??"))
+        {
+            yield return reply;
+            
+            if (reply.EndsWith("chars answered. Ready."))
+                yield break;
+            
+            reply = GetMore();
+        }
+
+        if (reply == CrcError)
+        {
+            yield return "";
+            yield return CrcError.Substring(3);
+        }
+    }
+
+    private String GetMore() => SendSingleCommand("");
+
+
+    private String SendSingleCommand(String command)
+    {
+        var payload = Header
+                     .Concat(CommandToBytes(command))
+                     .ToList();
+
+        var crc = CalcCrc(payload);
+        
+        payload.AddRange(crc);
+  
+        SerialPort.Write(payload.ToArray(), 0, payload.Count);
+
+        var response = Enumerable
+                      .Range(0, 255)
+                      .Select(ReadByte)
+                      .TakeWhile(b => b >= 0)
+                      .Select(Convert.ToByte)
+                      .ToArray();
+
+        if (!CheckCrc(response))
+        {
+            // TODO: this should go into outer loop instead of returning magic value CrcError
+            
+            //Console.WriteLine(BitConverter.ToString(response).Replace("-", " "));
+            return CrcError;
+        }
+
+        return response
+              .Skip(2)
+              .TakeWhile(b => b != 0x0D)
+              .ToArray()
+              .Apply(Encoding.ASCII.GetString);
+
+        Int32 ReadByte<T>(T _)
+        {
+            try
+            {
+                return SerialPort.ReadByte();
+            }
+            catch (TimeoutException)
+            {
+                return -1;
+            }
+        }
+    }
+    
+    private static IReadOnlyList<Byte> CalcCrc(IEnumerable<Byte> data)
+    {
+        UInt16 crc = 0xFFFF;
+
+        foreach (var b in data)
+        {
+            crc ^= b;
+
+            for (var bit = 0; bit < 8; bit++)
+            {
+                var bit0 = (crc & 0x0001) != 0;
+                crc >>= 1;
+                if (bit0) crc ^= 0xA001;
+            }
+        }
+
+        var hi = 0xFF & crc;
+        var lo = (crc >> 8) & 0xFF;
+
+        return new[] {(Byte) hi, (Byte) lo}; // big endian
+    }
+
+    private static Boolean CheckCrc(IReadOnlyList<Byte> data)
+    {
+        var expectedCrc = data.SkipLast(CrcLength).Apply(CalcCrc);
+        var actualCrc   = data.TakeLast(CrcLength);
+
+        return actualCrc.SequenceEqual(expectedCrc);
+    }
+
+    private static IEnumerable<Byte> CommandToBytes(String command)
+    {
+        if (command == "")
+            return Enumerable.Empty<Byte>();
+
+        return command
+              .Apply(Encoding.ASCII.GetBytes)
+              .Concat(NewLine);
+    }
+
+    private void StopSerialStarter()
+    {
+        CliPrograms.StopTty
+                   .WithArguments(Tty)
+                   .ExecuteBufferedAsync()
+                   .Task
+                   .Wait(3000);
+    }
+
+    private void StartSerialStarter()
+    {
+        CliPrograms.StartTty
+                   .WithArguments(Tty)
+                   .ExecuteBufferedAsync()
+                   .Task
+                   .Wait(3000);
+    }
+
+    public void Dispose()
+    {
+        SerialPort.Dispose();
+        StartSerialStarter();
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/BmsTunnel/BmsTunnel.csproj b/csharp/App_backup/BmsTunnel/BmsTunnel.csproj
new file mode 100644
index 000000000..886f107fd
--- /dev/null
+++ b/csharp/App_backup/BmsTunnel/BmsTunnel.csproj
@@ -0,0 +1,18 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <Import Project="../InnovEnergy.App.props" />
+
+    <PropertyGroup>
+        <RootNamespace>InnovEnergy.App.BmsTunnel</RootNamespace>
+    </PropertyGroup>
+    
+    <ItemGroup>
+        <PackageReference Include="CliWrap" Version="3.6.0" />
+        <PackageReference Include="System.IO.Ports" Version="7.0.0" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="../../Lib/Utils/Utils.csproj" />
+    </ItemGroup>
+
+</Project>
diff --git a/csharp/App_backup/BmsTunnel/CliPrograms.cs b/csharp/App_backup/BmsTunnel/CliPrograms.cs
new file mode 100644
index 000000000..c27df92df
--- /dev/null
+++ b/csharp/App_backup/BmsTunnel/CliPrograms.cs
@@ -0,0 +1,11 @@
+using CliWrap;
+
+namespace InnovEnergy.App.BmsTunnel;
+
+public static class CliPrograms
+{
+    public static Command LsDBus   { get; } = Cli.Wrap("/opt/innovenergy/scripts/lsdbus");
+    public static Command DBusSend { get; } = Cli.Wrap("/usr/bin/dbus-send");
+    public static Command StartTty { get; } = Cli.Wrap("/opt/victronenergy/serial-starter/start-tty.sh");
+    public static Command StopTty  { get; } = Cli.Wrap("/opt/victronenergy/serial-starter/stop-tty.sh");
+}
\ No newline at end of file
diff --git a/csharp/App_backup/BmsTunnel/Program.cs b/csharp/App_backup/BmsTunnel/Program.cs
new file mode 100644
index 000000000..b9968d63e
--- /dev/null
+++ b/csharp/App_backup/BmsTunnel/Program.cs
@@ -0,0 +1,85 @@
+// dotnet publish BmsTunnel.csproj -c Release -r linux-arm -p:PublishSingleFile=true --self-contained true && \
+// rsync -av bin/Release/net6.0/linux-arm/publish/ root@10.2.1.6:/home/root/tunnel && clear && \
+// ssh root@10.2.1.6 /home/root/tunnel/BmsTunnel
+
+using InnovEnergy.Lib.Utils;
+using static System.String;
+
+namespace InnovEnergy.App.BmsTunnel;
+
+public static class Program
+{
+    private const Byte DefaultNode = 2;
+
+    public static async Task<Int32> Main(String[] args)
+    {
+        var tty = await BatteryTty.GetTty();
+
+        if (tty is null)
+            return 2;
+        
+        Console.WriteLine("\nstarting BMS tunnel\n");
+        
+        using var tunnel = new BmsTunnel(tty, 2);
+
+        ExplainNode();
+        ExplainExit();
+        
+        //Console.WriteLine("");
+
+        while (true)
+        {
+            //Console.WriteLine("");
+            Console.Write($"node{tunnel.Node}> ");
+
+            var cmd = Console.ReadLine()?.ToUpper().Trim();
+
+            if (IsNullOrEmpty(cmd))
+                continue;
+
+            if (cmd.StartsWith("/"))
+            {
+                var exit = ProcessLocalCommand(cmd);
+                if (exit)
+                    break;
+                continue;
+            }
+
+            tunnel.SendCommand(cmd).Skip(1).ForEach(Console.WriteLine);
+        }
+        
+        
+        Boolean ProcessLocalCommand(String cmd)
+        {
+            cmd = cmd.TrimStart('/').Trim().ToUpper();
+            
+            if (cmd == "EXIT") 
+                return true;
+            if (cmd.StartsWith("NODE ")) 
+                ChangeNode(cmd);
+            else 
+                Console.WriteLine("unrecognized command");
+
+            return false;
+        }
+
+        return 0;
+        
+
+        void ChangeNode(String cmd)
+        {
+            var ndStr = cmd[5..].Trim();
+            
+            if (!Byte.TryParse(ndStr, out var newNode))
+            {
+                ExplainNode();
+                return;
+            }
+
+            tunnel.Node = newNode;
+        }
+    }
+
+    private static void ExplainExit()  => Console.WriteLine("/exit       exit bms cli");
+    private static void ExplainNode()  => Console.WriteLine("/node <nb>  change to node number <nb>");
+}
\ No newline at end of file
diff --git a/csharp/App_backup/BmsTunnel/debug.sh b/csharp/App_backup/BmsTunnel/debug.sh
new file mode 100644
index 000000000..09c8fa1ff
--- /dev/null
+++ b/csharp/App_backup/BmsTunnel/debug.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+
+csproj="BmsTunnel.csproj"
+exe="BmsTunnel"
+remote="10.2.1.6"
+#remote="10.2.2.152"
+
+netVersion="net6.0"
+platform="linux-arm"
+config="Release"
+host="root@$remote"
+dir="/data/innovenergy/$exe"
+
+set -e
+
+dotnet publish "$csproj" -c $config -r $platform -p:SuppressTrimmAnalysisWarnings=true -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false --self-contained true 
+rsync -av "bin/$config/$netVersion/$platform/publish/" "$host:$dir" 
+
+clear 
+ssh "$host" "$dir/$exe"
+  
\ No newline at end of file
diff --git a/csharp/App_backup/BmsTunnel/parameters.txt b/csharp/App_backup/BmsTunnel/parameters.txt
new file mode 100644
index 000000000..e69de29bb
diff --git a/csharp/App_backup/Collector/Collector.csproj b/csharp/App_backup/Collector/Collector.csproj
new file mode 100644
index 000000000..d3842cf17
--- /dev/null
+++ b/csharp/App_backup/Collector/Collector.csproj
@@ -0,0 +1,19 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <Import Project="../InnovEnergy.App.props" />
+
+    <PropertyGroup>
+        <RootNamespace>InnovEnergy.App.Collector</RootNamespace>
+        <IsTrimmable>false</IsTrimmable>
+    </PropertyGroup>
+    
+    <ItemGroup>
+      <ProjectReference Include="../../Lib/Utils/Utils.csproj" />
+      <ProjectReference Include="../../Lib/WebServer/WebServer.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <PackageReference Include="System.Linq.Async" Version="6.0.1" />
+    </ItemGroup>
+
+</Project>
diff --git a/csharp/App_backup/Collector/src/BatteryDataParser.cs b/csharp/App_backup/Collector/src/BatteryDataParser.cs
new file mode 100644
index 000000000..38b3c4fc0
--- /dev/null
+++ b/csharp/App_backup/Collector/src/BatteryDataParser.cs
@@ -0,0 +1,343 @@
+using System.Net;
+using System.Text;
+using InnovEnergy.App.Collector.Influx;
+using InnovEnergy.App.Collector.Records;
+using InnovEnergy.App.Collector.Utils;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Utils.Net;
+using Convert = System.Convert;
+
+
+namespace InnovEnergy.App.Collector;
+
+using Data = IReadOnlyList<String>;
+
+public static class BatteryDataParser
+{
+
+    public static IReadOnlyList<BatteryRecord> ParseDatagram(UdpDatagram datagram)
+    {
+        return ParseV3Datagram(datagram);
+
+        // if (IsV4Payload(buffer))
+        //     return ParseV4Datagram(endPoint, buffer);
+
+        // throw new Exception($"Wrong protocol header: Expected '{Settings.ProtocolV3}'");
+    }
+
+    private static Boolean IsV4Payload(Byte[] buffer)
+    {
+        return buffer
+              .ParseLengthValueEncoded()
+              .First()
+              .ToArray()
+              .Apply(Encoding.UTF8.GetString)
+              .Equals(Settings.ProtocolV4);
+    }
+
+    private static Boolean IsV3Payload(IEnumerable<Byte> buffer)
+    {
+        return buffer
+              .ToArray(Settings.ProtocolV3.Length)
+              .Apply(Encoding.UTF8.GetString)
+              .Equals(Settings.ProtocolV3);
+    }
+
+
+    // private static LineProtocolPayload ParseV4Datagram(IPEndPoint endPoint, Byte[] buffer)
+    // {
+    //     var timeOfArrival = DateTime.UtcNow; // influx wants UTC
+    //
+    //     BatteryDataParserV4.ParseV4Datagram(endPoint, buffer);
+    //     return new LineProtocolPayload();
+    // }
+
+    private static IReadOnlyList<BatteryRecord> ParseV3Datagram(UdpDatagram datagram)
+    {
+        var timeOfArrival = DateTime.UtcNow;
+
+        var data = datagram
+                  .Payload
+                  .ToArray()
+                  .Apply(Encoding.UTF8.GetString)
+                  .Split('\n');
+
+        data.ParseProtocolVersion().Apply(CheckProtocolId);
+
+        var installationName = data.ParseInstallationName();
+
+        return ParseBatteryRecords(data, installationName, timeOfArrival, datagram.EndPoint);
+    }
+
+
+    private static String ParseString(this Data data, Int32 i) => data[i].Trim();
+    private static UInt16 ParseUInt16(this Data data, Int32 i) => UInt16.Parse(ParseString(data, i));
+
+    private static UInt16 ParseUInt16Register(this Data data, Int32 register)
+    {
+        var i = register.RegToIndex();
+        return data.ParseUInt16(i);
+    }
+
+    private static UInt32 ParseUInt32Register(this Data data, Int32 register)
+    {
+        var lo = ParseUInt16Register(data, register);
+        var hi = ParseUInt16Register(data, register + 1);
+
+        return Convert.ToUInt32(((hi << 16) | lo) & UInt32.MaxValue);
+    }
+
+    private static UInt64 ParseUInt64Register(this Data data, Int32 register)
+    {
+        return Enumerable
+              .Range(register, 4)
+              .Reverse()
+              .Select(data.ParseUInt16Register)
+              .Aggregate(0ul, (a, b) => a << 16 | b);
+    }
+
+    private static Decimal ParseDecimalRegister(this Data data,
+                                                    Int32 register,
+                                                  Decimal scaleFactor = 1,
+                                                  Decimal offset = 0)
+    {
+        var i = register.RegToIndex();
+        Int32 n = data.ParseUInt16(i);
+
+        if (n >= 0x8000)
+            n -= 0x10000; // fiamm stores their integers signed AND with sign-offset @#%^&!
+
+        return (Convert.ToDecimal(n) + offset) * scaleFactor; // according fiamm doc
+    }
+
+    private static Int32 RegToIndex(this Int32 register) => register - 992;
+
+
+    private static Leds ParseLeds(this Data data, String installation, String batteryId)
+    {
+        var ledBitmap = ParseUInt16Register(data, 1004);
+        LedState Led(Int32 n) => (LedState) (ledBitmap >> (n * 2) & 0b11);
+
+        return new Leds
+        {
+            Installation = installation,
+            BatteryId    = batteryId,
+
+            Green = Led(0),
+            Amber = Led(1),
+            Blue  = Led(2),
+            Red   = Led(3),
+        };
+    }
+
+
+    private static IoStatus ParseIoStatus(this Data data, String installation, String batteryId)
+    {
+        var ioStatusBitmap = data.ParseUInt16Register(1013);
+        Boolean IoStatus(Int32 b) => (ioStatusBitmap >> b & 1) > 0;
+
+        return new IoStatus
+        {
+            Installation = installation,
+            BatteryId    = batteryId,
+
+            MainSwitchClosed       = IoStatus(0),
+            AlarmOutActive         = IoStatus(1),
+            InternalFanActive      = IoStatus(2),
+            VoltMeasurementAllowed = IoStatus(3),
+            AuxRelay               = IoStatus(4),
+            RemoteState            = IoStatus(5),
+            HeatingOn              = IoStatus(6),
+        };
+    }
+
+    private static Warnings ParseWarnings(this Data data, String installation, String batteryId)
+    {
+        var warningsBitmap = data.ParseUInt64Register(1005);
+        Boolean Warning(Int32 b) => (warningsBitmap >> b & 1ul) > 0;
+
+        return new Warnings
+        {
+            Installation = installation,
+            BatteryId    = batteryId,
+
+            TaM1 = Warning(1),
+            TbM1 = Warning(4),
+            VBm1 = Warning(6),
+            VBM1 = Warning(8),
+            IDM1 = Warning(10),
+            vsM1 = Warning(24),
+            iCM1 = Warning(26),
+            iDM1 = Warning(28),
+            MID1 = Warning(30),
+            BLPW = Warning(32),
+            Ah_W = Warning(35),
+            MPMM = Warning(38),
+            TCMM = Warning(39),
+            TCdi = Warning(40),
+            LMPW = Warning(44)
+        };
+    }
+
+    private static Alarms ParseAlarms(this Data data, String installation, String batteryId)
+    {
+        var alarmsBitmap = data.ParseUInt64Register(1009);
+        Boolean Alarm(Int32 b) => (alarmsBitmap >> b & 1ul) > 0;
+
+        return new Alarms
+        {
+            Installation = installation,
+            BatteryId    = batteryId,
+
+            Tam  = Alarm(0),
+            TaM2 = Alarm(2),
+            Tbm  = Alarm(3),
+            TbM2 = Alarm(5),
+            VBm2 = Alarm(7),
+            VBM2 = Alarm(9),
+            IDM2 = Alarm(11),
+            ISOB = Alarm(12),
+            MSWE = Alarm(13),
+            FUSE = Alarm(14),
+            HTRE = Alarm(15),
+            TCPE = Alarm(16),
+            CME  = Alarm(18),
+            HWFL = Alarm(19),
+            HWEM = Alarm(20),
+            ThM  = Alarm(21),
+            vsm1 = Alarm(22),
+            vsm2 = Alarm(23),
+            vsM2 = Alarm(25),
+            iCM2 = Alarm(27),
+            iDM2 = Alarm(29),
+            MID2 = Alarm(31),
+            CCBF = Alarm(33),
+            AhFL = Alarm(34),
+            TbCM = Alarm(36),
+            HTFS = Alarm(42),
+            DATA = Alarm(43),
+            LMPA = Alarm(45),
+            HEBT = Alarm(46),
+        };
+    }
+
+
+    private static BatteryStatus ParseBatteryStatus(this Data data,
+                                                       String installation,
+                                                       String batteryId,
+                                                      Decimal temperature,
+                                                     Warnings warnings,
+                                                       Alarms alarms,
+                                                     DateTime lastSeen,
+                                                   IPEndPoint endPoint)
+    {
+        var activeWarnings = Active(warnings);
+        var activeAlarms   = Active(alarms);
+
+        return new BatteryStatus
+        {
+            InstallationName = installation,
+            BatteryId        = batteryId,
+
+            HardwareVersion = data.ParseString(3),
+            FirmwareVersion = data.ParseString(4),
+            BmsVersion      = data.ParseString(5),
+            AmpereHours     = data.ParseUInt16(6),
+
+            Soc         = data.ParseDecimalRegister(1053, 0.1m),
+            Voltage     = data.ParseDecimalRegister(999, 0.01m),
+            Current     = data.ParseDecimalRegister(1000, 0.01m, -10000m),
+            BusVoltage  = data.ParseDecimalRegister(1001, 0.01m),
+            Temperature = temperature,
+
+            RtcCounter = data.ParseUInt32Register(1050),
+
+            IpAddress = endPoint.Address.ToString(),
+            Port      = endPoint.Port,
+
+            // stuff below really should be done by Grafana/InfluxDb, but not possible (yet)
+            // aka hacks to get around limitations of Grafana/InfluxDb
+
+            NumberOfWarnings = activeWarnings.Count,
+            NumberOfAlarms   = activeAlarms.Count,
+
+            WarningsBitmap = data.ParseUInt64Register(1005),
+            AlarmsBitmap   = data.ParseUInt64Register(1009),
+
+            LastSeen       = lastSeen.ToInfluxTime()
+        };
+
+        static IReadOnlyCollection<String> Active(BatteryRecord record) => record
+                                                                          .GetFields()
+                                                                          .Where(f => f.value is true)
+                                                                          .Select(f => f.key)
+                                                                          .ToList();
+
+    }
+
+
+    private static Temperatures ParseTemperatures(this Data data, String installation, String batteryId)
+    {
+        return new Temperatures
+        {
+            Installation = installation,
+            BatteryId    = batteryId,
+
+            Battery          = data.ParseDecimalRegister(1003, 0.1m, -400m),
+            Board            = data.ParseDecimalRegister(1014, 0.1m, -400m),
+            Center           = data.ParseDecimalRegister(1015, 0.1m, -400m),
+            Lateral1         = data.ParseDecimalRegister(1016, 0.1m, -400m),
+            Lateral2         = data.ParseDecimalRegister(1017, 0.1m, -400m),
+            CenterHeaterPwm  = data.ParseDecimalRegister(1018, 0.1m),
+            LateralHeaterPwm = data.ParseDecimalRegister(1019, 0.1m),
+        };
+    }
+
+
+    private static String CheckProtocolId(String ascii)
+    {
+        var protocolId = ascii.Substring(0, Settings.ProtocolV3.Length);
+        if (protocolId != Settings.ProtocolV3)
+            throw new Exception($"Wrong protocol header: Expected '{Settings.ProtocolV3}' but got '{protocolId}'");
+
+        return protocolId;
+    }
+
+
+    private static IReadOnlyList<BatteryRecord> ParseBatteryRecords(Data data,
+        String installation,
+        DateTime lastSeen,
+        IPEndPoint endPoint)
+    {
+        var batteryId     = data.ParseBatteryId();
+        var warnings      = data.ParseWarnings     (installation, batteryId);
+        var alarms        = data.ParseAlarms       (installation, batteryId);
+        var leds          = data.ParseLeds         (installation, batteryId);
+        var temperatures  = data.ParseTemperatures (installation, batteryId);
+        var ioStatus      = data.ParseIoStatus     (installation, batteryId);
+
+        var batteryStatus = data.ParseBatteryStatus(installation,
+            batteryId,
+            temperatures.Battery,
+            warnings,
+            alarms,
+            lastSeen,
+            endPoint);
+
+        return new BatteryRecord[]
+        {
+            batteryStatus,
+            temperatures,
+            leds,
+            ioStatus,
+            warnings,
+            alarms
+        };
+    }
+
+
+    private static String ParseProtocolVersion (this Data data) => data.ParseString(0);
+    private static String ParseInstallationName(this Data data) => data.ParseString(1);
+    private static String ParseBatteryId       (this Data data) => data.ParseString(2);
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/BatteryDataParserV4.cs b/csharp/App_backup/Collector/src/BatteryDataParserV4.cs
new file mode 100644
index 000000000..632eb926e
--- /dev/null
+++ b/csharp/App_backup/Collector/src/BatteryDataParserV4.cs
@@ -0,0 +1,365 @@
+using System.Net;
+using System.Text;
+using InnovEnergy.App.Collector.Influx;
+using InnovEnergy.App.Collector.Records;
+using InnovEnergy.App.Collector.Utils;
+using InnovEnergy.Lib.Utils;
+using Convert = System.Convert;
+
+
+// NOT (YET) USED
+
+namespace InnovEnergy.App.Collector;
+
+using Data = IReadOnlyList<String>;
+
+public static class BatteryDataParserV4
+{
+    // public static LineProtocolPayload ParseV4Datagram(IPEndPoint endPoint, Byte[] buffer)
+    // {
+    //     var timeOfArrival = DateTime.UtcNow; // influx wants UTC
+    //
+    //     Log.Info($"Got V4 datagram from {endPoint}");
+    //
+    //     using var enumerator = buffer.ParseLengthValueEncoded().GetEnumerator();
+    //
+    //     var protocolVersion = enumerator
+    //                          .Next()
+    //                          .ToArray()
+    //                          .Apply(Encoding.UTF8.GetString)
+    //                          .Equals(Settings.ProtocolV4);
+    //
+    //     var nBatteries = enumerator.NextByte();
+    //
+    //     foreach (var _ in Enumerable.Range(0, nBatteries))
+    //     {
+    //         ParseBattery(enumerator);
+    //     }
+    //
+    //
+    //     return new LineProtocolPayload();
+    // }
+
+    private static void ParseBattery(IEnumerator<ArraySegment<Byte>> e)
+    {
+        // var hardwareVersion = e.NextString();
+        // var firmwareVersion = e.NextString();
+        // var bmsVersion      = e.NextString();
+        //
+        // var modbusData = e.Next();
+        //
+        // Int32 ReadRegisterAtIndex(Int32 index) => (modbusData[index * 2] << 8) + modbusData[index * 2 + 1];
+        // Int32 ReadRegister(Int32 register)     => ReadRegisterAtIndex(register - 999);
+        //
+        // Double ReadDouble(Int32 register, Double scaleFactor = 1, Double offset = 0)
+        // {
+        //     var value = ReadRegisterAtIndex(register - 999);
+        //
+        //     if (value > 0x8000)
+        //         value -= 0x10000; // fiamm stores their integers signed AND with sign-offset @#%^&!
+        //
+        //     return (value + offset) * scaleFactor;
+        // }
+
+        // TODO
+    }
+
+    private static Byte NextByte(this IEnumerator<ArraySegment<Byte>> enumerator)
+    {
+        return enumerator.Next().Single();
+    }
+
+    private static String NextString(this IEnumerator<ArraySegment<Byte>> enumerator)
+    {
+        return enumerator
+              .Next()
+              .ToArray()
+              .Apply(Encoding.UTF8.GetString);
+    }
+
+
+    private static String ParseString(this Data data, Int32 i) => data[i].Trim();
+    private static UInt16 ParseUInt16(this Data data, Int32 i) => UInt16.Parse(ParseString(data, i));
+
+    private static UInt16 ParseUInt16Register(this Data data, Int32 register)
+    {
+        var i = register.RegToIndex();
+        return data.ParseUInt16(i);
+    }
+
+    private static UInt32 ParseUInt32Register(this Data data, Int32 register)
+    {
+        var lo = ParseUInt16Register(data, register);
+        var hi = ParseUInt16Register(data, register + 1);
+
+        return Convert.ToUInt32(lo | (hi << 16));
+    }
+
+    private static UInt64 ParseUInt64Register (this Data data, Int32 register)
+    {
+        return Enumerable
+              .Range(0, 4)
+              .Select(i => Convert.ToUInt64(data.ParseUInt16Register(register + i)) << (i * 16))
+              .Aggregate(0ul, (a, b) => a + b);    // Sum() does not work for UInt64 :(
+    }
+
+    private static Decimal ParseDecimalRegister(this Data data,
+        Int32 register,
+        Decimal scaleFactor = 1,
+        Decimal offset = 0)
+    {
+        var i = register.RegToIndex();
+        UInt32 n = data.ParseUInt16(i);
+
+        if (n >= 0x8000)
+            n -= 0x10000; // fiamm stores their integers signed AND with sign-offset @#%^&!
+
+        return (Convert.ToDecimal(n) + offset) * scaleFactor; // according fiamm doc
+    }
+
+    private static Int32 RegToIndex(this Int32 register) => register - 992;
+
+
+    private static Leds ParseLeds(this Data data, String installation, String batteryId)
+    {
+        var ledBitmap = ParseUInt16Register(data, 1004);
+        LedState Led(Int32 n) => (LedState) (ledBitmap >> (n * 2) & 0b11);
+
+        return new Leds
+        {
+            Installation = installation,
+            BatteryId    = batteryId,
+
+            Green = Led(0),
+            Amber = Led(1),
+            Blue  = Led(2),
+            Red   = Led(3),
+        };
+    }
+
+
+    private static IoStatus ParseIoStatus(this Data data, String installation, String batteryId)
+    {
+        var ioStatusBitmap = data.ParseUInt16Register(1013);
+        Boolean IoStatus(Int32 b) => (ioStatusBitmap >> b & 1) > 0;
+
+        return new IoStatus
+        {
+            Installation = installation,
+            BatteryId    = batteryId,
+
+            MainSwitchClosed       = IoStatus(0),
+            AlarmOutActive         = IoStatus(1),
+            InternalFanActive      = IoStatus(2),
+            VoltMeasurementAllowed = IoStatus(3),
+            AuxRelay               = IoStatus(4),
+            RemoteState            = IoStatus(5),
+            HeatingOn              = IoStatus(6),
+        };
+    }
+
+    private static Warnings ParseWarnings(this Data data, String installation, String batteryId)
+    {
+        var warningsBitmap = data.ParseUInt64Register(1005);
+        Boolean Warning(Int32 b) => (warningsBitmap >> b & 1ul) > 0;
+
+        return new Warnings
+        {
+            Installation = installation,
+            BatteryId    = batteryId,
+
+            TaM1 = Warning(1),
+            TbM1 = Warning(4),
+            VBm1 = Warning(6),
+            VBM1 = Warning(8),
+            IDM1 = Warning(10),
+            vsM1 = Warning(24),
+            iCM1 = Warning(26),
+            iDM1 = Warning(28),
+            MID1 = Warning(30),
+            BLPW = Warning(32),
+            Ah_W = Warning(35),
+            MPMM = Warning(38),
+            TCMM = Warning(39),
+            TCdi = Warning(40),
+            LMPW = Warning(44)
+        };
+    }
+
+    private static Alarms ParseAlarms(this Data data, String installation, String batteryId)
+    {
+        var alarmsBitmap = data.ParseUInt64Register(1009);
+        Boolean Alarm(Int32 b) => (alarmsBitmap >> b & 1ul) > 0;
+
+        return new Alarms
+        {
+            Installation = installation,
+            BatteryId    = batteryId,
+
+            Tam  = Alarm(0),
+            TaM2 = Alarm(2),
+            Tbm  = Alarm(3),
+            TbM2 = Alarm(5),
+            VBm2 = Alarm(7),
+            VBM2 = Alarm(9),
+            IDM2 = Alarm(11),
+            ISOB = Alarm(12),
+            MSWE = Alarm(13),
+            FUSE = Alarm(14),
+            HTRE = Alarm(15),
+            TCPE = Alarm(16),
+            CME  = Alarm(18),
+            HWFL = Alarm(19),
+            HWEM = Alarm(20),
+            ThM  = Alarm(21),
+            vsm1 = Alarm(22),
+            vsm2 = Alarm(23),
+            vsM2 = Alarm(25),
+            iCM2 = Alarm(27),
+            iDM2 = Alarm(29),
+            MID2 = Alarm(31),
+            CCBF = Alarm(33),
+            AhFL = Alarm(34),
+            TbCM = Alarm(36),
+            HTFS = Alarm(42),
+            DATA = Alarm(43),
+            LMPA = Alarm(45),
+            HEBT = Alarm(46), 
+        };
+    }
+
+
+    private static BatteryStatus ParseBatteryStatus(this Data data,
+                                                       String installation,
+                                                       String batteryId,
+                                                      Decimal temperature,
+                                                     Warnings warnings,
+                                                       Alarms alarms,
+                                                     DateTime lastSeen,
+                                                   IPEndPoint endPoint)
+    {
+        var activeWarnings = Active(warnings);
+        var activeAlarms   = Active(alarms);
+
+        return new BatteryStatus
+        {
+            InstallationName = installation,
+            BatteryId        = batteryId,
+
+            HardwareVersion = data.ParseString(3),
+            FirmwareVersion = data.ParseString(4),
+            BmsVersion      = data.ParseString(5),
+            AmpereHours     = data.ParseUInt16(6),
+
+            Soc         = data.ParseDecimalRegister(1053, 0.1m),
+            Voltage     = data.ParseDecimalRegister(999, 0.01m),
+            Current     = data.ParseDecimalRegister(1000, 0.01m, -10000m),
+            BusVoltage  = data.ParseDecimalRegister(1001, 0.01m),
+            Temperature = temperature,
+
+            RtcCounter = data.ParseUInt32Register(1050),
+
+            IpAddress = endPoint.Address.ToString(),
+            Port      = endPoint.Port,
+
+            // stuff below really should be done by Grafana/InfluxDb, but not possible (yet)
+            // aka hacks to get around limitations of Grafana/InfluxDb
+
+            NumberOfWarnings = activeWarnings.Count,
+            NumberOfAlarms   = activeAlarms.Count,
+
+            WarningsBitmap = data.ParseUInt64Register(1005),
+            AlarmsBitmap   = data.ParseUInt64Register(1009),
+
+            LastSeen       = lastSeen.ToInfluxTime()
+        };
+
+        static IReadOnlyCollection<String> Active(BatteryRecord record) => record
+            .GetFields()
+            .Where(f => f.value is Boolean b && b)
+            .Select(f => f.key)
+            .ToList();
+
+    }
+
+
+    private static Temperatures ParseTemperatures(this Data data, String installation, String batteryId)
+    {
+        return new Temperatures
+        {
+            Installation = installation,
+            BatteryId    = batteryId,
+
+            Battery          = data.ParseDecimalRegister(1003, 0.1m, -400m),
+            Board            = data.ParseDecimalRegister(1014, 0.1m, -400m),
+            Center           = data.ParseDecimalRegister(1015, 0.1m, -400m),
+            Lateral1         = data.ParseDecimalRegister(1016, 0.1m, -400m),
+            Lateral2         = data.ParseDecimalRegister(1017, 0.1m, -400m),
+            CenterHeaterPwm  = data.ParseDecimalRegister(1018, 0.1m),
+            LateralHeaterPwm = data.ParseDecimalRegister(1019, 0.1m),
+        };
+    }
+
+
+
+    // private static LineProtocolPayload CreatePayload(params LineProtocolPoint[] points) =>
+    //     CreatePayload((IEnumerable<LineProtocolPoint>) points);
+    //
+    //
+    // private static LineProtocolPayload CreatePayload(IEnumerable<LineProtocolPoint> points)
+    // {
+    //     var payload = new LineProtocolPayload();
+    //
+    //     foreach (var point in points)
+    //         payload.Add(point);
+    //
+    //     return payload;
+    // }
+
+    private static String CheckProtocolId(String ascii)
+    {
+        var protocolId = ascii.Substring(0, Settings.ProtocolV3.Length);
+        if (protocolId != Settings.ProtocolV3)
+            throw new Exception($"Wrong protocol header: Expected '{Settings.ProtocolV3}' but got '{protocolId}'");
+
+        return protocolId;
+    }
+
+
+    private static IReadOnlyList<BatteryRecord> ParseBatteryRecords(Data data,
+        String installation,
+        DateTime lastSeen,
+        IPEndPoint endPoint)
+    {
+        var batteryId     = data.ParseBatteryId();
+        var warnings      = data.ParseWarnings     (installation, batteryId);
+        var alarms        = data.ParseAlarms       (installation, batteryId);
+        var leds          = data.ParseLeds         (installation, batteryId);
+        var temperatures  = data.ParseTemperatures (installation, batteryId);
+        var ioStatus      = data.ParseIoStatus     (installation, batteryId);
+
+        var batteryStatus = data.ParseBatteryStatus(installation,
+            batteryId,
+            temperatures.Battery,
+            warnings,
+            alarms,
+            lastSeen,
+            endPoint);
+
+        return new BatteryRecord[]
+        {
+            batteryStatus,
+            temperatures,
+            leds,
+            ioStatus,
+            warnings,
+            alarms
+        };
+    }
+
+
+    private static String ParseProtocolVersion (this Data data) => data.ParseString(0);
+    private static String ParseInstallationName(this Data data) => data.ParseString(1);
+    private static String ParseBatteryId       (this Data data) => data.ParseString(2);
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Influx/FieldAttribute.cs b/csharp/App_backup/Collector/src/Influx/FieldAttribute.cs
new file mode 100644
index 000000000..58dc3d0ba
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Influx/FieldAttribute.cs
@@ -0,0 +1,21 @@
+using static System.AttributeTargets;
+
+#nullable disable
+
+namespace InnovEnergy.App.Collector.Influx;
+
+[AttributeUsage(Property)]
+public class FieldAttribute : Attribute
+{
+    public FieldAttribute(Type type)
+    {
+        Type = type;
+    }
+
+    public FieldAttribute()
+    {
+        Type = null;
+    }
+
+    public Type Type { get; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Influx/InfluxRecord.cs b/csharp/App_backup/Collector/src/Influx/InfluxRecord.cs
new file mode 100644
index 000000000..8b67c547d
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Influx/InfluxRecord.cs
@@ -0,0 +1,92 @@
+using System.Text;
+using InnovEnergy.App.Collector.Utils;
+using InnovEnergy.Lib.Utils;
+using static System.Globalization.CultureInfo;
+using static InnovEnergy.App.Collector.Influx.LineProtocolSyntax;
+
+namespace InnovEnergy.App.Collector.Influx;
+
+public static class InfluxRecord
+{
+    public static IEnumerable<(String key, String value)> GetTags(this Object record)
+    {
+        return record
+            .GetProperties()
+            .Where(p => p.HasAttribute<TagAttribute>())
+            .Where(p => p.IsReadable)
+            .Select(p => (key: p.Name, value: ConvertTag(p)));
+
+    }
+
+    public static IEnumerable<(String key, Object? value)> GetFields(this Object record)
+    {
+        return record
+              .GetProperties()
+              .Where(p => p.HasAttribute<FieldAttribute>())
+              .Where(p => p.IsReadable)
+              .Select(p => (key: p.Name, value: ConvertField(p)));
+    }
+
+
+    private static Object? ConvertField(Property p)
+    {
+        var value = p.Get();
+        var type  = p.GetAttributes<FieldAttribute>().Single().Type;
+
+        return type != null
+             ? Convert.ChangeType(value, type, InvariantCulture)
+             : value;
+    }
+
+    private static String ConvertTag(Property p)
+    {
+        return p.Get()?.ToString()!;
+    }
+
+    public static String Serialize(this Object record)
+    {
+        var sb = new StringBuilder();
+
+        record.GetType()   // Measurement Name, TODO: NameAttribute
+            .Name
+            .Apply(EscapeName)
+            .Apply(sb.Append);
+
+        var tags = record
+            .GetTags()
+            .Where(t => !String.IsNullOrEmpty(t.value))
+            .OrderBy(t => t.key);
+
+        foreach (var (key, value) in tags)
+        {
+            sb.Append(',');
+            sb.Append(EscapeName(key));
+            sb.Append('=');
+            sb.Append(EscapeName(value));
+        }
+
+        var fieldDelimiter = ' ';
+
+        foreach (var (key, value) in record.GetFields())
+        {
+            sb.Append(fieldDelimiter);
+            fieldDelimiter = ',';
+            sb.Append(EscapeName(key));
+            sb.Append('=');
+            sb.Append(FormatValue(value!));
+        }
+
+
+        // TODO: timestamp handling
+
+        // let the DB add the timestamp
+
+        // if (point.UtcTimestamp != null)
+        // {
+        //     sb.Append(' ');
+        //     sb.Append(FormatTimestamp(point.UtcTimestamp.Value));
+        // }
+
+        return sb.ToString();
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Influx/LineProtocolSyntax.cs b/csharp/App_backup/Collector/src/Influx/LineProtocolSyntax.cs
new file mode 100644
index 000000000..2c55b3725
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Influx/LineProtocolSyntax.cs
@@ -0,0 +1,76 @@
+using System.Diagnostics.CodeAnalysis;
+using static System.Globalization.CultureInfo;
+
+namespace InnovEnergy.App.Collector.Influx;
+
+internal static class LineProtocolSyntax
+{
+    private static readonly DateTime Origin = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+
+    public static String EscapeName(String nameOrKey)
+    {
+        return nameOrKey
+              .Replace("=", "\\=")
+              .Replace(" ", "\\ ")
+              .Replace(",", "\\,");
+    }
+
+    [SuppressMessage("ReSharper", "RedundantVerbatimPrefix")]
+    public static String FormatValue(Object value)
+    {
+        return value switch
+        {
+            String   @string   => FormatString  (@string),
+            Single   @single   => FormatFloat   (@single),
+            Double   @double   => FormatFloat   (@double),
+            Int32    @int32    => FormatInteger (@int32),
+            UInt32   @uInt32   => FormatInteger (@uInt32),
+            Int64    @int64    => FormatInteger (@int64),
+            UInt64   @uInt64   => FormatInteger (@uInt64),
+            Decimal  @decimal  => FormatFloat   (@decimal),
+            Int16    @int16    => FormatInteger (@int16),
+            UInt16   @uInt16   => FormatInteger (@uInt16),
+            SByte    @sByte    => FormatInteger (@sByte),
+            Byte     @byte     => FormatInteger (@byte),
+            Boolean  @boolean  => FormatBoolean (@boolean),
+            TimeSpan @timeSpan => FormatTimespan(@timeSpan),
+            _                  => FormatString  (value.ToString())
+        };
+    }
+
+    private static String FormatInteger<T>(T i) where T: IFormattable
+    {
+        return FormatFloat(i) + "i";
+    }
+
+    private static String FormatFloat<T>(T f) where T: IFormattable
+    {
+        return f.ToString(format: null, InvariantCulture);
+    }
+
+    private static String FormatTimespan(TimeSpan timeSpan)
+    {
+        return timeSpan
+            .TotalMilliseconds
+            .ToString(InvariantCulture);
+    }
+
+    private static String FormatBoolean(Boolean b) => b ? "t" : "f";
+
+
+    private static String FormatString(Object? o)
+    {
+        var s = o?.ToString();
+
+        return s is null 
+             ? "<null>" 
+             : "\"" + s.Replace("\"", "\\\"") + "\"";
+    }
+
+    public static String FormatTimestamp(DateTime utcTimestamp)
+    {
+        var t = utcTimestamp - Origin;
+        return (t.Ticks * 100L).ToString(InvariantCulture);
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Influx/TagAttribute.cs b/csharp/App_backup/Collector/src/Influx/TagAttribute.cs
new file mode 100644
index 000000000..db028d6a8
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Influx/TagAttribute.cs
@@ -0,0 +1,8 @@
+using static System.AttributeTargets;
+
+namespace InnovEnergy.App.Collector.Influx;
+
+[AttributeUsage(Property)]
+public class TagAttribute : Attribute
+{
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Program.cs b/csharp/App_backup/Collector/src/Program.cs
new file mode 100644
index 000000000..9adb55306
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Program.cs
@@ -0,0 +1,173 @@
+using System.Net.Sockets;
+using System.Reactive.Concurrency;
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using System.Text.Json;
+using InnovEnergy.App.Collector.Influx;
+using InnovEnergy.App.Collector.Records;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Utils.Net;
+using InnovEnergy.Lib.WebServer;
+using static System.Text.Encoding;
+using static InnovEnergy.Lib.Utils.ExceptionHandling;
+
+
+namespace InnovEnergy.App.Collector;
+
+// dotnet publish Collector.csproj -c Release -r linux-x64 -p:PublishTrimmed=false -p:PublishSingleFile=true --self-contained true ; scp ./bin/Release/net6.0/linux-x64/publish/* ig@salidomo.innovenergy.ch:~/collector
+
+internal record BatteryData
+(
+    String Installation, 
+    String Battery, 
+    Double EnergyCapacity, 
+    Double EnergyStored,
+    Double Power
+);
+
+
+internal static class Program
+{
+    //private static readonly Logger Logger = new Logger(Settings.LoggingEndPoint);
+
+    private static UdpClient _incomingSocket = new UdpClient(Settings.IncomingEndPoint);
+    private static UdpClient _dbSocket       = new UdpClient();
+    
+    private static readonly Subject<BatteryData>  Batteries   = new Subject<BatteryData>();
+    private static readonly JsonSerializerOptions JsonOptions = new JsonSerializerOptions { WriteIndented = true };
+    
+    public static void Main(String[] args)
+    {
+        Task.Run(ServeJsonStats);
+
+        while (true)
+            ProcessDatagram();
+
+        // ReSharper disable once FunctionNeverReturns
+    }
+
+    private static void ServeJsonStats()
+    {
+        var json = "";
+        
+        Batteries.ObserveOn(TaskPoolScheduler.Default)
+                 .Buffer(TimeSpan.FromSeconds(4))
+                 .Select(b => b.GroupBy(d => d.Battery).Select(d => d.First()).ToList())
+                 .Select(ToJson)
+                 .Subscribe(j => json = j);
+
+        HttpResponse ServeRequest(HttpRequest httpRequest)
+        {
+            return new HttpResponse
+            {
+                Content     = json.Apply(UTF8.GetBytes),
+                ContentType = ContentType.ApplicationJson,
+                Headers     = new[] { new HttpHeader("Access-Control-Allow-Origin", "*") }
+            };
+        }
+
+        WebServer.ServeOnLocalHost(3333, ServeRequest);
+    }
+
+    private static String ToJson(IReadOnlyCollection<BatteryData> batteryData)
+    {
+        var nInstallations   = batteryData.GroupBy(d => d.Installation).Count();
+        var nBatteries       = batteryData.Count;
+        var energyStored     = batteryData.Sum(d => d.EnergyStored).Apply(Math.Round);
+        var energyCapacity   = batteryData.Sum(d => d.EnergyCapacity).Apply(Math.Round);
+        var chargingPower    = batteryData.Where(d => d.Power > 0).Sum(d => d.Power / 1000).Apply(Math.Round);
+        var dischargingPower = batteryData.Where(d => d.Power < 0).Sum(d => -d.Power / 1000).Apply(Math.Round);
+
+        var json = new
+        {
+            nInstallations,
+            nBatteries,
+            energyStored_kWh    = energyStored,
+            energyCapacity_kWh  = energyCapacity,
+            chargingPower_kW    = chargingPower,
+            dischargingPower_kW = dischargingPower,
+        };
+
+        return JsonSerializer.Serialize(json, JsonOptions);
+        
+        
+        // Console.WriteLine($"nInstallations  : {nInstallations}");
+        // Console.WriteLine($"nBatteries      : {nBatteries}");
+        // Console.WriteLine($"energyStored    : {Math.Round(energyStored)} kWh");
+        // Console.WriteLine($"energyCapacity  : {Math.Round(energyCapacity)} kWh");
+        // Console.WriteLine($"chargingPower   : {Math.Round(chargingPower / 1000)} kW");
+        // Console.WriteLine($"dischargingPower: {Math.Round(dischargingPower/ 1000)} kW");
+        
+    }
+
+
+    private static void ProcessDatagram()
+    {
+        ReadDatagram()
+            .ThenTry(ParseDatagram)
+            .ThenTry(SendToDb)
+            .OnErrorDo(Console.WriteLine);
+    }
+
+    private static Try<Byte[]> ParseDatagram(UdpDatagram datagram)
+    {
+        Byte[] Parse()
+        {
+            var batteryRecords = BatteryDataParser
+                                .ParseDatagram(datagram);
+
+            if (batteryRecords.FirstOrDefault() is BatteryStatus bs)
+            {
+                var battery      = bs.InstallationName + bs.BatteryId;
+                var capacity     = 48.0/1000 * bs.AmpereHours;
+                var energyStored = (Double) bs.Soc / 100 * capacity;
+                var power        = bs.Current * bs.Voltage;
+
+                var data = new BatteryData(bs.InstallationName, 
+                                           battery, 
+                                           capacity,
+                                           energyStored,
+                                           (Double)power);
+                
+                Batteries.OnNext(data);
+            }
+                
+            return batteryRecords
+                  .Select(InfluxRecord.Serialize)
+                  .JoinLines()
+                  .Apply(UTF8.GetBytes);
+        }
+
+        return Try(Parse)
+              .OnErrorLog("ParseDatagram failed " + datagram.EndPoint.Address);
+    }
+
+    private static Try<UdpDatagram> ReadDatagram()
+    {
+        return Try(_incomingSocket.ReadDatagram)
+              .OnErrorLog("Failed to read from UDP socket")
+              .OnErrorDo(ResetIncomingSocket);
+    }
+
+
+    private static Try<Int32> SendToDb(Byte[] data)
+    {
+        Int32 Send() => _dbSocket.SendDatagram(data, Settings.DbEndPoint);
+
+        return Try(Send)
+              .OnErrorLog("SendToDb failed")
+              .OnErrorDo(ResetDbSocket);
+    }
+
+    private static void ResetDbSocket(Exception e)
+    {
+        _dbSocket.Dispose();
+        _dbSocket = new UdpClient();
+    }
+
+    private static void ResetIncomingSocket(Exception e)
+    {
+        _incomingSocket.Dispose();
+        _incomingSocket = new UdpClient(Settings.IncomingEndPoint);
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Records/Alarms.cs b/csharp/App_backup/Collector/src/Records/Alarms.cs
new file mode 100644
index 000000000..4d995c6cf
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Records/Alarms.cs
@@ -0,0 +1,43 @@
+using InnovEnergy.App.Collector.Influx;
+
+// ReSharper disable IdentifierTypo
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+// ReSharper disable InconsistentNaming
+
+namespace InnovEnergy.App.Collector.Records;
+
+public class Alarms : BatteryRecord
+{
+    [Tag] public required String Installation { get; init; }
+    [Tag] public required String BatteryId    { get; init; }
+
+    [Field(typeof(Int32))] public required Boolean Tam  { get; init; }
+    [Field(typeof(Int32))] public required Boolean TaM2 { get; init; }
+    [Field(typeof(Int32))] public required Boolean Tbm  { get; init; }
+    [Field(typeof(Int32))] public required Boolean TbM2 { get; init; }
+    [Field(typeof(Int32))] public required Boolean VBm2 { get; init; }
+    [Field(typeof(Int32))] public required Boolean VBM2 { get; init; }
+    [Field(typeof(Int32))] public required Boolean IDM2 { get; init; }
+    [Field(typeof(Int32))] public required Boolean MSWE { get; init; }
+    [Field(typeof(Int32))] public required Boolean FUSE { get; init; }
+    [Field(typeof(Int32))] public required Boolean HTRE { get; init; }
+    [Field(typeof(Int32))] public required Boolean TCPE { get; init; }
+    [Field(typeof(Int32))] public required Boolean CME  { get; init; }
+    [Field(typeof(Int32))] public required Boolean HWFL { get; init; }
+    [Field(typeof(Int32))] public required Boolean HWEM { get; init; }
+    [Field(typeof(Int32))] public required Boolean ThM  { get; init; }
+    [Field(typeof(Int32))] public required Boolean vsm1 { get; init; }
+    [Field(typeof(Int32))] public required Boolean vsm2 { get; init; }
+    [Field(typeof(Int32))] public required Boolean vsM2 { get; init; }
+    [Field(typeof(Int32))] public required Boolean iCM2 { get; init; }
+    [Field(typeof(Int32))] public required Boolean iDM2 { get; init; }
+    [Field(typeof(Int32))] public required Boolean MID2 { get; init; }
+    [Field(typeof(Int32))] public required Boolean CCBF { get; init; }
+    [Field(typeof(Int32))] public required Boolean AhFL { get; init; }
+    [Field(typeof(Int32))] public required Boolean TbCM { get; init; }
+    [Field(typeof(Int32))] public required Boolean HTFS { get; init; }
+    [Field(typeof(Int32))] public required Boolean DATA { get; init; }
+    [Field(typeof(Int32))] public required Boolean ISOB { get; init; }
+    [Field(typeof(Int32))] public required Boolean LMPA { get; init; }
+    [Field(typeof(Int32))] public required Boolean HEBT { get; init; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Records/BatteryRecord.cs b/csharp/App_backup/Collector/src/Records/BatteryRecord.cs
new file mode 100644
index 000000000..49cfcc00c
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Records/BatteryRecord.cs
@@ -0,0 +1,4 @@
+namespace InnovEnergy.App.Collector.Records;
+
+public abstract class BatteryRecord
+{}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Records/BatteryStatus.cs b/csharp/App_backup/Collector/src/Records/BatteryStatus.cs
new file mode 100644
index 000000000..809248f21
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Records/BatteryStatus.cs
@@ -0,0 +1,32 @@
+using InnovEnergy.App.Collector.Influx;
+
+namespace InnovEnergy.App.Collector.Records;
+
+public class BatteryStatus : BatteryRecord
+{
+    [Tag]   public required String  InstallationName { get; init; }
+    [Tag]   public required String  BatteryId        { get; init; }
+
+    [Field] public required String  HardwareVersion  { get; init; }
+    [Field] public required String  FirmwareVersion  { get; init; }
+    [Field] public required String  BmsVersion       { get; init; }
+    [Field] public required UInt32  AmpereHours      { get; init; }
+    [Field] public required UInt32  RtcCounter       { get; init; }
+
+    [Field] public required Decimal Voltage          { get; init; }
+    [Field] public required Decimal Current          { get; init; }
+    [Field] public required Decimal BusVoltage       { get; init; }
+    [Field] public required Decimal Soc              { get; init; }
+    [Field] public required Decimal Temperature      { get; init; }
+
+    [Field] public required Int32   NumberOfWarnings { get; init; }
+    [Field] public required Int32   NumberOfAlarms   { get; init; }
+
+    [Field] public required UInt64  WarningsBitmap   { get; init; }
+    [Field] public required UInt64  AlarmsBitmap     { get; init; }
+
+    [Field] public required Int64   LastSeen         { get; init; }
+
+    [Field] public required String  IpAddress        { get; init; }
+    [Field] public required Int32   Port             { get; init; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Records/IoStatus.cs b/csharp/App_backup/Collector/src/Records/IoStatus.cs
new file mode 100644
index 000000000..ad3e04807
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Records/IoStatus.cs
@@ -0,0 +1,18 @@
+using InnovEnergy.App.Collector.Influx;
+
+
+namespace InnovEnergy.App.Collector.Records;
+
+public class IoStatus : BatteryRecord
+{
+    [Tag] public required String Installation { get; init; }
+    [Tag] public required String BatteryId    { get; init; }
+
+    [Field(typeof(Int32))] public required Boolean MainSwitchClosed       { get; init; }
+    [Field(typeof(Int32))] public required Boolean AlarmOutActive         { get; init; }
+    [Field(typeof(Int32))] public required Boolean InternalFanActive      { get; init; }
+    [Field(typeof(Int32))] public required Boolean VoltMeasurementAllowed { get; init; }
+    [Field(typeof(Int32))] public required Boolean AuxRelay               { get; init; }
+    [Field(typeof(Int32))] public required Boolean RemoteState            { get; init; }
+    [Field(typeof(Int32))] public required Boolean HeatingOn              { get; init; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Records/Leds.cs b/csharp/App_backup/Collector/src/Records/Leds.cs
new file mode 100644
index 000000000..8b197094b
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Records/Leds.cs
@@ -0,0 +1,26 @@
+using InnovEnergy.App.Collector.Influx;
+
+// ReSharper disable UnusedAutoPropertyAccessor.Global
+// ReSharper disable UnusedMember.Global
+
+namespace InnovEnergy.App.Collector.Records;
+
+public class Leds : BatteryRecord
+{
+    [Tag] public required String Installation { get; init; }
+    [Tag] public required String BatteryId    { get; init; }
+
+    [Field] public required LedState Green { get; set; }
+    [Field] public required LedState Amber { get; set; }
+    [Field] public required LedState Blue  { get; set; }
+    [Field] public required LedState Red   { get; set; }
+}
+
+
+public enum LedState : byte
+{
+    Off          = 0b00,
+    On           = 0b01,
+    BlinkingSlow = 0b10,
+    BlinkingFast = 0b11,
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Records/Temperatures.cs b/csharp/App_backup/Collector/src/Records/Temperatures.cs
new file mode 100644
index 000000000..34d53a92e
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Records/Temperatures.cs
@@ -0,0 +1,21 @@
+using InnovEnergy.App.Collector.Influx;
+
+namespace InnovEnergy.App.Collector.Records;
+
+
+#pragma warning disable CS8618
+
+public class Temperatures : BatteryRecord
+{
+
+    [Tag] public String Installation { get; set; }
+    [Tag] public String BatteryId    { get; set; }
+
+    [Field] public Decimal Battery          { get; init; }
+    [Field] public Decimal Board            { get; init; }
+    [Field] public Decimal Center           { get; init; }
+    [Field] public Decimal Lateral1         { get; init; }
+    [Field] public Decimal Lateral2         { get; init; }
+    [Field] public Decimal CenterHeaterPwm  { get; init; }
+    [Field] public Decimal LateralHeaterPwm { get; init; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Records/Warnings.cs b/csharp/App_backup/Collector/src/Records/Warnings.cs
new file mode 100644
index 000000000..6a1f50feb
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Records/Warnings.cs
@@ -0,0 +1,30 @@
+using System.Diagnostics.CodeAnalysis;
+using InnovEnergy.App.Collector.Influx;
+
+namespace InnovEnergy.App.Collector.Records;
+
+#pragma warning disable CS8618
+
+[SuppressMessage("ReSharper", "InconsistentNaming")]
+[SuppressMessage("ReSharper", "IdentifierTypo")]
+public class Warnings : BatteryRecord
+{
+    [Tag] public required String Installation { get; init; }
+    [Tag] public required String BatteryId    { get; init; }
+
+    [Field(typeof(Int32))] public required Boolean TaM1 { get; init; }
+    [Field(typeof(Int32))] public required Boolean TbM1 { get; init; }
+    [Field(typeof(Int32))] public required Boolean VBm1 { get; init; }
+    [Field(typeof(Int32))] public required Boolean VBM1 { get; init; }
+    [Field(typeof(Int32))] public required Boolean IDM1 { get; init; }
+    [Field(typeof(Int32))] public required Boolean vsM1 { get; init; }
+    [Field(typeof(Int32))] public required Boolean iCM1 { get; init; }
+    [Field(typeof(Int32))] public required Boolean iDM1 { get; init; }
+    [Field(typeof(Int32))] public required Boolean MID1 { get; init; }
+    [Field(typeof(Int32))] public required Boolean BLPW { get; init; }
+    [Field(typeof(Int32))] public required Boolean Ah_W { get; init; }
+    [Field(typeof(Int32))] public required Boolean MPMM { get; init; }
+    [Field(typeof(Int32))] public required Boolean TCMM { get; init; }
+    [Field(typeof(Int32))] public required Boolean TCdi { get; init; }
+    [Field(typeof(Int32))] public required Boolean LMPW { get; init; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Settings.cs b/csharp/App_backup/Collector/src/Settings.cs
new file mode 100644
index 000000000..7708dc819
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Settings.cs
@@ -0,0 +1,13 @@
+using System.Net;
+
+namespace InnovEnergy.App.Collector;
+
+public static class Settings
+{
+    public const String ProtocolV3 = "48TL200V3";
+    public const String ProtocolV4 = "48TL200V4";
+
+    public static readonly IPEndPoint IncomingEndPoint = IPEndPoint.Parse("10.2.0.1:8134");
+    public static readonly IPEndPoint DbEndPoint       = IPEndPoint.Parse("127.0.0.1:8089");
+    public static readonly IPEndPoint LoggingEndPoint  = IPEndPoint.Parse("127.0.0.1:6030");
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Utils/Extensions.cs b/csharp/App_backup/Collector/src/Utils/Extensions.cs
new file mode 100644
index 000000000..9b79e4ab4
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Utils/Extensions.cs
@@ -0,0 +1,44 @@
+using System.Globalization;
+
+namespace InnovEnergy.App.Collector.Utils;
+
+public static class Extensions
+{
+
+
+    public static IEnumerable<String> SplitBefore(this String s, String separator)
+    {
+        var start = 0;
+
+        Int32 NextSeparator() => s.IndexOf(separator, start + 1, StringComparison.Ordinal);
+
+        while (true)
+        {
+            var index = NextSeparator();
+
+            if (index < 0)
+            {
+                yield return s.Substring(start);
+                yield break;
+            }
+
+            yield return s.Substring(start, index-start);
+
+            start = index;
+        }
+    }
+
+    public static Int64 ToInfluxTime(this DateTime date)
+    {
+        var timeSinceEpoch = date.ToUniversalTime() - Utils.Epoch;
+        return Convert.ToInt64(timeSinceEpoch.TotalMilliseconds) ;
+    }
+
+    public static String FormatTimestamp(DateTime utcTimestamp)
+    {
+        return ((utcTimestamp - Utils.Epoch).Ticks * 100L).ToString(CultureInfo.InvariantCulture);
+    }
+
+    public static void Nop<T>(T t)
+    {}
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Utils/Log.cs b/csharp/App_backup/Collector/src/Utils/Log.cs
new file mode 100644
index 000000000..8e32f0174
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Utils/Log.cs
@@ -0,0 +1,39 @@
+namespace InnovEnergy.App.Collector.Utils;
+
+internal static class Log
+{
+    public static void Info(this String l)
+    {
+        Console.WriteLine($"[{DateTime.Now.ToLongTimeString()}] {l}");
+    }
+
+    public static void Error(this String l)
+    {
+        Console.WriteLine($"[{DateTime.Now.ToLongTimeString()}] {l}");
+    }
+
+
+    public static void Warning(this String l)
+    {
+        Console.WriteLine($"[{DateTime.Now.ToLongTimeString()}] {l}");
+    }
+
+    public static void Debug(this String l)
+    {
+        Console.WriteLine($"[{DateTime.Now.ToLongTimeString()}] {l}");
+    }
+
+    public static T Dump<T>(this T t)
+    {
+        Console.WriteLine(t);
+        return t;
+    }
+
+    public static void Dump<T>(this T l, Int32 n)
+    {
+        Console.WriteLine(l);
+
+        for (var i = 0; i < n; i++)
+            Console.WriteLine();
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Utils/Property.cs b/csharp/App_backup/Collector/src/Utils/Property.cs
new file mode 100644
index 000000000..9bf825c26
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Utils/Property.cs
@@ -0,0 +1,49 @@
+using System.Reflection;
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.Collector.Utils;
+
+public readonly struct Property
+{
+    public  Object       Instance     { get; }
+    private PropertyInfo PropertyInfo { get; }
+
+    public Property(Object instance, PropertyInfo propertyInfo)
+    {
+        Instance = instance ?? throw new ArgumentException("instance cannot be null", nameof(instance));
+        PropertyInfo = propertyInfo;
+    }
+
+    public Boolean IsWritable => PropertyInfo.CanWrite;
+    public Boolean IsReadable => PropertyInfo.CanRead;
+
+    public IEnumerable<Attribute> Attributes => GetAttributes<Attribute>();
+
+    public String Name => PropertyInfo.Name;
+    public Type   Type => PropertyInfo.PropertyType;
+
+    public Object? Get() => PropertyInfo.GetValue(Instance);
+
+    public T? Get<T>() => (T?) PropertyInfo.GetValue(Instance);
+
+    public void Set(Object value) => PropertyInfo.SetValue(Instance, value);
+
+    public IEnumerable<T> GetAttributes<T> () where T : Attribute => PropertyInfo
+                                                                    .GetCustomAttributes(inherit: false)
+                                                                    .OfType<T>();
+
+    public Boolean HasAttribute<T> () where T : Attribute => GetAttributes<T>().Any();
+
+}
+
+public static class PropertyExtensions
+{
+    public static IEnumerable<Property> GetProperties(this Object instance)
+    {
+        return instance
+              .GetType()
+              .GetProperties(BindingFlags.Instance | BindingFlags.Public)
+              .Unless(p => p.GetIndexParameters().Any())  // no indexers please
+              .Select(pi => new Property(instance, pi));
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Utils/ReadOnlyListExtensions.cs b/csharp/App_backup/Collector/src/Utils/ReadOnlyListExtensions.cs
new file mode 100644
index 000000000..7c8d915b3
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Utils/ReadOnlyListExtensions.cs
@@ -0,0 +1,45 @@
+namespace InnovEnergy.App.Collector.Utils;
+
+public static class ReadOnlyListExtensions
+{
+    public static IReadOnlyList<R> Select<T,R>(this IReadOnlyList<T> src, Func<T, R> map)
+    {
+        var a = new R[src.Count];
+        var i = 0;
+
+        foreach (var t in src)
+            a[i++] = map(t);
+
+        return a;
+    }
+
+    public static T[] ToArray<T>(this IReadOnlyCollection<T> src)
+    {
+        if (src is T[] a)
+            return a;
+
+        var array = new T[src.Count];
+        var i = 0;
+
+        foreach (var s in src)
+            array[i++] = s;
+
+        return array;
+    }
+
+
+    public static IReadOnlyList<R> SelectMany<T, R>(this IReadOnlyList<T> source, Func<T, IReadOnlyList<R>> selector)
+    {
+        var outer = source.Select(selector);
+        var n = outer.Sum(o => o.Count);
+
+        var inner = new R[n];
+        var i = 0;
+
+        foreach (var o in outer)
+        foreach (var p in o)
+            inner[i++] = p;
+
+        return inner;
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/Collector/src/Utils/Utils.cs b/csharp/App_backup/Collector/src/Utils/Utils.cs
new file mode 100644
index 000000000..4232c5656
--- /dev/null
+++ b/csharp/App_backup/Collector/src/Utils/Utils.cs
@@ -0,0 +1,19 @@
+namespace InnovEnergy.App.Collector.Utils;
+
+public static class Utils
+{
+    public static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+    public static IEnumerable<ArraySegment<Byte>> ParseLengthValueEncoded(this Byte[] source)
+    {
+        var index = 0;
+
+        while (index < source.Length)
+        {
+            var length = source[index++];
+
+            yield return new ArraySegment<Byte>(source, index, length);
+            index += length;
+        }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/DeligreenBatteryCommunication/DeligreenBatteryCommunication.csproj b/csharp/App_backup/DeligreenBatteryCommunication/DeligreenBatteryCommunication.csproj
new file mode 100644
index 000000000..176ad9f64
--- /dev/null
+++ b/csharp/App_backup/DeligreenBatteryCommunication/DeligreenBatteryCommunication.csproj
@@ -0,0 +1,22 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <PropertyGroup>
+        <RootNamespace>InnovEnergy.App.DeligreenBatteryCommunication</RootNamespace>
+    </PropertyGroup>
+    
+    
+    <Import Project="../InnovEnergy.App.props" />
+    
+    <ItemGroup>
+      <ProjectReference Include="../../Lib/Devices/BatteryDeligreen/BatteryDeligreen.csproj" />
+      <ProjectReference Include="..\..\Lib\Devices\Amax5070\Amax5070.csproj" />
+      <ProjectReference Include="..\..\Lib\Protocols\Modbus\Modbus.csproj" />
+      <ProjectReference Include="..\..\Lib\Units\Units.csproj" />
+    </ItemGroup>
+    
+    <ItemGroup>
+      <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.1.39" />
+      <PackageReference Include="System.IO.Ports" Version="7.0.0" />
+    </ItemGroup>
+
+</Project>
diff --git a/csharp/App_backup/DeligreenBatteryCommunication/DeviceState.cs b/csharp/App_backup/DeligreenBatteryCommunication/DeviceState.cs
new file mode 100644
index 000000000..4fac5fc9d
--- /dev/null
+++ b/csharp/App_backup/DeligreenBatteryCommunication/DeviceState.cs
@@ -0,0 +1,8 @@
+namespace InnovEnergy.App.DeligreenBatteryCommunication;
+
+public enum DeviceState
+{
+    Disabled,
+    Measured,
+    Computed
+}
\ No newline at end of file
diff --git a/csharp/App_backup/DeligreenBatteryCommunication/Program.cs b/csharp/App_backup/DeligreenBatteryCommunication/Program.cs
new file mode 100644
index 000000000..1ca6610a2
--- /dev/null
+++ b/csharp/App_backup/DeligreenBatteryCommunication/Program.cs
@@ -0,0 +1,124 @@
+using System.Net;
+using InnovEnergy.Lib.Devices.BatteryDeligreen;
+using InnovEnergy.Lib.Protocols.Modbus.Channels;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Utils.Net;
+
+namespace InnovEnergy.App.DeligreenBatteryCommunication;
+
+internal static class Program
+{
+    private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(2);
+
+    private static Channel _relaysChannel;
+
+    private const String Port = "/dev/ttyUSB0";
+    
+
+    static Program()
+    {
+        Console.WriteLine("Hello, Deligreen World!");
+
+        // BatteriesChannel  = new SerialPortChannel(Port, BaudRate, Parity, DataBits, StopBits);
+        
+    }
+
+    public static async Task Main(String[] args)
+    {
+        var device1 = new SalimaxDevice { Host = "10.0.1.3" , Port = 502, DeviceState = DeviceState.Measured };
+
+        _relaysChannel        = CreateChannel(device1);
+
+        var saliMaxRelaysDevice   = new RelaysDeviceAmax(_relaysChannel);
+        
+       /* var listOfBatteries = new List<BatteryDeligreenDevice>
+        {
+            new BatteryDeligreenDevice(Port, 0),
+            new BatteryDeligreenDevice(Port, 1)
+        };
+
+        var batteryDevices = new BatteryDeligreenDevices(listOfBatteries);*/
+    
+        Console.WriteLine("Starting Battery Communication");
+
+        while (true)
+        { 
+            try
+            {
+                var startTime = DateTime.Now;
+                Console.WriteLine("***************************** Reading  Battery Data  *********************************************");
+               // Console.WriteLine($"Start Reading all Batteries: {startTime}");
+                //var batteriesRecord     = batteryDevices.Read();
+                var stopTime            = DateTime.Now;
+                //Console.WriteLine($"Finish Reading all Batteries: {stopTime}");
+                var relays = saliMaxRelaysDevice.Read();
+
+                Console.WriteLine("***************************** Writing register 27 to true Amax  *********************************************");
+                
+                relays.K0 = true;
+                relays.K1 = true;
+                relays.R0 = true;
+                relays.R1 = true;
+                relays.R2 = true;
+                relays.R3 = true;
+              //  relays.K0Input.WriteLine(" : K0input");
+                saliMaxRelaysDevice.Write(relays);
+
+                Console.WriteLine("***************************** Reading  Amax  *********************************************");
+          
+
+               // relays.K1GridBusIsConnectedToGrid.WriteLine(" : K1");
+           
+                await Task.Delay(2000);
+                
+                Console.WriteLine("***************************** Writing register 27 to false  Amax  *********************************************");
+                relays.K0 = false;
+                relays.K1 = false;
+                relays.R0 = false;
+                relays.R1 = false;
+                relays.R2 = false;
+                relays.R3 = false;
+               // relays.K0Input.WriteLine(" : K0input");
+
+                saliMaxRelaysDevice.Write(relays);
+                
+                Console.WriteLine("***************************** Reading  Amax  *********************************************");
+
+
+                /*
+                 
+
+                Console.WriteLine("Time used for reading all batteries:" + (stopTime - startTime));
+                
+                 Console.WriteLine("Average SOC "     + batteriesRecord?.Soc);
+                 Console.WriteLine("Cell Alarm 1 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellAlarmList[0]);
+                 Console.WriteLine("Cell Alarm 2 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellAlarmList[1]);
+                 
+                 Console.WriteLine("Cell Temperature Alarm 1 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellTemperatureAlarm[0]);
+                 Console.WriteLine("Cell Temperature Alarm 2 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellTemperatureAlarm[1]);
+                 
+                 Console.WriteLine("Battery 1 EnviTemp Alarm: " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.EnviTempAlarm);
+                 Console.WriteLine("Battery 1 Current Alarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CurrentAlarm);
+                 
+                 Console.WriteLine("Battery 2 EnviTemp Alarm: " + batteriesRecord?.Devices[1].BatteryDeligreenAlarmRecord.EnviTempAlarm);
+                 Console.WriteLine("Battery 2 Current Alarm : " + batteriesRecord?.Devices[1].BatteryDeligreenAlarmRecord.CurrentAlarm);
+
+                 Console.WriteLine("TotalVoltage Alarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.TotalVoltageAlarm);
+                 Console.WriteLine("PowerTemp Alarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.PowerTempAlarm);*/
+                 
+                
+                // Wait for 2 seconds before the next reading
+                await Task.Delay(2000); // Delay in milliseconds (2000ms = 2 seconds)
+            }
+            catch (Exception e)
+            {
+                // Handle exception and print the error
+                Console.WriteLine(e + "   This the first try loop ");
+                await Task.Delay(2000); // Delay in milliseconds (2000ms = 2 seconds)
+            }    
+        }
+
+        Channel CreateChannel(SalimaxDevice device) => new TcpChannel(device);
+    }
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/DeligreenBatteryCommunication/RelaysDeviceAmax.cs b/csharp/App_backup/DeligreenBatteryCommunication/RelaysDeviceAmax.cs
new file mode 100644
index 000000000..9adef3106
--- /dev/null
+++ b/csharp/App_backup/DeligreenBatteryCommunication/RelaysDeviceAmax.cs
@@ -0,0 +1,38 @@
+using InnovEnergy.Lib.Devices.Amax5070;
+using InnovEnergy.Lib.Protocols.Modbus.Channels;
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.DeligreenBatteryCommunication;
+
+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}".WriteLine();
+            return null;
+        }
+    }
+
+    public void Write(RelaysRecordAmax r)
+    {
+        try
+        {
+            AmaxDevice.Write(r);
+        }
+        catch (Exception e)
+        {
+            $"Failed to write to {nameof(RelaysDeviceAmax)}\n{e}".WriteLine();
+        }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/DeligreenBatteryCommunication/RelaysRecordAmax.cs b/csharp/App_backup/DeligreenBatteryCommunication/RelaysRecordAmax.cs
new file mode 100644
index 000000000..af77d4ce1
--- /dev/null
+++ b/csharp/App_backup/DeligreenBatteryCommunication/RelaysRecordAmax.cs
@@ -0,0 +1,98 @@
+using InnovEnergy.Lib.Devices.Amax5070;
+
+namespace InnovEnergy.App.DeligreenBatteryCommunication;
+
+public class RelaysRecordAmax
+{
+    private readonly Amax5070Registers _regs;
+
+    private  RelaysRecordAmax(Amax5070Registers regs) => _regs = regs;
+    
+    //public UInt16 K0Input
+    //{
+    //    get => _regs.DigitalInput;
+    //}
+
+    
+    public Boolean K0
+    {
+        get => _regs.DigitalOutput0;
+        set => _regs.DigitalOutput0 = value;
+    }
+
+    public Boolean K1
+    {
+        get => _regs.DigitalOutput1;
+        set => _regs.DigitalOutput1 = value;
+    }
+
+    public Boolean K2
+    {
+        get => _regs.DigitalOutput2;
+        set => _regs.DigitalOutput2 = value;
+    }
+
+    public Boolean K3
+    {
+        get => _regs.DigitalOutput3;
+        set => _regs.DigitalOutput3 = value;
+    }
+
+
+    public Boolean R0
+    {
+        get => _regs.Relay12;
+        set => _regs.Relay12 = value;
+    }
+
+    public Boolean R1
+    {
+        get => _regs.Relay22;
+        set => _regs.Relay22 = value;
+    }
+
+    public Boolean R2
+    {
+        get => _regs.Relay32;
+        set => _regs.Relay32 = value;
+    }
+
+    public Boolean R3
+    {
+        get => _regs.Relay42;
+        set => _regs.Relay42 = value;
+    }
+
+
+    /*
+    public Boolean K1GridBusIsConnectedToGrid      => _regs.DigitalInput22;
+    public Boolean K2IslandBusIsConnectedToGridBus => !_regs.DigitalInput20;
+    public IEnumerable<Boolean> 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.Relay22; 
+    //    set => _regs.Relay22 = value;
+    //}
+    
+
+    public static implicit operator Amax5070Registers(RelaysRecordAmax d) => d._regs;
+    public static implicit operator RelaysRecordAmax(Amax5070Registers d) => new RelaysRecordAmax(d);
+}
\ No newline at end of file
diff --git a/csharp/App_backup/DeligreenBatteryCommunication/SalimaxDevice.cs b/csharp/App_backup/DeligreenBatteryCommunication/SalimaxDevice.cs
new file mode 100644
index 000000000..a31c73a99
--- /dev/null
+++ b/csharp/App_backup/DeligreenBatteryCommunication/SalimaxDevice.cs
@@ -0,0 +1,13 @@
+using InnovEnergy.Lib.Utils.Net;
+
+namespace InnovEnergy.App.DeligreenBatteryCommunication;
+
+public class SalimaxDevice : Ip4Address
+{
+    public required DeviceState DeviceState { get; init; }
+
+    public override String ToString() => $"{base.ToString()} ({DeviceState})";
+
+    public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), DeviceState);
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/DeligreenBatteryCommunication/deploy.sh b/csharp/App_backup/DeligreenBatteryCommunication/deploy.sh
new file mode 100755
index 000000000..05983dda8
--- /dev/null
+++ b/csharp/App_backup/DeligreenBatteryCommunication/deploy.sh
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+dotnet_version='net6.0'
+salimax_ip="$1"
+username='ie-entwicklung'
+root_password='Salimax4x25'
+set -e
+
+echo -e "\n============================ Build ============================\n" 
+
+dotnet publish  \
+       ./DeligreenBatteryCommunication.csproj \
+       -p:PublishTrimmed=false \
+       -c Release \
+       -r linux-x64 
+
+echo -e "\n============================ Deploy ============================\n" 
+
+rsync -v \
+      --exclude '*.pdb' \
+      ./bin/Release/$dotnet_version/linux-x64/publish/* \
+       $username@"$salimax_ip":~/salimax
\ No newline at end of file
diff --git a/csharp/App_backup/EmuMeterDriver/Config.cs b/csharp/App_backup/EmuMeterDriver/Config.cs
new file mode 100644
index 000000000..c78c9490d
--- /dev/null
+++ b/csharp/App_backup/EmuMeterDriver/Config.cs
@@ -0,0 +1,65 @@
+using System.Reflection;
+using InnovEnergy.Lib.Victron.VeDBus;
+
+namespace InnovEnergy.App.EmuMeterDriver;
+
+public static class Config
+{
+    public const String  Version = "1.0";
+    public const String  BusName = "com.victronenergy.grid.emu";
+    public const Byte    ModbusNodeId = 1;
+    public const String  OwnAddress  = "10.0.0.1";
+    public const String  PeerAddress = "10.0.0.2";
+    //public const String  PeerAddress = "127.0.0.1";
+    public const UInt16  PeerPort = 502;
+
+    public static TimeSpan TcpTimeout { get; } = TimeSpan.FromSeconds(2);
+
+
+    public static readonly TimeSpan UpdatePeriod = TimeSpan.FromSeconds(1);
+    
+    public static readonly IReadOnlyList<Signal> Signals = new Signal[]
+    {
+        new(s => s.Ac.L1.Current, "/Ac/L1/Current", "0.0 A"),
+        new(s => s.Ac.L2.Current, "/Ac/L2/Current", "0.0 A"),
+        new(s => s.Ac.L3.Current, "/Ac/L3/Current", "0.0 A"),
+        new(s => s.Ac.L1.Current + s.Ac.L2.Current + s.Ac.L3.Current, "/Ac/Current", "0.0 A"),
+
+        new(s => s.Ac.L1.Voltage, "/Ac/L1/Voltage", "0.0 A"),
+        new(s => s.Ac.L2.Voltage, "/Ac/L2/Voltage", "0.0 A"),
+        new(s => s.Ac.L3.Voltage, "/Ac/L3/Voltage", "0.0 A"),
+        new(s => (s.Ac.L1.Voltage + s.Ac.L2.Voltage + s.Ac.L3.Voltage) / 3, "/Ac/Voltage", "0.0 A"),
+
+        new(s => s.Ac.L1.Power.Active, "/Ac/L1/Power", "0 W"),
+        new(s => s.Ac.L2.Power.Active, "/Ac/L2/Power", "0 W"),
+        new(s => s.Ac.L3.Power.Active, "/Ac/L3/Power", "0 W"),
+        new(s => s.Ac.Power.Active, "/Ac/Power", "0 W"),
+
+        // new(s => s.EnergyImportL123, "Ac/Energy/Forward", "0.00 kWh"),
+        // new(s => s.EnergyExportL123, "Ac/Energy/Reverse", "0.00 kWh"),
+        //
+        // new(s => s.EnergyImportL1, "Ac/L1/Energy/Forward", "0.00 kWh"),
+        // new(s => s.EnergyExportL1, "Ac/L1/Energy/Reverse", "0.00 kWh"),
+        //
+        // new(s => s.EnergyImportL2, "Ac/L2/Energy/Forward", "0.00 kWh"),
+        // new(s => s.EnergyExportL2, "Ac/L2/Energy/Reverse", "0.00 kWh"),
+        //
+        // new(s => s.EnergyImportL3, "Ac/L3/Energy/Forward", "0.00 kWh"),
+        // new(s => s.EnergyExportL3, "Ac/L3/Energy/Reverse", "0.00 kWh"),
+    };
+    
+    public static VeProperties DefaultProperties => new VeProperties
+    {
+        new("/ProductName"        , "Grid meter" ),
+        new("/CustomName"         , "EMU Professional II"),
+        new("/DeviceInstance"     , 30),
+        new("/DeviceType"         , 72),
+        new("/Mgmt/Connection"    , "Modbus TCP"),
+        new("/Mgmt/ProcessName"   , Assembly.GetEntryAssembly()?.Location ?? "unknown"),
+        new("/Mgmt/ProcessVersion", Version),
+        new("/Connected"          , 1),
+        new("/ProductId"          , 45058, "b002"),
+        new("/Role"               , "grid"),
+    };
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/EmuMeterDriver/EmuMeterDriver.cs b/csharp/App_backup/EmuMeterDriver/EmuMeterDriver.cs
new file mode 100644
index 000000000..0aa36aa30
--- /dev/null
+++ b/csharp/App_backup/EmuMeterDriver/EmuMeterDriver.cs
@@ -0,0 +1,66 @@
+using System.Reactive.Linq;
+using InnovEnergy.Lib.Devices.EmuMeter;
+using InnovEnergy.Lib.Protocols.DBus;
+using InnovEnergy.Lib.Protocols.Modbus.Clients;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Victron.VeDBus;
+
+namespace InnovEnergy.App.EmuMeterDriver;
+
+public static class EmuMeterDriver
+{
+    public static Task<Exception> Run(String hostName, Bus dbusAddress)
+    {
+        return Run(hostName, ModbusTcpClient.DefaultPort, dbusAddress);
+    }
+
+    public static async Task<Exception> Run(String hostName, UInt16 port, Bus dbusAddress)
+    {
+        // var ep = new UnixDomainSocketEndPoint("/home/eef/graber_dbus.sock");
+        // var auth = AuthenticationMethod.ExternalAsRoot();
+        // dbusAddress = new Bus(ep, auth);
+
+        var emuMeter = new EmuMeterDevice(hostName, port, Config.ModbusNodeId);
+        
+        
+        var meterStatus = Observable
+                         .Interval(Config.UpdatePeriod)
+                         .Select(_ => emuMeter.Read())
+                         .Publish();
+
+        var poller = meterStatus.Connect();
+
+        var properties = Config.DefaultProperties;
+
+        var signals = Config
+                     .Signals
+                     .Select(signal => meterStatus.Select(signal.ToVeProperty))
+                     .Merge()
+                     .Do(p => properties.Set(p));
+
+        // TODO: remove when possible
+        // Apparently some VE services need to be periodically reminded that
+        // this service is /Connected
+        meterStatus.Subscribe(_ => properties.Set("/Connected", 1));
+        
+        // Wait until status is read once to make sure all
+        // properties are set when we go onto the bus.
+        var dbus = meterStatus
+                  .Skip(1)
+                  .Take(1)
+                  .SelectMany(_ => PublishPropertiesOnDBus(properties, dbusAddress));
+
+        return await signals
+                    .MergeErrors(dbus)
+                    .Finally(poller.Dispose)
+                    .SelectErrors();
+
+    }
+
+    
+    private static Task<Exception> PublishPropertiesOnDBus(VeProperties properties, Bus bus)
+    {
+        Console.WriteLine($"Connecting to DBus {bus}");
+        return properties.PublishOnDBus(bus, Config.BusName);
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/EmuMeterDriver/EmuMeterDriver.csproj b/csharp/App_backup/EmuMeterDriver/EmuMeterDriver.csproj
new file mode 100644
index 000000000..5afaaff4a
--- /dev/null
+++ b/csharp/App_backup/EmuMeterDriver/EmuMeterDriver.csproj
@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    
+    <PropertyGroup>
+        <RootNamespace>InnovEnergy.App.EmuMeterDriver</RootNamespace>
+    </PropertyGroup>
+    
+    <Import Project="../InnovEnergy.App.props" />
+
+    <ItemGroup>
+      <ProjectReference Include="../../Lib/Devices/EmuMeter/EmuMeter.csproj" />
+      <ProjectReference Include="../../Lib/Protocols/DBus/DBus.csproj" />
+      <ProjectReference Include="../../Lib/Protocols/Modbus/Modbus.csproj" />
+      <ProjectReference Include="../../Lib/Utils/Utils.csproj" />
+      <ProjectReference Include="../../Lib/Victron/VeDBus/VeDBus.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <PackageReference Include="CliWrap" Version="3.6.0" />
+    </ItemGroup>
+    
+    
+
+</Project>
diff --git a/csharp/App_backup/EmuMeterDriver/Nic.cs b/csharp/App_backup/EmuMeterDriver/Nic.cs
new file mode 100644
index 000000000..2c507cb8d
--- /dev/null
+++ b/csharp/App_backup/EmuMeterDriver/Nic.cs
@@ -0,0 +1,149 @@
+using System.Text.Json.Nodes;
+using CliWrap;
+using CliWrap.Buffered;
+
+namespace InnovEnergy.App.EmuMeterDriver;
+
+public readonly struct Nic
+{
+    private static Command IpCommand { get; } = Cli
+                                               .Wrap("/sbin/ip")
+                                               .WithValidation(CommandResultValidation.None);
+    
+    private readonly JsonNode _Node;
+
+    private Nic(JsonNode node)
+    {
+        _Node = node;
+    }
+
+    public Boolean IsEthernet
+    {
+        get
+        {
+            try
+            {
+                return _Node["link_type"]!.GetValue<String>() == "ether";
+            }
+            catch
+            {
+                return false;
+            }
+        }
+    }
+
+    public Boolean IsUp
+    {
+        get
+        {
+            // ReSharper disable once StringLiteralTypo
+            try
+            {
+                return _Node["operstate"]!.GetValue<String>() == "UP";
+            }
+            catch
+            {
+                return false;
+            }
+        }
+    }
+
+    public IReadOnlyList<String> Ip4Addresses
+    {
+        get
+        {
+            // ReSharper disable once StringLiteralTypo
+            try
+            {
+                return _Node["addr_info"]!
+                       .AsArray()
+                       .TryWhere(n => n!["family"]!.GetValue<String>() == "inet")
+                       .TrySelect(n => n!["local"]!.GetValue<String>())
+                       .ToList();
+            }
+            catch
+            {
+                return Array.Empty<String>();
+            }
+        }
+    }
+
+    public String Name
+    {
+        get
+        {
+            // ReSharper disable once StringLiteralTypo
+            try
+            {
+                return _Node["ifname"]!.GetValue<String>();
+            }
+            catch
+            {
+                return "<NO_NAME>";
+            }
+        }
+    }
+    
+  
+
+    public async Task<Boolean> AddPointToPoint(String sourceAddress, String destinationAddress)
+    {
+        var result = await IpCommand
+                          .WithArguments($"address add local {sourceAddress} peer {destinationAddress} dev {Name}")
+                          .ExecuteAsync();
+
+        return result.ExitCode == 0;
+    }
+    
+    public async Task<Boolean> RemoveAddress(String address)
+    {
+        var result = await IpCommand
+                          .WithArguments($"address del {address} dev {Name}")
+                          .ExecuteBufferedAsync();
+
+        return result.ExitCode == 0;
+    }
+
+
+    public  async Task<Boolean> AddRoute(String route)
+    {
+        var result = await IpCommand
+                          .WithArguments($"route add {route} dev {Name}")
+                          .ExecuteAsync();
+
+        return result.ExitCode == 0;
+    }
+
+    public async Task<Boolean> RemoveRoute(String route)
+    {
+        var result = await IpCommand
+                          .WithArguments($"route del {route} dev {Name}")
+                          .ExecuteAsync();
+
+        return result.ExitCode == 0;
+    }
+
+  
+    public static async Task<IReadOnlyList<Nic>> GetNetworkInterfaces()
+    {
+
+        try
+        {
+             var result = await IpCommand
+                               .WithArguments("-details -pretty -json address")
+                               .ExecuteBufferedAsync();
+
+             return JsonNode
+                   .Parse(result.StandardOutput)!
+                   .AsArray()
+                   .Where(n => n != null)
+                   .Select(n => new Nic(n!))
+                   .ToList();
+        }
+        catch 
+        {
+            return Array.Empty<Nic>();
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/EmuMeterDriver/Program.cs b/csharp/App_backup/EmuMeterDriver/Program.cs
new file mode 100644
index 000000000..6ad63d04d
--- /dev/null
+++ b/csharp/App_backup/EmuMeterDriver/Program.cs
@@ -0,0 +1,60 @@
+using InnovEnergy.App.EmuMeterDriver;
+using InnovEnergy.Lib.Protocols.DBus;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Utils.Net;
+
+
+// dotnet publish EmuMeter.csproj -c Release -r linux-arm -p:PublishSingleFile=true --self-contained true && \
+// rsync -av bin/Release/net6.0/linux-arm/publish/ root@10.2.1.6:/home/root/emu && clear && \
+// ssh root@10.2.1.6 /home/root/emu/EmuMeter
+
+
+Console.WriteLine("Starting EmuMeter Driver " + Config.Version);
+
+var networkInterfaces = await Nic.GetNetworkInterfaces();
+
+var candidates = networkInterfaces.Where(n => n.IsUp && 
+                                              n.IsEthernet && 
+                                             (!n.Ip4Addresses.Any() || n.Ip4Addresses.Contains(Config.OwnAddress)));
+
+foreach (var nic in candidates)
+{
+    Console.WriteLine($"Found new network interface: {nic.Name}");
+    
+    if (!nic.Ip4Addresses.Contains(Config.OwnAddress))
+    {
+        Console.WriteLine($"Configuring Point-to-Point connection on {nic.Name}");
+        Console.WriteLine($"  own address:  {Config.OwnAddress}");
+        Console.WriteLine($"  peer address: {Config.PeerAddress}");
+        
+        var success = await nic.AddPointToPoint($"{Config.OwnAddress}/16", $"{Config.PeerAddress}/16");
+
+        if (!success)
+        {
+            Console.WriteLine($"Failed to configure network interface: {nic.Name}");
+            continue;
+        }    
+    }
+
+    Console.WriteLine($"Pinging peer @ {Config.PeerAddress}");
+    
+    var ping = await Config.PeerAddress.Ping();
+
+    if (ping)
+    {
+        Console.WriteLine($"Got answer from {Config.PeerAddress}");
+        var ex = await EmuMeterDriver.Run($"{Config.PeerAddress}:{Config.PeerPort}", Bus.System);
+
+        Console.WriteLine($"{nameof(EmuMeterDriver)} FAILED with\n{ex}");
+    }
+    else
+    {
+        Console.WriteLine($"No answer from {Config.PeerAddress}");
+    }
+
+    Console.Write($"Removing Point-to-Point connection on {nic.Name} ...");
+    var removed = await nic.RemoveAddress($"{Config.OwnAddress}/16");
+    Console.WriteLine(removed ? "done" : "failed");
+}
+
+Console.WriteLine("Stopping EmuMeter Driver");
\ No newline at end of file
diff --git a/csharp/App_backup/EmuMeterDriver/Signal.cs b/csharp/App_backup/EmuMeterDriver/Signal.cs
new file mode 100644
index 000000000..7d22b547b
--- /dev/null
+++ b/csharp/App_backup/EmuMeterDriver/Signal.cs
@@ -0,0 +1,16 @@
+using InnovEnergy.Lib.Devices.EmuMeter;
+using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes;
+using InnovEnergy.Lib.Victron.VeDBus;
+
+namespace InnovEnergy.App.EmuMeterDriver;
+
+
+// TODO: Does not compile
+public record Signal(Func<EmuMeterRegisters, Object> Source, ObjectPath Path, String Format = "")
+{
+    public VeProperty ToVeProperty(EmuMeterRegisters status)
+    {
+        var value = Source(status);
+        return new VeProperty(Path, value, String.Format($"{{0:{Format}}}", value));
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/EmuMeterDriver/Utils.cs b/csharp/App_backup/EmuMeterDriver/Utils.cs
new file mode 100644
index 000000000..65f02de6a
--- /dev/null
+++ b/csharp/App_backup/EmuMeterDriver/Utils.cs
@@ -0,0 +1,49 @@
+namespace InnovEnergy.App.EmuMeterDriver;
+
+public static class Utils
+{
+    public static IEnumerable<T> TryWhere<T>(this IEnumerable<T> src, Func<T, Boolean> predicate)
+    {
+        foreach (var e in src)
+        {
+            var ok = false;
+            
+            try
+            {
+                ok = predicate(e);
+            }
+            catch
+            {
+                // ignored
+            }
+
+            if (ok)
+                yield return e;
+        }
+    }
+    
+    public static IEnumerable<R> TrySelect<T,R>(this IEnumerable<T> src, Func<T, R> map)
+    {
+        foreach (var e in src)
+        {
+            var ok = false;
+            var result = default(R);
+            
+            try
+            {
+                result = map(e);
+                ok = true;
+            }
+            catch
+            {
+                // ignored
+            }
+
+            if (ok)
+                yield return result!;
+        }
+    }
+    
+
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/EmuMeterDriver/debug.sh b/csharp/App_backup/EmuMeterDriver/debug.sh
new file mode 100644
index 000000000..799b56950
--- /dev/null
+++ b/csharp/App_backup/EmuMeterDriver/debug.sh
@@ -0,0 +1,20 @@
+#!/bin/bash
+
+csproj="EmuMeter.csproj"
+exe="EmuMeter"
+#remote="10.2.1.6"
+remote="10.2.2.152"
+
+netVersion="net6.0"
+platform="linux-arm"
+config="Release"
+host="root@$remote"
+dir="/opt/innovenergy/$exe"
+
+set -e
+
+dotnet publish "$csproj" -c $config -r $platform -p:SuppressTrimmAnalysisWarnings=true -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false --self-contained true 
+rsync -av "bin/$config/$netVersion/$platform/publish/" "$host:$dir" 
+#clear 
+#ssh "$host" "$dir/$exe"
+  
\ No newline at end of file
diff --git a/csharp/App_backup/EmuMeterDriver/deploy.sh b/csharp/App_backup/EmuMeterDriver/deploy.sh
new file mode 100644
index 000000000..eacfec15f
--- /dev/null
+++ b/csharp/App_backup/EmuMeterDriver/deploy.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+
+csproj="SchneiderMeterDriver.csproj"
+exe="SchneiderMeterDriver"
+remote="10.2.4.114"
+platform="linux-arm"
+netVersion="net6.0"
+config="Release"
+host="root@$remote"
+dir="/opt/innovenergy/$exe"
+log_dir="/var/log/SchneiderMeterDriver"
+
+set -e
+
+# Publish the project locally
+dotnet publish "$csproj" -c $config -r $platform -p:SuppressTrimmAnalysisWarnings=true -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false --self-contained true
+
+# Sync the published files to the remote server
+rsync -av "bin/$config/$netVersion/$platform/publish/" "$host:$dir"
+
+# Execute commands on the remote server
+ssh "$host" << 'EOF'
+set -e
+
+# Remount the root filesystem with read and write permissions
+mount -o remount,rw /
+
+# Create service and log directories
+mkdir -p /opt/innovenergy/SchneiderMeterDriver/service
+mkdir -p /opt/innovenergy/SchneiderMeterDriver/service/log
+mkdir -p /var/log/SchneiderMeterDriver
+
+# Create the service run script
+cat << 'EOL' > /opt/innovenergy/SchneiderMeterDriver/service/run
+#!/bin/sh
+exec 2>&1
+exec softlimit -d 200000000 -s 2000000 -a 200000000 /opt/innovenergy/SchneiderMeterDriver/SchniederDriver
+EOL
+chmod +x /opt/innovenergy/SchneiderMeterDriver/service/run
+
+# Create the log run script
+cat << 'EOL' > /opt/innovenergy/SchneiderMeterDriver/service/log/run
+#!/bin/sh
+exec 2>&1
+exec multilog t s25000 n4 /var/log/SchneiderMeterDriver
+EOL
+chmod +x /opt/innovenergy/SchneiderMeterDriver/service/log/run
+
+# Create the symbolic link for the service
+ln -sf /opt/innovenergy/SchneiderMeterDriver/service /service/SchneiderMeterDriver
+
+# Wait a bit for the symlink to be recognized
+
+sleep 2
+
+# Start the service
+start SchneiderMeterDriver
+EOF
+
+echo "Deployment and service setup completed successfully."
diff --git a/csharp/App_backup/EmuMeterDriver/service/log/run b/csharp/App_backup/EmuMeterDriver/service/log/run
new file mode 100755
index 000000000..62e2f8679
--- /dev/null
+++ b/csharp/App_backup/EmuMeterDriver/service/log/run
@@ -0,0 +1,3 @@
+#!/bin/sh
+exec 2>&1
+exec multilog t s25000 n4 /var/log/EmuMeter
diff --git a/csharp/App_backup/EmuMeterDriver/service/run b/csharp/App_backup/EmuMeterDriver/service/run
new file mode 100755
index 000000000..58cdb6ff7
--- /dev/null
+++ b/csharp/App_backup/EmuMeterDriver/service/run
@@ -0,0 +1,3 @@
+#!/bin/sh
+exec 2>&1
+exec softlimit -d 100000000 -s 1000000 -a 100000000 /opt/innovenergy/EmuMeter/EmuMeter
diff --git a/csharp/App_backup/InnovEnergy.App.props b/csharp/App_backup/InnovEnergy.App.props
new file mode 100644
index 000000000..0cf09ae83
--- /dev/null
+++ b/csharp/App_backup/InnovEnergy.App.props
@@ -0,0 +1,25 @@
+<Project>
+
+    <Import Project="../InnovEnergy.props" />
+
+    <PropertyGroup Condition=" '$(Configuration)' != 'Debug' ">
+        <!-- https://learn.microsoft.com/en-us/dotnet/core/deploying/trimming/trimming-options?pivots=dotnet-7-0 -->
+        <RuntimeIdentifier>linux-x64</RuntimeIdentifier>
+        <SelfContained>true</SelfContained>
+        <PublishTrimmed>true</PublishTrimmed>
+        <DebuggerSupport>false</DebuggerSupport>
+        <PublishSingleFile>true</PublishSingleFile>
+        <PublishReadyToRun>true</PublishReadyToRun>
+        <TrimmerRemoveSymbols>true</TrimmerRemoveSymbols>
+    </PropertyGroup>
+
+    <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+        <DebugType>full</DebugType>
+    </PropertyGroup>
+
+    <PropertyGroup>
+        <OutputType>Exe</OutputType>
+    </PropertyGroup>
+
+</Project>  
+    
\ No newline at end of file
diff --git a/csharp/App_backup/OpenVpnCertificatesServer/AccToken.cs b/csharp/App_backup/OpenVpnCertificatesServer/AccToken.cs
new file mode 100644
index 000000000..bd8ea28e6
--- /dev/null
+++ b/csharp/App_backup/OpenVpnCertificatesServer/AccToken.cs
@@ -0,0 +1,17 @@
+using System.Diagnostics.CodeAnalysis;
+
+namespace InnovEnergy.App.OpenVpnCertificatesServer;
+
+
+[SuppressMessage("ReSharper", "InconsistentNaming")]
+[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)]
+internal class AccToken
+{
+    public String bearer { get; init; } = "";
+    public UInt64 idUser      { get; init; }
+    public String verification_mode { get; init; } = "password";
+    public Boolean verification_sent { get; init; } = false;
+    public Boolean success { get; init; } = false;
+    public String token { get; init; } = "";
+    public String idAccessToken { get; init; } = "";
+}
\ No newline at end of file
diff --git a/csharp/App_backup/OpenVpnCertificatesServer/Files.cs b/csharp/App_backup/OpenVpnCertificatesServer/Files.cs
new file mode 100644
index 000000000..00fbf58d5
--- /dev/null
+++ b/csharp/App_backup/OpenVpnCertificatesServer/Files.cs
@@ -0,0 +1,35 @@
+namespace InnovEnergy.App.OpenVpnCertificatesServer;
+
+public static class Files
+{
+    public const String ReadMeTxt = @"
+for systemd:
+   store everything in /etc/openvpn/client
+   sudo systemctl start openvpn-client@innovenergy.service
+   sudo systemctl enable openvpn-client@innovenergy.service
+
+otherwise:
+    openvpn --config innovenergy.conf
+";
+
+    public const String OvpnConf = @"
+client
+nobind
+resolv-retry infinite
+ca ca-certificate
+cert client-certificate
+key client-key
+remote salidomo.innovenergy.ch
+port 7002
+proto udp
+dev innovenergy
+dev-type tun
+auth SHA256
+cipher AES-256-CBC
+verb 3
+keepalive 10 120
+persist-key
+persist-tun
+";
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/OpenVpnCertificatesServer/Http.cs b/csharp/App_backup/OpenVpnCertificatesServer/Http.cs
new file mode 100644
index 000000000..5442047ae
--- /dev/null
+++ b/csharp/App_backup/OpenVpnCertificatesServer/Http.cs
@@ -0,0 +1,48 @@
+using System.Net;
+using Flurl;
+
+namespace InnovEnergy.App.OpenVpnCertificatesServer;
+
+public static class Http
+{
+    // TODO: use worker thread
+    // TODO: could make it more efficient using TcpClient + SSL/TLS
+
+    public static void ServeGet(this String prefix, Func<Url, Byte[]?> f)
+    {
+        var listener = new HttpListener();
+        listener.Prefixes.Add(prefix);
+        listener.Start();
+
+        Console.WriteLine("Server started on " + prefix);
+
+        while (true)
+        {
+            var context  = listener.GetContext();
+            var request  = context.Request;
+            var response = context.Response;
+
+            if (request.HttpMethod.ToUpper() == "GET")
+            {
+                var url = new Url(request.Url?.OriginalString ?? "");
+                var reply = f(url);
+
+                if (reply != null)
+                {
+                    using var stream = response.OutputStream;
+                    stream.Write(reply);
+                }
+                else
+                {
+                    response.StatusCode = 404;
+                }
+            }
+            else
+                response.StatusCode = 403;
+
+            response.Close();
+        }
+
+        // ReSharper disable once FunctionNeverReturns
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/OpenVpnCertificatesServer/OpenVpnCertificatesServer.csproj b/csharp/App_backup/OpenVpnCertificatesServer/OpenVpnCertificatesServer.csproj
new file mode 100644
index 000000000..3319ad099
--- /dev/null
+++ b/csharp/App_backup/OpenVpnCertificatesServer/OpenVpnCertificatesServer.csproj
@@ -0,0 +1,26 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <Import Project="../InnovEnergy.App.props" />
+
+    <PropertyGroup>
+        <RootNamespace>InnovEnergy.App.OpenVpnCertificatesServer</RootNamespace>
+    </PropertyGroup>
+    
+    <ItemGroup>
+      <PackageReference Include="BouncyCastle" Version="1.8.9" />
+      <PackageReference Include="Flurl.Http" Version="3.2.2" />
+      <PackageReference Include="SharpZipLib" Version="1.3.3" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="../../Lib/Protocols/DBus/DBus.csproj" />
+      <ProjectReference Include="../../Lib/Victron/VictronVRM/VictronVRM.csproj" />
+      <ProjectReference Include="../../Lib/Utils/Utils.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <None Update="token.json">
+        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </None>
+    </ItemGroup>
+    
+</Project>
diff --git a/csharp/App_backup/OpenVpnCertificatesServer/PKI/CertificateAuthority.cs b/csharp/App_backup/OpenVpnCertificatesServer/PKI/CertificateAuthority.cs
new file mode 100644
index 000000000..b782c8165
--- /dev/null
+++ b/csharp/App_backup/OpenVpnCertificatesServer/PKI/CertificateAuthority.cs
@@ -0,0 +1,141 @@
+using Org.BouncyCastle.Asn1.Pkcs;
+using Org.BouncyCastle.Asn1.X509;
+using Org.BouncyCastle.Crypto;
+using Org.BouncyCastle.Crypto.Generators;
+using Org.BouncyCastle.Crypto.Operators;
+using Org.BouncyCastle.Math;
+using Org.BouncyCastle.Security;
+using Org.BouncyCastle.X509;
+
+namespace InnovEnergy.App.OpenVpnCertificatesServer.PKI;
+
+public static class CertificateAuthority
+{
+    private const Int32 KeySize = 2048;
+    private const Double DaysPerYear = 365.25;
+
+    private static X509Certificate     CaCertificate { get; } = Pem.Decode<X509Certificate>(CaCertificatePem);
+    private static RsaKeyPairGenerator KeyGenerator  { get; } = new RsaKeyPairGenerator();
+    private static SecureRandom        Rng           { get; } = new SecureRandom();
+
+    static CertificateAuthority()
+    {
+        var parameters = new KeyGenerationParameters(Rng, KeySize);
+        KeyGenerator.Init(parameters);
+    }
+
+    public static AsymmetricCipherKeyPair CreateKeyPair()
+    {
+        Console.WriteLine("Creating key pair");
+        return KeyGenerator.GenerateKeyPair();
+    }
+
+    public static X509Certificate CreateCertificate(String subjectName, AsymmetricCipherKeyPair subjKeyPair)
+    {
+        Console.WriteLine($"issuing certificate for {subjectName}");
+
+        var caKey = Pem.Decode<AsymmetricCipherKeyPair>(CaKeyPem, "max helfried harald ursin");
+
+        if (caKey == null)
+            throw new Exception("Failed to decode the CA key!");
+
+        var subjectDn = new X509Name("CN=" + subjectName);
+        var issuerDn  = CaCertificate.IssuerDN;
+        var notBefore = DateTime.UtcNow;
+        var notAfter  = notBefore.Add(TimeSpan.FromDays(DaysPerYear*100));
+        var serialNo  = BigInteger.ValueOf(Rng.Next());
+
+        var cg = new X509V3CertificateGenerator();
+
+        cg.SetIssuerDN(issuerDn);
+        cg.SetSubjectDN(subjectDn);
+        cg.SetSerialNumber(serialNo);
+        cg.SetNotAfter(notAfter);
+        cg.SetNotBefore(notBefore);
+        cg.SetPublicKey(subjKeyPair.Public);
+
+        var algorithm = PkcsObjectIdentifiers.Sha256WithRsaEncryption.ToString();
+        var sign      = new Asn1SignatureFactory(algorithm, caKey.Private);
+        var subjCert  = cg.Generate(sign);
+
+        if (!Validate(subjCert, caKey.Public))
+            throw new Exception("Failed to validate newly created certificate!");
+
+        return subjCert;
+    }
+
+
+    private static Boolean Validate(X509Certificate cert, ICipherParameters pubKey)
+    {
+        cert.CheckValidity(DateTime.UtcNow);
+
+        var tbsCert = cert.GetTbsCertificate();  // To Be Signed
+        var sig     = cert.GetSignature();
+
+        var signer = SignerUtilities.GetSigner(cert.SigAlgName);
+
+        signer.Init(forSigning: false,
+            parameters: pubKey);
+
+        signer.BlockUpdate(input: tbsCert,
+            inOff: 0,                // offset
+            length: tbsCert.Length);
+
+        return signer.VerifySignature(sig);
+    }
+
+    private const String CaKeyPem =
+        @"-----BEGIN RSA PRIVATE KEY-----
+          Proc-Type: 4,ENCRYPTED
+          DEK-Info: AES-256-CBC,C0B37765BDFC7D269C7057A3A463AB63
+
+          gSajHtV+qhJHBONKDktf3XoGS2Vwp0dHpk9sr1dRIThxLf6D7O1dYcTjIXJ/Iz+M
+          DkV7vnyigADH//9Hv7BwEyJCnie44ChTVxfIGLIwtxlp2oKBVuNmOJRtl+VFgKeG
+          zzzPxhMpgZJjpRm/jmdLp2YII374+P0fix+ZCTlzWDE5nbjnuumfK5+f+xtQMC4l
+          F1QoiZSe6k80Li7e/9Qd+e7i8nMNwCrS7jA6ayGIr7/vLWDjMtEjLF6YGAzGGHzQ
+          3JHb8rsmUX1I6KApYjlZVYHsdnqtN34TZen9zcWyqijSJS6S+hQLf0i+F8hY0Z/J
+          Y5fHk6br61n/6ZeEY7ijklVUeesbpc+JbjWAhYsg8wyVFTEQGVlLy7D0okO0x6J7
+          ej/aYUd9Fo2Jnl1I5BZYLXgg9WDsL4hu8u8EqbM4j4oIFxg6wlClMb4gS4iIl26v
+          36gisNzUY0xNVffLGKyAHsfi6pU8NSbh4Oil/TJcYR9jYu6GhB9Yvpnunc2/ae0W
+          1uRz6LP+RV9KWme2Diz3Kw1A049eiRC4qCJCsCGAJZzrKq6Anc7Ia47FDEC9J2yC
+          +5vaPPv6j+pWJJv3wbopl8AStm0hTjg26HAyju3BYFqawP/XcjmVsO+y675vclaH
+          g+Anr8gi3K4YNzOOLSOl2Fe/IK1BnMpHvabKy4Jo+NYfEIHUnWW1YBRAL6lWE2j2
+          wuZ5u+mhBYGkdQvhRCpJbe9GmXORjy5GJF/Pkyh27KHU/BxsnceY5OW12wk7nGBf
+          HUajeFI5G9DyftJvVJ/WyACe5915JoujVIgSnYM3z/kg3VzOB8OdvUYkNBj+Xj3i
+          Bwd1k3Due1HSlPnrW4JlCkMGQV2uhoOSZhHgxkUw/JRmBNBll6qyyqdyeRFFFIMD
+          KaELfoCSpjlh6uA6zgS2MO1KdilINgTevyrSeMMqPY7YlH9YyEknyqSpwXNqyDfb
+          itHs03n9lrksPgKQGstG9yrbKBan6dDn5rXnbvWrkEIUfrer/1YteCy1IYG49uZZ
+          /jo82FEO/h7fyApevk/CHWMBt52EgXzAzAqz9okRNp93aGoc6jWFvfdAxp/vxIOp
+          NGNN3LMvFwROr2y1d7VFKe45skCjFsYSGKqjnMZfUGHyFJSKV7LiOj9N32Mt9pj8
+          6nN8JFW2NmhOOPJd1CK7r1zXv4UWwnFFNlqzQkLujTNUUmhJhrqPCsbWOcVT9kqp
+          6GyyE4XO0u02ORpcA9WdniJmua5lTVF25BVZ8lwErJ5hlYaOPYpTbIlJGNZo9e1A
+          73ZfmANg/5TO4FqI0NJrUJN7ZIwwUPU8qH1wZcsTrnhSqaEKES+xqP6GfORMgIaE
+          J3aLMd7BRVPWC05/6ki98LJz8+/ZURS9oGVNKwy4S7rCi0me2Vi/shzmEk6cRH4R
+          uthELMaGMFCUjjKGclIdsT8MfMHPZ3VvzTEZANTMWSljdVat4UUcEj+MAQr5Q9uL
+          VcaAe/AweVaVI3hmXORzph3Uv4o8Im0eWss+IHUziE1uqwOrLhXIljNUNztrD2hP
+          kF20s1oGUKZ/V+lC/az7V7aZP8PreYV0P5mvP+v8gNepx62yCPLcyks9+IINz349
+          -----END RSA PRIVATE KEY-----";
+
+    public const String CaCertificatePem =
+        @"-----BEGIN CERTIFICATE-----
+          MIIDNDCCAhygAwIBAgIJAOtFlqoxtDSsMA0GCSqGSIb3DQEBCwUAMBUxEzARBgNV
+          BAMMCjQ4VEwyMDBfY2EwIBcNMTgxMTE5MTUxMjQ5WhgPMjExODExMjAxNTEyNDla
+          MBUxEzARBgNVBAMMCjQ4VEwyMDBfY2EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
+          ggEKAoIBAQCyPY+iqCVOwIEulgIwKktMA4RpGPwHxDXKBak+M2/uG2skW1DnoaaK
+          taWxcGBJrxsRJQEoAKkYDZAmleBwRcd7oaVK/NcRtyA83y7GJ4y77HxvVKYxwBMB
+          37EgkUXnBUcnUedrITVDZ6Fr+JYzRzfTb5M0x06W3RZvkTfyirq27CdnZ9SJXHC1
+          cgeyqnJzlPqH7etmxdUnqPeuRMa6g1lsGGjz0p03Fqbu5rjkB+TXGSkUqPbAho4b
+          Qg+7XFR1El5MP0u83rmRyID+6k5yt2KLPlr6VH0xUL/EQzezNmEgwuFv5AWt33HT
+          /VCpo5TDXkZBvgjxlBvoeoswuKYv9wR7AgMBAAGjgYQwgYEwHQYDVR0OBBYEFKtA
+          pTzAM02NhCrCtEuuw+x9S08gMEUGA1UdIwQ+MDyAFKtApTzAM02NhCrCtEuuw+x9
+          S08goRmkFzAVMRMwEQYDVQQDDAo0OFRMMjAwX2NhggkA60WWqjG0NKwwDAYDVR0T
+          BAUwAwEB/zALBgNVHQ8EBAMCAQYwDQYJKoZIhvcNAQELBQADggEBAJPK22mlvUA2
+          yKnNJKC+91AVtclwuVB+862RyGb1d1RwI2+fB7KkqyBUXaIqvDS3c5R/gXksx2Nj
+          nDZT0wUZpuSK8jCS6vaibQZOeucq1hYdTJUuPW34LWVUQs8LD4kaF9CNbiWMtuUg
+          UOKul0yD2xVhbR+ZfBqdp836gYQmR44SRNRd7IwZJsuopciQKnoVPZPXk6isyusK
+          hvx12IpeWtmc0n2ubOhDKhiK8kVU6QKQvCcuhiNqPKc7FvUyHrVBUhUuHluxXy2T
+          TltkEV+VSpv5sA/c25zp8hGyLtV+LirphEMUZgBG3cE2dX2O0PUFDBTVJUDUTCQw
+          aO2cAFGrouw=
+          -----END CERTIFICATE-----";
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/OpenVpnCertificatesServer/PKI/Pem.cs b/csharp/App_backup/OpenVpnCertificatesServer/PKI/Pem.cs
new file mode 100644
index 000000000..8015c4e71
--- /dev/null
+++ b/csharp/App_backup/OpenVpnCertificatesServer/PKI/Pem.cs
@@ -0,0 +1,53 @@
+using Org.BouncyCastle.OpenSsl;
+
+namespace InnovEnergy.App.OpenVpnCertificatesServer.PKI;
+
+public static class Pem
+{
+    public static String Encode(Object obj)
+    {
+        using var sw = new StringWriter();
+
+        var pem = new PemWriter(sw);
+        pem.WriteObject(obj);
+        pem.Writer.Flush();
+
+        return sw.ToString();
+    }
+
+    public static T Decode<T>(String pem)
+    {
+        using var reader = new StringReader(pem);
+        var pemReader = new PemReader(reader);
+        return (T) pemReader.ReadObject();
+    }
+
+    public static T? Decode<T>(String pem, IPasswordFinder pwf)
+    {
+        try
+        {
+            using var reader = new StringReader(pem);
+            var pemReader = new PemReader(reader, pwf);
+            return (T) pemReader.ReadObject();
+        }
+        catch
+        {
+            return default;
+        }
+    }
+
+    public static T? Decode<T>(String pem, String password)
+    {
+        try
+        {
+            using var reader = new StringReader(pem);
+            var pwf = new PwdFinder(password);
+            var pemReader = new PemReader(reader, pwf);
+            return (T) pemReader.ReadObject();
+        }
+        catch (Exception)
+        {
+            return default;
+        }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/OpenVpnCertificatesServer/PKI/PwdFinder.cs b/csharp/App_backup/OpenVpnCertificatesServer/PKI/PwdFinder.cs
new file mode 100644
index 000000000..3bbe9354a
--- /dev/null
+++ b/csharp/App_backup/OpenVpnCertificatesServer/PKI/PwdFinder.cs
@@ -0,0 +1,15 @@
+using Org.BouncyCastle.OpenSsl;
+
+namespace InnovEnergy.App.OpenVpnCertificatesServer.PKI;
+
+public class PwdFinder : IPasswordFinder
+{
+    private readonly Char[] _Password;
+
+    public PwdFinder(String password)
+    {
+        _Password = password.ToCharArray();
+    }
+
+    public Char[] GetPassword() => _Password;
+}
\ No newline at end of file
diff --git a/csharp/App_backup/OpenVpnCertificatesServer/Program.cs b/csharp/App_backup/OpenVpnCertificatesServer/Program.cs
new file mode 100644
index 000000000..189653434
--- /dev/null
+++ b/csharp/App_backup/OpenVpnCertificatesServer/Program.cs
@@ -0,0 +1,287 @@
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Text;
+using System.Text.Json;
+using Flurl;
+using ICSharpCode.SharpZipLib.Tar;
+using InnovEnergy.App.OpenVpnCertificatesServer.PKI;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Victron.VictronVRM;
+using Org.BouncyCastle.Crypto;
+using static InnovEnergy.App.OpenVpnCertificatesServer.PKI.CertificateAuthority;
+
+namespace InnovEnergy.App.OpenVpnCertificatesServer;
+
+// export SolutionDir=$(pwd)
+// dotnet publish OpenVpnCertificatesServer.csproj -c Release -r linux-x64 -p:PublishSingleFile=true --self-contained true  && scp bin/Release/net6.0/linux-x64/publish/OpenVpnCertificatesServer ig@salidomo.innovenergy.ch:~/get_cert/get_cert
+// scp bin/Release/net6.0/linux-x64/publish/token.json ig@salidomo.innovenergy.ch:~/get_cert/token.json
+
+
+// http://localhost:4000/get_cert?machine_serial=HQ2032UAWYM
+// http://localhost:4000/get_cert?unique_id=985dadd0cf29
+
+// quotes!!
+// wget 'http://localhost:4000/get_cert?name=MYNAME&pw=MwBRbQb3QaX7l9XIaakq'  
+
+[SuppressMessage("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic access otherwise can break functionality when trimming application code")]
+public static class Program
+{
+    // TODO: use fody weaver to obfuscate strings?
+
+    private const String VpnSubnet  = "10.2.";
+    private const String VpnGateway = "10.2.0.1";
+
+    private const String VpnDir     = "/etc/openvpn/server/Salino";
+    private const String CcdDir     = VpnDir + "/ccd";
+    private const String CertsDir   = VpnDir + "/certs";
+    private const String Endpoint   = "http://localhost:4000/";
+
+    private static AccToken? ReadAccessToken()
+    {
+        var content = File.ReadAllText("./token.json");
+        
+        return JsonSerializer.Deserialize<AccToken>(content);
+    }
+
+
+    private const String ManualPw   = "MwBRbQb3QaX7l9XIaakq";
+    
+    private const String QueryPath               = "/get_cert";
+    private const String NameQueryParam          = "name";
+    private const String UniqueIdQueryParam      = "unique_id";
+    private const String MachineSerialQueryParam = "machine_serial";
+    private const String PwdQueryParam           = "pw";
+    
+
+    private const Int64 MachineSerialAttribute = 556; // TODO: enum
+    
+    private static readonly (String, String)? InvalidRequest = null;
+    
+    private static IEnumerable<String> CcdFiles => Directory.GetFiles(CcdDir);
+
+    public static void Main(String[] args) => Endpoint.ServeGet(GetCert);
+
+    private static Byte[]? GetCert(Url url)
+    {
+        var request = ParseRequest(url).Result;
+
+        if (!request.HasValue)
+            return null;
+
+        var (ccdName, humanReadableName) = request.Value;
+
+        var ip      = GetNextFreeIp();
+        var keyPair = CreateKeyPair();
+        var cert    = CreateCertificate(ccdName, keyPair).Apply(Pem.Encode);
+        var tar     = PackTar(cert, humanReadableName, keyPair, ip);
+
+        WriteServerCertificateFiles(ccdName, ip, tar);
+
+        Console.WriteLine($"returning certificate for {humanReadableName}\n");
+        return tar;
+    }
+
+    private static Byte[] PackTar(String certificate, String certificateName, AsymmetricCipherKeyPair keys, String ip)
+    {
+        Console.WriteLine("packing tar"); 
+
+        var publicKey  = keys.Public .Apply(Pem.Encode);
+        var privateKey = keys.Private.Apply(Pem.Encode);
+
+        using var ms = new MemoryStream();
+
+        using (var ts = new TarOutputStream(ms, Encoding.UTF8) {IsStreamOwner = false})
+        {
+            ts.WriteTextFile("installation-ip"   , ip);
+            ts.WriteTextFile("installation-name" , certificateName);
+            ts.WriteTextFile("ca-certificate"    , CaCertificatePem.Replace("  ", "")); // remove leading spaces
+            ts.WriteTextFile("client-certificate", certificate);
+            ts.WriteTextFile("client-key.pub"    , publicKey);
+            ts.WriteTextFile("client-key"        , privateKey);
+            ts.WriteTextFile("innovenergy.conf"  , Files.OvpnConf);
+            ts.WriteTextFile("README.txt"        , Files.ReadMeTxt);
+        }
+        // MUST Dispose to commit data to underlying MemoryStream !!!!
+
+        return ms.ToArray();
+    }
+
+
+    private static async Task<(String CcdName, String HumanReadableName)?> ParseRequest(Url? url = null)
+    {
+        Console.WriteLine($"got request {url}");
+
+        if (!url.Path.EndsWith(QueryPath))
+        {
+            Console.WriteLine("Malformed request\n");
+            return InvalidRequest;
+        }
+        
+        var ps = url.QueryParams;
+
+        var ccdName = ps.FirstOrDefault(UniqueIdQueryParam)     ?.ToString() ?? 
+                      ps.FirstOrDefault(MachineSerialQueryParam)?.ToString() ??
+                      ps.FirstOrDefault(NameQueryParam)         ?.ToString();
+
+        if (!IsCcdNameValid(ccdName))
+        {
+            Console.WriteLine($"Invalid ccd name {ccdName}\n");
+            return InvalidRequest;
+        }
+
+        if (!IsCcdNameAvailable(ccdName))
+        {
+            Console.WriteLine($"ccd name {ccdName} is already in use\n");
+            return InvalidRequest;
+        }
+
+        return ps.Contains(NameQueryParam)          ? ParseManualRequest(ps, ccdName!)
+             : ps.Contains(UniqueIdQueryParam)      ? await LookupInstallationNameByUniqueId(ccdName!) 
+             : ps.Contains(MachineSerialQueryParam) ? await LookupInstallationNameByMachineSerial(ccdName!)
+             : InvalidRequest;
+    }
+
+    private static async Task<(String ccdName, String humanReadableName)?> LookupInstallationNameByUniqueId(String uniqueId)
+    {
+        Console.WriteLine($"looking up unique id {uniqueId} on VRM");
+
+        //var installationName = await LookupInstallationNameByUniqueId(ccdName);
+        var token = ReadAccessToken();
+
+        var vrm = VrmAccount.Token(token.idUser, token.token);  
+
+        
+        var installations = await vrm.GetInstallations();
+        
+        var installationName = installations
+                              .Where(i => i.UniqueId == uniqueId)
+                              .Select(i => i.Name)
+                              .FirstOrDefault();
+
+        return !String.IsNullOrWhiteSpace(installationName) 
+             ? (uniqueId, installationName) 
+             : null;
+    }
+    
+    private static async Task<(String ccdName, String humanReadableName)?> LookupInstallationNameByMachineSerial(String ccdName)
+    {
+        Console.WriteLine($"looking up {ccdName} on VRM");
+        var token = ReadAccessToken();
+
+        using var vrm = VrmAccount.Token(token.idUser, token.token);  
+
+        var installations = await vrm.GetInstallations();
+
+        foreach (var installation in installations)
+        {
+            var details = await installation.GetDetails();
+
+            var id = details.MachineSerial();
+
+            if (id == ccdName)
+                return (ccdName, installation.Name);
+        }
+
+        Console.WriteLine($"not found");
+        return null;
+    }
+
+
+    private static (String ccdName, String humanReadableName)? ParseManualRequest(QueryParamCollection ps, String ccdName)
+    {
+        Console.WriteLine("parsing manual request");
+
+        var pw = ps.FirstOrDefault(PwdQueryParam)?.ToString();
+
+        if (pw == ManualPw)
+            return (ccdName, ccdName); // ccd and hr name are the same in this case
+
+        Console.WriteLine("Malformed request\n");
+
+        return null;
+    }
+
+
+    private static Boolean IsCcdNameValid(String? ccdName)
+    {
+        return ccdName != null && ccdName.All(IsValidChar);
+
+        Boolean IsValidChar(Char c) => Char.IsLetterOrDigit(c) || c is '_' or '-';
+    }
+    
+    private static Boolean IsCcdNameAvailable(String? ccdName)
+    {
+        Console.WriteLine($"checking ccdName {ccdName ?? ""}");
+
+        return ccdName != null && !CcdFiles
+                                  .Select(Path.GetFileName)
+                                  .Contains(ccdName);
+    }
+
+    private static void WriteServerCertificateFiles(String ccdName, String ip, Byte[] certsTar)
+    {
+        Console.WriteLine($"storing certificate {ccdName} in {CertsDir}");
+
+        var ccdContent = $"ifconfig-push {ip} {VpnGateway}";
+        var ccdFile    = $"{CcdDir}/{ccdName}";
+        var tarFile    = $"{CertsDir}/{ccdName}.tar";
+
+        File.WriteAllText (ccdFile, ccdContent);
+        File.WriteAllBytes(tarFile, certsTar);
+    }
+
+
+    // TODO: this could be made simpler using OrderBy/ThenBy
+    private static Int32 GetId(String ccdFile)
+    {
+        var ip = File
+                .ReadAllLines(ccdFile)
+                .Select(l => l.Trim())
+                .SingleOrDefault(l => l.StartsWith("ifconfig-push"))?
+                .Split(" ", StringSplitOptions.RemoveEmptyEntries)
+                .ElementAtOrDefault(1);
+
+        if (ip == null)
+            return 0;
+
+        var bytes = ip.Split(".");
+
+        var hi = bytes.ElementAtOrDefault(2);
+        var lo = bytes.ElementAtOrDefault(3);
+
+        // TODO: GetIp => tuple (a,b,hi,lo) ?
+
+        if (hi == null || lo == null)
+            return 0;
+
+        if (!Byte.TryParse(hi, out var hiByte) || !Byte.TryParse(lo, out var loByte))
+            return 0;
+
+        return hiByte * 256 + loByte;
+    }
+
+
+    // TODO: this could be made simpler using OrderBy/ThenBy
+    private static String GetNextFreeIp()
+    {
+        // 10.2.hi.lo
+        // id := 256 * hi + lo
+
+        var id = CcdFiles.Max(GetId) + 1;
+
+        var lo = id % 256;
+
+        // x.y.z.0 and x.y.z.255 are reserved
+        if (lo == 0)   id += 1;
+        if (lo == 255) id += 2;
+
+        var hi = id / 256;
+        lo = id % 256;
+
+        Debug.Assert(lo != 0);
+        Debug.Assert(lo != 255);
+        Debug.Assert(hi != 255);
+
+        return $"{VpnSubnet}{hi}.{lo}";
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/OpenVpnCertificatesServer/Utils.cs b/csharp/App_backup/OpenVpnCertificatesServer/Utils.cs
new file mode 100644
index 000000000..aea6faf59
--- /dev/null
+++ b/csharp/App_backup/OpenVpnCertificatesServer/Utils.cs
@@ -0,0 +1,38 @@
+using System.Text;
+using ICSharpCode.SharpZipLib.Tar;
+
+namespace InnovEnergy.App.OpenVpnCertificatesServer;
+
+public static class Utils
+{
+    public static void WriteFile(this TarOutputStream tar, String fileName, Byte[] contents, Boolean executable = false)
+    {
+        var header = new TarHeader
+        {
+            Name      = fileName.Trim('\\', '/'),
+            Mode      = executable ? 33261 : 33188,  // chmod: 100755, 100644
+            Size      = contents.Length,
+            ModTime   = DateTime.UtcNow,
+            TypeFlag  = TarHeader.LF_NORMAL,  // normal file
+            UserName  = "",
+            GroupName = "",
+        };
+
+        var entry = new TarEntry(header);
+
+        tar.PutNextEntry(entry);
+        tar.Write(contents);
+        tar.CloseEntry();
+    }
+
+    public static void WriteTextFile(this TarOutputStream tar,
+                                                   String fileName,
+                                                   String contents,
+                                                Encoding? encoding = null,
+                                                  Boolean executable = false)
+    {
+        encoding ??= Encoding.UTF8;
+        var data = encoding.GetBytes(contents);
+        tar.WriteFile(fileName, data, executable);
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/OpenVpnCertificatesServer/newToken.sh b/csharp/App_backup/OpenVpnCertificatesServer/newToken.sh
new file mode 100755
index 000000000..37984f65a
--- /dev/null
+++ b/csharp/App_backup/OpenVpnCertificatesServer/newToken.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+## create a new VRM access token
+## USAGE: ./newToken.sh -u Username -p Password -n UniqueTokenName
+
+
+while getopts u:p:n:  flag
+do
+    case "${flag}" in
+        u) username=${OPTARG};;
+        p) password=${OPTARG};;
+        n) name=${OPTARG};;
+    esac
+done
+
+response=$(curl --request POST \
+  --url https://vrmapi.victronenergy.com/v2/auth/login \
+  --header 'Content-Type: application/json' \
+  --data '{"username":"'$username'","password":"'$password'","sms_token":null,"remember_me":false,"language":""}')
+  
+echo -n '{"bearer' > token.json
+token=$(echo $response | jq ".token")
+
+uid=$(echo $response | jq ".idUser")  
+echo -n ${response:7:-1} >> token.json
+
+response2=$(curl --request POST \
+  --url https://vrmapi.victronenergy.com/v2/users/$uid/accesstokens/create \
+  --header 'Content-Type: application/json' \
+  --header 'x-authorization: Bearer '${token:1:-1} \
+  --data '{"name":"'$name'"}')
+echo -n , >> token.json
+echo -n ${response2:1} >> token.json
\ No newline at end of file
diff --git a/csharp/App_backup/OpenVpnCertificatesServer/token.json b/csharp/App_backup/OpenVpnCertificatesServer/token.json
new file mode 100644
index 000000000..456c09453
--- /dev/null
+++ b/csharp/App_backup/OpenVpnCertificatesServer/token.json
@@ -0,0 +1 @@
+{"bearer":"eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImp0aSI6IjJmYzY5NTNiMjhjNTg3NWQyMzQwNzM3ZjlhNGIzM2RjIn0.eyJ1aWQiOiI1NTQ1MCIsInRva2VuX3R5cGUiOiJkZWZhdWx0IiwiaXNzIjoidnJtYXBpLnZpY3Ryb25lbmVyZ3kuY29tIiwiYXVkIjoiaHR0cHM6Ly92cm1hcGkudmljdHJvbmVuZXJneS5jb20vIiwiaWF0IjoxNjc5NTg2ODU0LCJleHAiOjE2Nzk2NzMyNTQsImp0aSI6IjJmYzY5NTNiMjhjNTg3NWQyMzQwNzM3ZjlhNGIzM2RjIn0.AsLJU7qDPBHO-_FjVo9a8RbyoxhYrDrwZX7V3z4Xq8EoUulv2VyTqy9OXLpez4JI2FVAfSO5a7Amj4XvK1AWtDr9MxP07IBfPyMu5LLGTzjPMAJ6fvZbvZ-eRsP1-aARCFekMGoeSvEEBDrZB9_0kps4h9idQwwGwAby2Tya0vNGu6QEw9WmHcbL8qjvJzxavg8bK6Lttv2-3l_11iZoqiYLdXbwBi32GYK_sdwp-fbGvPE1d6g6eVll94JfSqNLZl9baijtOksx_Qouu7YB8knCgFNrx535d4iJtCkMv9xWztWXbevpSQiy9S8pCgLSpmHNztlVDjacEYyduwUzyw","idUser":55450,"verification_mode":"password","verification_sent":false,"success":true,"token":"108a1407da84afea182b8102a202a2c6b73e9bd6ad919f521033b09837e6e564","idAccessToken":"385119"}
\ No newline at end of file
diff --git a/csharp/App_backup/RemoteSupportConsole/Login.cs b/csharp/App_backup/RemoteSupportConsole/Login.cs
new file mode 100644
index 000000000..7e3edbe35
--- /dev/null
+++ b/csharp/App_backup/RemoteSupportConsole/Login.cs
@@ -0,0 +1,7 @@
+namespace InnovEnergy.App.RemoteSupportConsole;
+
+public static class Login
+{
+    public const String Token = "ccf470115ca6b729475b9f6a8722a7eb4036df043e7cbaf5002c2c18ccd2e4ee";
+    public const UInt64 UserId = 55450;
+}
\ No newline at end of file
diff --git a/csharp/App_backup/RemoteSupportConsole/ObservablePipeSource.cs b/csharp/App_backup/RemoteSupportConsole/ObservablePipeSource.cs
new file mode 100644
index 000000000..a5e1af9d7
--- /dev/null
+++ b/csharp/App_backup/RemoteSupportConsole/ObservablePipeSource.cs
@@ -0,0 +1,20 @@
+using System.Reactive.Linq;
+using System.Reactive.Threading.Tasks;
+using CliWrap;
+
+namespace InnovEnergy.App.RemoteSupportConsole;
+
+public static class ObservablePipeSource 
+{
+    
+    // TODO: move to utils?
+    
+    public static PipeSource AsPipeSource(this IObservable<Byte[]> observable)
+    {
+        return PipeSource.Create((destination, cancellationToken) =>
+            observable
+                .Select(data => destination.WriteAsync(data, 0, data.Length, cancellationToken))
+                .ToTask(cancellationToken)
+        );
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/RemoteSupportConsole/Program.cs b/csharp/App_backup/RemoteSupportConsole/Program.cs
new file mode 100644
index 000000000..03067a613
--- /dev/null
+++ b/csharp/App_backup/RemoteSupportConsole/Program.cs
@@ -0,0 +1,162 @@
+using System.Reflection;
+using System.Text;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Victron.VictronVRM;
+using static System.Globalization.CompareOptions;
+
+namespace InnovEnergy.App.RemoteSupportConsole;
+
+// dotnet publish -c release -r ubuntu-x64
+// dotnet publish RemoteSupportConsole.csproj -c Release -r linux-x64 -p:PublishSingleFile=true --self-contained true 
+
+public static class Program
+{
+    public static async Task<Int32> Main(String[] args)
+    {
+        Console.WriteLine();
+
+        var options          = args.Where(IsOption).Select(a => a.Trim('-').ToLower()).ToList();
+        var installationName = args.LastOrDefault(a => !a.IsOption()) ?? "";  
+        
+        if (options.Contains("h") || options.Contains("help"))
+        {
+            PrintUsage();
+            return 0;
+        }
+
+        var vrm = VrmAccount.Token(Login.UserId, Login.Token);
+      
+        var installations = await VrmInfo.GetInstallations(vrm);
+
+        // await DumpInstallations(installations);
+
+        var chosenInstallation = installations.ChooseInstallation(installationName);
+        
+        if (!chosenInstallation.HasValue)
+            return 0;
+
+        var installation = chosenInstallation.Value;
+
+        var details      = await installation.GetDetails();
+        var useVrmTunnel = options.Contains("vrm");
+
+        var connection = useVrmTunnel
+                       ? VrmConnection.Open(installation, details)
+                       : VpnConnection.Open(installation, details);
+        
+        return await connection;
+    }
+
+    
+
+    private static void PrintUsage()
+    {
+        var asm = Assembly.GetExecutingAssembly().GetName().Name;
+
+        Console.WriteLine();
+        Console.WriteLine($"Usage: {asm} [--vpn|--vrm] [InstallationName]");
+        Console.WriteLine("                                                                    ");
+        Console.WriteLine("       --vpn        use InnovEnergy VPN to connect (default)        "); 
+        Console.WriteLine("       --vrm        use Victron remote support to connect           ");
+        Console.WriteLine("                                                                    ");
+        Console.WriteLine("       InstallationName can be omitted or partial (case insensitive)");
+        Console.WriteLine();
+    }
+
+    private static Installation? ChooseInstallation(this IReadOnlyList<Installation> chooseFrom,
+                                                                              String installationName)
+    {
+        
+        // IgnoreNonSpace: u==ü, ignore umlauts and the like
+        Boolean Match(Installation i) => i.Name.Contains(installationName, IgnoreCase | IgnoreSymbols | IgnoreNonSpace);
+
+        var matchingInstallations = chooseFrom
+                                   .Where(Match)
+                                   .ToList();
+
+        if (matchingInstallations.Count == 1)
+            return matchingInstallations.First(); // found a single match, return it
+
+        var input = matchingInstallations.Count > 0 ? installationName : "";
+
+        // zero or more than 1 found, let user choose
+        var iName = "Select installation:".ChooseFrom(chooseFrom.Select(i=>i.Name), input);
+
+        if (chooseFrom.All(i => i.Name != iName))
+            return null;
+
+        return chooseFrom.First(i => i.Name == iName);
+    }
+
+    private static Boolean IsOption(this String arg) => arg.StartsWith("-");
+    
+    // DEBUG
+    private static async Task DumpInstallations(IReadOnlyList<Installation> installations)
+    {
+        const String vrmLink = "https://vrm.victronenergy.com/installation/";
+
+        var sb = new StringBuilder();
+
+
+        var header = new[]
+        {
+            "Name Installation",
+            "Kunde",
+            "Ort",
+            "Region",
+            "Firma",
+            "Auftragsnummer",
+            "Erste Inbetriebnahme",
+            "VRM Id",
+            "VRM Link",
+            "Geraet Typ",
+            "Geraet Name",
+            "Geraete Serienummer",
+            "Firmware Version"
+        };
+
+        sb.AppendLine(header.JoinWith("$"));
+
+        foreach (var i in installations)
+        {
+            var devices = await i.GetDevices();
+            
+            foreach (var device in devices)
+            {
+                var x = i.Name.Split('|');
+                var nameLocation = x.FirstOrDefault()?.Split(',', 2);
+
+                var customer = nameLocation?.FirstOrDefault()?.Replace("IBN", "").Trim("_ ".ToCharArray()) ?? "";
+                var locationRegion = nameLocation?.LastOrDefault()?.Trim() ?? "";
+                var location = locationRegion.Split("/").FirstOrDefault()?.Trim() ?? "";
+                var region = locationRegion.Split("/").LastOrDefault()?.Trim() ?? "";
+
+                var company = x.LastOrDefault()?.Split('(').FirstOrDefault()?.Trim() ?? "";
+                var sn = x.LastOrDefault()?.Split('(').LastOrDefault()?.Trim("() ".ToCharArray()) ?? "";
+
+                var created = i.Created.DateTimeFromUnixTime().ToString("dd.MM.yyyy HH:mm");
+
+                var line = new[]
+                {
+                    i.Name,
+                    customer,
+                    location,
+                    region,
+                    company,
+                    sn,
+                    created,
+                    i.UniqueId,
+                    vrmLink + i.IdSite,
+                    device.Type,
+                    device.ProductName,
+                    device.SerialNumber,
+                    device.FirmwareVersion
+                };
+
+                sb.AppendLine(line.JoinWith("$").WriteLine());
+            }
+        }
+
+        File.WriteAllText("Installations.csv", sb.ToString());
+    }
+}
diff --git a/csharp/App_backup/RemoteSupportConsole/RemoteSupportConsole.csproj b/csharp/App_backup/RemoteSupportConsole/RemoteSupportConsole.csproj
new file mode 100644
index 000000000..ac1e5336b
--- /dev/null
+++ b/csharp/App_backup/RemoteSupportConsole/RemoteSupportConsole.csproj
@@ -0,0 +1,21 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <Import Project="../InnovEnergy.App.props" />
+
+    <PropertyGroup>
+        <RootNamespace>InnovEnergy.App.RemoteSupportConsole</RootNamespace>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <ProjectReference Include="../../Lib/Utils/Utils.csproj" />
+        <ProjectReference Include="../../Lib/Victron/VictronVRM/VictronVRM.csproj" />
+    </ItemGroup>
+
+
+    <ItemGroup>
+      <PackageReference Include="CliWrap" Version="3.6.0" />
+      <PackageReference Include="System.Linq.Async" Version="6.0.1" />
+    </ItemGroup>
+
+
+</Project>
diff --git a/csharp/App_backup/RemoteSupportConsole/RemoteSupportConsole.csproj.DotSettings b/csharp/App_backup/RemoteSupportConsole/RemoteSupportConsole.csproj.DotSettings
new file mode 100644
index 000000000..4887f9475
--- /dev/null
+++ b/csharp/App_backup/RemoteSupportConsole/RemoteSupportConsole.csproj.DotSettings
@@ -0,0 +1,2 @@
+<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
+	<s:String x:Key="/Default/CodeInspection/CSharpLanguageProject/LanguageLevel/@EntryValue">CSharp100</s:String></wpf:ResourceDictionary>
\ No newline at end of file
diff --git a/csharp/App_backup/RemoteSupportConsole/Ssh.cs b/csharp/App_backup/RemoteSupportConsole/Ssh.cs
new file mode 100644
index 000000000..dd4fe7a4e
--- /dev/null
+++ b/csharp/App_backup/RemoteSupportConsole/Ssh.cs
@@ -0,0 +1,46 @@
+using CliWrap;
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.RemoteSupportConsole;
+
+public static class Ssh
+{
+    const String ConfigFile = "/data/innovenergy/openvpn/installation-name";
+    
+    public static Task<Int32> Interactive(String host, String? installationName, String user, String port)
+    {
+        var fixInstallationName = $"echo '{installationName}' > {ConfigFile}; exec bash -l";
+        
+        var args = GetArgs(host, user, port)
+                  .Append(fixInstallationName);
+        
+        return Cli
+              .Wrap("ssh")
+              .WithArguments(args, true)
+              .ExecuteInteractive();
+    }
+
+    public static Command Command(String host, String user , String port)
+    {
+        var args = GetArgs(host, user, port);
+
+        return Cli
+              .Wrap("ssh")
+              .WithArguments(args, true);
+    }
+
+    private static IEnumerable<String> GetArgs(String host, String user, String port)
+    {
+        return new[]
+        {
+            "-o UserKnownHostsFile=/dev/null",
+            "-o StrictHostKeyChecking=no",
+            "-o ServerAliveInterval=5",
+            "-o PasswordAuthentication=no",
+            "-o LogLevel=ERROR",
+            "-tt", // allocate PTS, necessary
+            $"-p {port}",
+            $"{user}@{host}"
+        };
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/RemoteSupportConsole/VpnConnection.cs b/csharp/App_backup/RemoteSupportConsole/VpnConnection.cs
new file mode 100644
index 000000000..b2442eea1
--- /dev/null
+++ b/csharp/App_backup/RemoteSupportConsole/VpnConnection.cs
@@ -0,0 +1,25 @@
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Victron.VictronVRM;
+using static System.ConsoleColor;
+
+namespace InnovEnergy.App.RemoteSupportConsole;
+
+public static class VpnConnection
+{
+    public static async Task<Int32> Open(Installation installation, IReadOnlyList<Detail> details)
+    {
+        var machineSerial  = details.MachineSerial();  // DEPRECATED
+        var installationId = installation.UniqueId;
+        
+        var vpnIp = await VpnInfo.LookUpIp(installationId, machineSerial); // look up by InstallationId OR machineSerial
+
+        if (String.IsNullOrWhiteSpace(vpnIp))
+            return 1;
+
+        "Starting VPN connection with ".Write();    
+        installation.Name.WriteLine(Green);
+        Console.WriteLine();
+
+        return await Ssh.Interactive(vpnIp, installation.Name, "root", "22");
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/RemoteSupportConsole/VpnInfo.cs b/csharp/App_backup/RemoteSupportConsole/VpnInfo.cs
new file mode 100644
index 000000000..0137a3b50
--- /dev/null
+++ b/csharp/App_backup/RemoteSupportConsole/VpnInfo.cs
@@ -0,0 +1,36 @@
+using Flurl;
+using Flurl.Http;
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.RemoteSupportConsole;
+
+public static class VpnInfo
+{
+    private const String Address = "http://10.2.0.1";
+    private const String Path    = "vpnstatus.txt";
+
+    public static async Task<String?> LookUpIp(params String?[] commonNames)
+    {
+        $"looking up ip of {String.Join("/", commonNames)} in VPN routing table... ".Write(); 
+        
+        var raw = await Address
+                       .AppendPathSegment(Path)
+                       .GetStringAsync();
+
+        var ip = raw
+                .Split('\n')
+                .Select(l => l.Split(','))
+                .Where(l => l.Length > 2 && commonNames.Contains(l[2].Trim()) && l[0] == "ROUTING_TABLE")
+                .Select(l => l[1])
+                .SingleOrDefault();
+
+        if (String.IsNullOrEmpty(ip))
+            "not found".WriteLine(ConsoleColor.Red);
+        else
+            ip.WriteLine(ConsoleColor.Cyan);
+
+        return ip;
+    }
+
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/RemoteSupportConsole/VrmConnection.cs b/csharp/App_backup/RemoteSupportConsole/VrmConnection.cs
new file mode 100644
index 000000000..03dd1e252
--- /dev/null
+++ b/csharp/App_backup/RemoteSupportConsole/VrmConnection.cs
@@ -0,0 +1,128 @@
+using System.Reactive.Linq;
+using System.Reactive.Subjects;
+using System.Text;
+using CliWrap.EventStream;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Victron.VictronVRM;
+using static System.ConsoleColor;
+using static System.StringSplitOptions;
+
+namespace InnovEnergy.App.RemoteSupportConsole;
+
+public static class VrmConnection
+{
+    public static async Task<Int32> Open(Installation installation, IReadOnlyList<Detail> details)
+    {
+        var rsHost = details.RemoteSupportIp();
+        var rsPort = details.RemoteSupportPort();
+
+        "starting remote support connection with ".Write();
+        installation.Name.WriteLine(Green);
+
+        using var proxy = await OpenProxy(rsHost!, rsPort!);
+
+        "initiating SSH connection\n".WriteLine();
+        
+        return await Ssh.Interactive(proxy.Host, installation.Name, proxy.User, proxy.Port);
+
+    }
+
+    private static async Task<VrmProxy> OpenProxy(String rsHost, String rsPort)
+    {
+        "contacting Victron remote support host at ".Write();
+        rsHost.Write(Cyan);
+        " ... ".Write();
+
+        var stdIn = new Subject<String>();
+
+        var stdInPipe = stdIn
+                       .Select(l => l + "\n")
+                       .Select(Encoding.UTF8.GetBytes)
+                       .AsPipeSource();
+
+        var stdOut = Ssh
+                    .Command(rsHost, "ccgxsupport", "22")
+                    .WithStandardInputPipe(stdInPipe)
+                    .Observe()
+                    .OfType<StandardOutputCommandEvent>()
+                    .Select(e => e.Text)
+                    .Replay();
+
+        var connection = stdOut.Connect();
+        
+        await stdOut.FirstAsync(l => l.StartsWith("Full readline support"));
+        
+        "ok".WriteLine();
+        "requesting tunnel through port ".Write();
+        rsPort.Write(Cyan);
+        " ... ".Write();    
+        
+        stdIn.OnNext($"proxyport {rsPort}");
+
+        var connectMsg = await stdOut.FirstAsync(l => l.StartsWith("You can now connect"));
+        
+        "ok".WriteLine();
+
+        var portUserHost = connectMsg.Split(" ", RemoveEmptyEntries);
+        var userHost     = portUserHost[^1].Split("@");
+        var user         = userHost[0];
+        var host         = userHost[1];
+        var port         = portUserHost[^2];
+        
+        return new VrmProxy(connection, user, host, port);
+    }
+
+    public static IObservable<CommandEvent> Log(this IObservable<CommandEvent> es)
+    {
+        return es.Do(e => Console.WriteLine(Log(e)));
+    }
+
+    private static String Log(CommandEvent e) => e switch
+    {
+        StandardOutputCommandEvent so => "=> " + so.Text,
+        StandardErrorCommandEvent  se => "!! " + se.Text,
+        StartedCommandEvent           => "START",
+        ExitedCommandEvent            => "EXIT",
+        _                             => "UNKNOWN MESSAGE: " + e.GetType().Name
+    };
+}
+
+
+// ReSharper disable CommentTypo
+
+// $ ssh ccgxsupport@3.72.120.57
+// Welcome to the Victron remote support tunnel creator.
+//
+// You will given a one-time tunnel. SSH authentication to
+// the Color Control is done with your SSH public key.
+//
+// *****************************************************************************
+// *                               IMPORTANT NOTE:                             *
+// *                                                                           *
+// * There are now multiple SSH support hosts. Logging in directly             *
+// * is not officially supported behavior, because you may end up on the wrong *
+// * server. A script called 'rs' to use the new format '1.2.3.4;45630' is     *
+// * available. Ask your Victron contact to supply you with it.                *
+// *****************************************************************************
+//
+// The proxy opened up to the CCGX will be restricted to your IP: 145.40.192.17
+//
+// Commands are:
+//  proxyport [port]  - take color control [port] and temporarily make it available to the outside.
+//  iptoport [ip]     - show the ports being listened on by that IP. Note, you do need to know
+//                      which server the device is connected to. Currently, possibilites are:
+//                      - 84.22.108.49 (EU)
+//                      - 84.22.107.120 (EU)
+//                      - 3.69.217.175 (EU)
+//                      - 3.72.120.57 (EU)
+//                      - 3.25.10.245 (AUS Sydney)
+//                      - 35.165.124.40 (USA West)
+//                      - 13.244.154.199 (South Africa)
+//                      - 13.246.113.18 (South Africa)
+//                      A good start would be the last known, as seen on the diagnostics page.
+//  exit              - exits the shell and all tunnels you set up
+//
+// Full readline support (command history and editing) is available.
+//
+// proxyport 24337
+// You can now connect to the CCGX with: ssh -o 'UserKnownHostsFile=/dev/null' -o 'StrictHostKeyChecking=no' -o 'ServerAliveInterval=60' -p 4001 root@3.72.120.57
\ No newline at end of file
diff --git a/csharp/App_backup/RemoteSupportConsole/VrmInfo.cs b/csharp/App_backup/RemoteSupportConsole/VrmInfo.cs
new file mode 100644
index 000000000..32568ae23
--- /dev/null
+++ b/csharp/App_backup/RemoteSupportConsole/VrmInfo.cs
@@ -0,0 +1,45 @@
+using Flurl.Http;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Victron.VictronVRM;
+using static System.ConsoleColor;
+
+namespace InnovEnergy.App.RemoteSupportConsole;
+
+
+public static class VrmInfo
+{
+    public static async Task<IReadOnlyList<Installation>> GetInstallations(this VrmAccount account)
+    {
+        "Fetching installations from VRM... ".Write();
+
+        try
+        {
+            var installations = await account.GetInstallations();
+
+            $"success ({installations.Count})".WriteLine(Green);
+
+            if (installations.Count <= 0)
+            {
+                "No installations found".WriteLine(Red);
+                Environment.Exit(1);
+            }
+            
+            return installations;
+        }
+        catch (AggregateException ae) when (ae.InnerExceptions.FirstOrDefault() is FlurlHttpException fhe &&
+                                            fhe.Call.Completed)
+        {
+            fhe.Call.Response.StatusCode.ToString().WriteLine();
+            "failed!".WriteLine(Red);
+            Environment.Exit(1);
+        }
+        catch (Exception e)
+        {
+            e.Message.WriteLine();
+            "failed!".WriteLine(Red);
+            Environment.Exit(1);
+        }
+
+        return Array.Empty<Installation>();
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/RemoteSupportConsole/VrmProxy.cs b/csharp/App_backup/RemoteSupportConsole/VrmProxy.cs
new file mode 100644
index 000000000..c629c5476
--- /dev/null
+++ b/csharp/App_backup/RemoteSupportConsole/VrmProxy.cs
@@ -0,0 +1,6 @@
+namespace InnovEnergy.App.RemoteSupportConsole;
+
+public readonly record struct VrmProxy(IDisposable Connection, String User, String Host, String Port) : IDisposable
+{
+    public void Dispose() => Connection.Dispose();
+}
\ No newline at end of file
diff --git a/csharp/App_backup/ResetBms/ResetBms.csproj b/csharp/App_backup/ResetBms/ResetBms.csproj
new file mode 100644
index 000000000..394fd6b96
--- /dev/null
+++ b/csharp/App_backup/ResetBms/ResetBms.csproj
@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <Import Project="../InnovEnergy.App.props" />
+
+    <PropertyGroup>
+        <RootNamespace>InnovEnergy.App.ResetBms</RootNamespace>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="..\..\Lib\Protocols\Modbus\Modbus.csproj" />
+    </ItemGroup>
+    
+    
+
+</Project>
diff --git a/csharp/App_backup/S3Explorer/Program.cs b/csharp/App_backup/S3Explorer/Program.cs
new file mode 100644
index 000000000..e00a2df02
--- /dev/null
+++ b/csharp/App_backup/S3Explorer/Program.cs
@@ -0,0 +1,156 @@
+using Amazon.IdentityManagement.Model;
+using Amazon.S3;
+using Amazon.S3.Model;
+using InnovEnergy.Lib.S3Utils;
+using InnovEnergy.Lib.S3Utils.DataTypes;
+using InnovEnergy.Lib.Utils;
+using S3Cfg = InnovEnergy.Lib.S3Utils.S3Cfg;
+
+namespace InnovEnergy.App.S3Explorer;
+
+public static class Program
+{
+    private const String BucketSalt = "-3e5b3069-214a-43ee-8d85-57d72000c19d";
+
+    public static async Task<Int32> Main(String[] args)
+    {
+        var region = S3Cfg.GetDefaultRegionAndCredentials();
+        var x = Iam.GetIamClient(region!);
+
+        var c = await x.ListRolesAsync(new ListRolesRequest());
+
+        
+            
+        
+        
+        
+        // Todo refactor S3Access into Lib
+        
+        // Sssssecret
+        if (args.Contains("-s"))
+            await SnakeGameSs.PlaySnake();
+
+        // Help message
+        if (args.Length < 1 || args.Contains("-h"))
+        {
+            Console.WriteLine("Usage: S3Explorer installation-id [from-unix-time] [to-unix-time] [nb-data-points]");
+            Console.WriteLine("-h Shows this message.");
+            Console.WriteLine("-s 🐍");
+            return 0;
+        }
+        
+        // Parsing Arguments
+        var bucketName = args[0] + BucketSalt;
+        var now = DateTime.Now;
+        
+        var startTime   = Int64.Parse(args.ElementAtOr(1, (now - TimeSpan.FromSeconds(20)).ToString()));
+        var endTime     = Int64.Parse(args.ElementAtOr(2, now.ToString()));
+        var nDataPoints = Int64.Parse(args.ElementAtOr(3, "10"));
+        
+        var timestampList = GetDataTimestamps(startTime, endTime, nDataPoints);
+
+        await PrintFiles(bucketName, timestampList);
+        
+        // Success
+        return 0;
+    }
+
+    public static async Task ListingObjectsAsync(IAmazonS3 client, String bucketName)
+    {
+        var request = new ListObjectsV2Request { BucketName = bucketName , Prefix = "1689236"};
+
+        var listObjectsV2Paginator = client.Paginators.ListObjectsV2(request);
+
+        await foreach (var response in listObjectsV2Paginator.Responses)
+        {
+            Console.WriteLine($"HttpStatusCode: {response.HttpStatusCode}");
+            Console.WriteLine($"Number of Keys: {response.KeyCount}");
+            
+            foreach (var entry in response.S3Objects)
+            {
+                Console.WriteLine($"Key = {entry.Key} Size = {entry.Size}");
+            }
+        }
+    }
+    
+
+    private static IEnumerable<Int64> GetDataTimestamps(Int64 startTime, Int64 endTime, Int64 nDataPoints)
+    {
+        // Calculating temporal distance of data files from the number of requested points. (rounding for int division)
+        var timeSpan = endTime - startTime;
+        var timeBetweenDataPoints = (Double)timeSpan / nDataPoints;
+        // We only upload data every second second so sampling more is impossible.
+        // If this ever changes we might have to change this as well.
+        
+        // Building a List of the timestamps we want to grab the files for.
+        for (Double i = startTime; i <= endTime; i += timeBetweenDataPoints)
+        {
+            //Rounding to even numbers only (we only save every second second)
+            var integer = (Int64) Math.Round(i);
+            yield return integer / 2 * 2;
+        }
+    }
+    
+    private static async Task PrintFiles(String bucketName, IEnumerable<Int64> timestamps)
+    {
+        var columns = new Dictionary<String, List<String?>>
+        {
+            ["timestamp"] = new()
+        };
+        var index = 0;
+        
+        foreach (var timestamp in timestamps)
+        {
+            var csvFileText = await GetFileText(bucketName, timestamp);
+
+            columns["timestamp"].Add(timestamp.ToString());
+            
+            var dict = csvFileText is null
+                     ? new Dictionary<String, String>()
+                     : csvFileText
+                      .Select(l => l.Split(";"))
+                      .ToDictionary(kv => kv[0], kv => kv[1]);
+            
+            foreach (var key in dict.Keys)
+            {
+                // if a key is not yet present in columns we need to backfill it with nulls
+                if (!columns.ContainsKey(key))
+                    columns[key] = Enumerable.Repeat<String?>(null, index).ToList();
+
+                columns[key].Add(dict[key]);
+            }
+
+            // if a key in columns is not present in this record (dict) (except the timestamp) we need to set it to null 
+            foreach (var key in columns.Keys.Where(key => !dict.ContainsKey(key) && key != "timestamp"))
+            {
+                columns[key].Add(null);
+            }
+            index++;
+        }
+
+        var headerKeys = columns
+                        .Keys
+                        .OrderBy(k => k)
+                        .Where(k => k != "timestamp")
+                        .Prepend("timestamp")
+                        .ToList();
+        
+        String.Join(';', headerKeys).WriteLine();
+
+        Enumerable.Range(0, index)
+                  .Select(i => headerKeys.Select(hk => columns[hk][i]).JoinWith(";"))
+                  .ForEach(Console.WriteLine);
+    }
+
+    // This Method extracts the Text from a given csv file on the s3 bucket
+    private static async Task<IReadOnlyList<String>?> GetFileText(String bucketName, Int64 timestamp)
+    {
+        var csv = await S3Cfg
+                       .GetDefaultRegionAndCredentials()!
+                       .Bucket(bucketName)
+                       .Path($"{timestamp}.csv")
+                       .GetObjectAsString();
+        
+        return csv.Split(Environment.NewLine);
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/S3Explorer/S3Explorer.csproj b/csharp/App_backup/S3Explorer/S3Explorer.csproj
new file mode 100644
index 000000000..920519e5e
--- /dev/null
+++ b/csharp/App_backup/S3Explorer/S3Explorer.csproj
@@ -0,0 +1,20 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    <Import Project="../InnovEnergy.App.props" />
+
+    <PropertyGroup>
+        <RootNamespace>InnovEnergy.App.S3Explorer</RootNamespace>
+    </PropertyGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="../../Lib/Time/Time.csproj" />
+      <ProjectReference Include="../Backend/Backend.csproj" />
+      <ProjectReference Include="../../Lib/S3Utils/S3Utils.csproj" />
+    </ItemGroup>
+
+
+    <ItemGroup>
+      <PackageReference Include="AWSSDK.S3" Version="3.7.205.17" />
+    </ItemGroup>
+
+</Project>
diff --git a/csharp/App_backup/S3Explorer/SnakeGameSS.cs b/csharp/App_backup/S3Explorer/SnakeGameSS.cs
new file mode 100644
index 000000000..1c07985e5
--- /dev/null
+++ b/csharp/App_backup/S3Explorer/SnakeGameSS.cs
@@ -0,0 +1,283 @@
+namespace InnovEnergy.App.S3Explorer;
+
+public static class SnakeGameSs
+{
+    public static async Task PlaySnake()
+    {
+        var tickRate = TimeSpan.FromMilliseconds(90);
+        var snakeGame = new SnakeGame();
+
+        using (var cts = new CancellationTokenSource())
+        {
+            async Task MonitorKeyPresses()
+            {
+                while (!cts.Token.IsCancellationRequested)
+                {
+                    if (Console.KeyAvailable)
+                    {
+                        var key = Console.ReadKey(intercept: true).Key;
+                        snakeGame.OnKeyPress(key);
+                    }
+
+                    await Task.Delay(10);
+                }
+            }
+
+            var monitorKeyPresses = MonitorKeyPresses();
+
+            do
+            {
+                snakeGame.OnGameTick();
+                snakeGame.Render();
+                await Task.Delay(tickRate);
+            } while (!snakeGame.GameOver);
+
+            // Allow time for user to weep before application exits.
+            for (var i = 0; i < 3; i++)
+            {
+                Console.Clear();
+                await Task.Delay(500);
+                snakeGame.Render();
+                await Task.Delay(500);
+            }
+
+            cts.Cancel();
+            await monitorKeyPresses;
+        }
+    }
+
+    enum Direction
+    {
+        Up,
+        Down,
+        Left,
+        Right
+    }
+
+    interface IRenderable
+    {
+        void Render();
+    }
+
+    readonly struct Position
+    {
+        public Position(int top, int left)
+        {
+            Top = top;
+            Left = left;
+        }
+
+        public int Top { get; }
+        public int Left { get; }
+
+        public Position RightBy(int n) => new Position(Top, Left + n);
+        public Position DownBy(int n) => new Position(Top + n, Left);
+    }
+
+    class Apple : IRenderable
+    {
+        public Apple(Position position)
+        {
+            Position = position;
+        }
+
+        public Position Position { get; }
+
+        public void Render()
+        {
+            Console.SetCursorPosition(Position.Left, Position.Top);
+            Console.Write("🐜");
+        }
+    }
+
+    class Snake : IRenderable
+    {
+        private List<Position> _body;
+        private int _growthSpurtsRemaining;
+
+        public Snake(Position spawnLocation, int initialSize = 1)
+        {
+            _body = new List<Position> { spawnLocation };
+            _growthSpurtsRemaining = Math.Max(0, initialSize - 1);
+            Dead = false;
+        }
+
+        public bool Dead { get; private set; }
+        public Position Head => _body.First();
+        private IEnumerable<Position> Body => _body.Skip(1);
+
+        public void Move(Direction direction)
+        {
+            if (Dead) throw new InvalidOperationException();
+
+            Position newHead;
+
+            switch (direction)
+            {
+                case Direction.Up:
+                    newHead = Head.DownBy(-1);
+                    break;
+
+                case Direction.Left:
+                    newHead = Head.RightBy(-1);
+                    break;
+
+                case Direction.Down:
+                    newHead = Head.DownBy(1);
+                    break;
+
+                case Direction.Right:
+                    newHead = Head.RightBy(1);
+                    break;
+
+                default:
+                    throw new ArgumentOutOfRangeException();
+            }
+
+            if (_body.Contains(newHead) || !PositionIsValid(newHead))
+            {
+                Dead = true;
+                return;
+            }
+
+            _body.Insert(0, newHead);
+
+            if (_growthSpurtsRemaining > 0)
+            {
+                _growthSpurtsRemaining--;
+            }
+            else
+            {
+                _body.RemoveAt(_body.Count - 1);
+            }
+        }
+
+        public void Grow()
+        {
+            if (Dead) throw new InvalidOperationException();
+
+            _growthSpurtsRemaining++;
+        }
+
+        public void Render()
+        {
+            Console.SetCursorPosition(Head.Left, Head.Top);
+            Console.Write("◉");
+
+            foreach (var position in Body)
+            {
+                Console.SetCursorPosition(position.Left, position.Top);
+                Console.Write("■");
+            }
+        }
+
+        private static bool PositionIsValid(Position position) =>
+            position.Top >= 0 && position.Left >= 0;
+    }
+
+    class SnakeGame : IRenderable
+    {
+        private static readonly Position Origin = new Position(0, 0);
+
+        private Direction _currentDirection;
+        private Direction _nextDirection;
+        private Snake _snake;
+        private Apple _apple;
+
+        public SnakeGame()
+        {
+            _snake = new Snake(Origin, initialSize: 5);
+            _apple = CreateApple();
+            _currentDirection = Direction.Right;
+            _nextDirection = Direction.Right;
+        }
+
+        public bool GameOver => _snake.Dead;
+
+        public void OnKeyPress(ConsoleKey key)
+        {
+            Direction newDirection;
+
+            switch (key)
+            {
+                case ConsoleKey.W:
+                    newDirection = Direction.Up;
+                    break;
+
+                case ConsoleKey.A:
+                    newDirection = Direction.Left;
+                    break;
+
+                case ConsoleKey.S:
+                    newDirection = Direction.Down;
+                    break;
+
+                case ConsoleKey.D:
+                    newDirection = Direction.Right;
+                    break;
+
+                default:
+                    return;
+            }
+
+            // Snake cannot turn 180 degrees.
+            if (newDirection == OppositeDirectionTo(_currentDirection))
+            {
+                return;
+            }
+
+            _nextDirection = newDirection;
+        }
+
+        public void OnGameTick()
+        {
+            if (GameOver) throw new InvalidOperationException();
+
+            _currentDirection = _nextDirection;
+            _snake.Move(_currentDirection);
+
+            // If the snake's head moves to the same position as an apple, the snake
+            // eats it.
+            if (_snake.Head.Equals(_apple.Position))
+            {
+                _snake.Grow();
+                _apple = CreateApple();
+            }
+        }
+
+        public void Render()
+        {
+            Console.Clear();
+            _snake.Render();
+            _apple.Render();
+            Console.SetCursorPosition(0, 0);
+        }
+
+        private static Direction OppositeDirectionTo(Direction direction)
+        {
+            switch (direction)
+            {
+                case Direction.Up: return Direction.Down;
+                case Direction.Left: return Direction.Right;
+                case Direction.Right: return Direction.Left;
+                case Direction.Down: return Direction.Up;
+                default: throw new ArgumentOutOfRangeException();
+            }
+        }
+
+        private static Apple CreateApple()
+        {
+            // Can be factored elsewhere.
+            const int numberOfRows = 20;
+            const int numberOfColumns = 20;
+
+            var random = new Random();
+            var top = random.Next(1, numberOfRows);
+            var left = random.Next(1, numberOfColumns);
+            var position = new Position(top, left);
+            var apple = new Apple(position);
+
+            return apple;
+        }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/Doc/AllStates.graphml b/csharp/App_backup/SaliMax/Doc/AllStates.graphml
new file mode 100644
index 000000000..5877679af
--- /dev/null
+++ b/csharp/App_backup/SaliMax/Doc/AllStates.graphml
@@ -0,0 +1,934 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
+  <!--Created by yEd 3.22-->
+  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
+  <key for="port" id="d1" yfiles.type="portgraphics"/>
+  <key for="port" id="d2" yfiles.type="portgeometry"/>
+  <key for="port" id="d3" yfiles.type="portuserdata"/>
+  <key attr.name="url" attr.type="string" for="node" id="d4"/>
+  <key attr.name="description" attr.type="string" for="node" id="d5"/>
+  <key for="node" id="d6" yfiles.type="nodegraphics"/>
+  <key for="graphml" id="d7" yfiles.type="resources"/>
+  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
+  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
+  <graph edgedefault="directed" id="G">
+    <data key="d0" xml:space="preserve"/>
+    <node id="n0">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="21.541219630823434" y="771.0152962325975"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="3.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">23</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="123.90064835074503" y="608.1111986375686"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">19</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="286.80474594577396" y="505.75176991764715"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">3</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="659.5852254317465" y="547.754042478705"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">9</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="477.9883579995691" y="484.21055028682383"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">1</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n5">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="795.6278615079401" y="683.7966785548988"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">13</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n6">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="859.171353699821" y="865.3935459870761"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">29</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n7">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="393.7937432135768" y="243.91085524521077"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">5</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n8">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="241.37121964689652" y="243.91085524521077"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">7</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n9">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="913.7233840476495" y="390.20661887776083"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">11</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n10">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="1077.2454208201314" y="623.2576236989755"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">15</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n11">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="393.7937432135768" y="32.482490272373525"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">21</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n12">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="546.2162667802571" y="243.91085524521077"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">17</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n13">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="913.7233840476495" y="243.91085524521077"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">25</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n14">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="966.0692273802504" y="543.8131759748794"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">27</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n15">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="1077.853337442888" y="743.4321654278978"/>
+          <y:Fill color="#FFEB9C" color2="#FF0000" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">31</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n16">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="837.6301340689974" y="1056.5771580408712"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="3.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">28</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n17">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="735.2707053490758" y="1219.4812556359"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">24</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n18">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="572.3666077540471" y="1321.8406843558214"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">8</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n19">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="63.54349219188089" y="1143.79577571857"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">6</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n20">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="381.1829957002519" y="1343.3819039866448"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">0</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n21">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="199.58612826807462" y="1279.8384117947637"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">4</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n22">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="0.0" y="962.1989082863927"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">22</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n23">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="381.1829957002519" y="1666.836774125534"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">16</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n24">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="20.81460729275682" y="1564.3516575638562"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">20</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n25">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="247.63957015025017" y="1827.5924542734685"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">18</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n26">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="247.63957015025017" y="1666.836774125534"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">2</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n27">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="735.2707053490758" y="1666.836774125534"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">10</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n28">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="572.3666077540471" y="1666.836774125534"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">12</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n29">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="572.3666077540471" y="1827.5924542734685"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">14</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n30">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="878.9380146340279" y="1661.335945415411"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">26</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n31">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="572.3666077540471" y="1988.348134421403"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">30</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <edge id="e0" source="n6" target="n5">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="side_slider" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="-7.6066190457218" xml:space="preserve" y="-64.14237361463267">turn off
+Inverters<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n3" target="n4">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-22.56775183821742" anchorY="-5.777885637190593" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="-0.33027906195516804" upY="0.9438833303083672" verticalTextPosition="bottom" visible="true" width="57.61639404296875" x="-89.06825945702936" xml:space="preserve" y="-24.807374214941643">switch to 
+grid tie<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n5" target="n3">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-10.834918063373834" anchorY="-3.763856446737236" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="-0.7071067811865482" upY="0.7071067811865468" verticalTextPosition="bottom" visible="true" width="58.68438720703125" x="-66.71650738854461" xml:space="preserve" y="-45.25998459060615">K3's open<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="5.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n4" target="n2">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-36.90577949345038" anchorY="-18.32720550204249" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.11196447610330729" upY="0.9937122098932426" verticalTextPosition="bottom" visible="true" width="50.044342041015625" x="-86.63545321568132" xml:space="preserve" y="-18.32720550204249">close K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n2" target="n1">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-34.073280050695104" anchorY="-24.282018553049284" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.5320320765153357" upY="0.8467241992282847" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="-80.73486874846864" xml:space="preserve" y="-24.282018553049284">turn on 
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e5" source="n1" target="n0">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-28.71096311323771" anchorY="3.695546315632555" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.8467241992282841" upY="0.5320320765153367" verticalTextPosition="bottom" visible="true" width="58.492401123046875" x="-59.83079674310035" xml:space="preserve" y="3.695546315632555">K3's close<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e6" source="n9" target="n3">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="21.046829314938122" ty="-45.01680098318991">
+            <y:Point x="874.0493776621389" y="435.20661887776083"/>
+            <y:Point x="715.6320547466846" y="519.7002048682145"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-95.81164490935862" anchorY="32.19070766070081" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" upX="0.47060708552926384" upY="0.8823428874590944" verticalTextPosition="bottom" visible="true" width="50.236328125" x="-140.13731172251363" xml:space="preserve" y="32.19070766070081">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5000000000000019" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e7" source="n10" target="n5">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-83.35912704662064" anchorY="-4.934957639868799" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.21016777322347138" upY="0.977665334916958" verticalTextPosition="bottom" visible="true" width="50.236328125" x="-132.47344360794696" xml:space="preserve" y="-4.934957639868799">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e8" source="n12" target="n4">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="15.152106842485068" ty="-45.029446684529034">
+            <y:Point x="581.2162667802571" y="365.51788910504735"/>
+            <y:Point x="528.1404648420541" y="447.09481487826343"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-11.667059533747079" anchorY="53.20739194515636" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.8382050245156525" upY="0.5453552391576654" verticalTextPosition="bottom" visible="true" width="48.38832092285156" x="-38.055883863066654" xml:space="preserve" y="53.20739194515636">turn off
+inverter<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5000000000000003" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e9" source="n15" target="n5">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="7.654500781281627" ty="24.1090580512913"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="side_slider" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="128.14088439941406" x="-170.15796772429303" xml:space="preserve" y="-60.704752015977874">turn off
+inverters and open K2<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e10" source="n11" target="n7">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-38.68821708305404" anchorY="33.66069253418277" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="1.0" upY="1.8369701987210297E-16" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="-38.68821708305405" xml:space="preserve" y="33.66069253418277">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e11" source="n14" target="n3">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="3.644099504220094" ty="8.35227299621522"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-54.95981067303774" anchorY="4.232641597982592" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.0405594629413826" upY="0.999177126422491" verticalTextPosition="bottom" visible="true" width="128.14088439941406" x="-182.99525132448088" xml:space="preserve" y="4.232641597982592">turn off
+inverters and open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5000000000000013" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e12" source="n13" target="n3">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
+            <y:Point x="875.759476311287" y="288.91085524521077"/>
+            <y:Point x="694.5852254317465" y="500.95688054473624"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-140.36047493803812" anchorY="60.311953252495755" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.7602822957199457" upY="0.6495928192451093" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="-175.52707071953466" xml:space="preserve" y="60.311953252495755">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5000000000000003" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e13" source="n17" target="n16">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" anchorX="28.71097587067584" anchorY="-3.6954992687265076" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.8467241992282839" upY="-0.532032076515337" verticalTextPosition="bottom" visible="true" width="58.492401123046875" x="11.48512197796088" xml:space="preserve" y="-64.0461521483817">K3's close<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e14" source="n23" target="n20">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="38.68824090899216" anchorY="-89.20073479644952" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-1.0" upY="-6.123233995736766E-17" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="2.0000123018144222" xml:space="preserve" y="-144.3091027163714">turn off 
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.4999999999999987" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e15" source="n20" target="n18">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="26.307533067371537" anchorY="35.968859753049855" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.11196447610330768" upY="-0.9937122098932426" verticalTextPosition="bottom" visible="true" width="73.21649169921875" x="22.199754772210497" xml:space="preserve" y="-8.686327108482413">switch to 
+island mode<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5000000000000004" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e16" source="n18" target="n17">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="34.27900103801858" anchorY="24.15265971881331" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.5320320765153352" upY="-0.846724199228285" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="14.759686588472483" xml:space="preserve" y="-35.714435444192404">turn on 
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e17" source="n22" target="n19">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" anchorX="-31.943410160379326" anchorY="25.84898726732399" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.9438833303083679" upY="-0.33027906195516604" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="-31.943410160379326" xml:space="preserve" y="13.73163353814864">turn off
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e18" source="n21" target="n20">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="26.036995974329216" anchorY="6.991824229345639" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.33027906195516815" upY="-0.9438833303083672" verticalTextPosition="bottom" visible="true" width="55.984375" x="26.036995974329216" xml:space="preserve" y="-12.21064613169991">K3 opens<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e19" source="n19" target="n21">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="4.486562103588653" anchorY="36.08589104047951" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.7071067811865477" upY="-0.7071067811865474" verticalTextPosition="bottom" visible="true" width="50.236328125" x="4.486562103588653" xml:space="preserve" y="21.70042985917759">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e20" source="n25" target="n20">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="-12.24576280270594" ty="10.36548331324957">
+            <y:Point x="118.40975029349039" y="1695.1176856990405"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-68.85844058790809" anchorY="-205.23460946299792" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.7201576722416737" upY="-0.6938104403303932" verticalTextPosition="bottom" visible="true" width="130.25289916992188" x="-95.2797499003236" xml:space="preserve" y="-324.4919101768264">turn off
+inverters and Open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5000000000000001" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e21" source="n26" target="n20">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="-13.677119243026084" ty="44.9895375252479">
+            <y:Point x="282.63957015025017" y="1624.6814853696424"/>
+            <y:Point x="402.50587645722584" y="1481.6545842023272"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="42.26630925296365" anchorY="-95.71994547025815" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.7664335365409145" upY="-0.6423236209772972" verticalTextPosition="bottom" visible="true" width="50.236328125" x="26.67389777947143" xml:space="preserve" y="-147.29025726298894">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.4999999999999997" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e22" source="n27" target="n18">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="19.39352371895791" ty="45.01703178967159">
+            <y:Point x="770.2707053490758" y="1624.8477957198368"/>
+            <y:Point x="626.760131473005" y="1451.0534797665293"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-38.53255297975011" anchorY="-123.76381877365907" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.7710894922153189" upY="0.6367267820620722" verticalTextPosition="bottom" visible="true" width="50.236328125" x="-86.20650129732047" xml:space="preserve" y="-162.50052351832747">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.500000000000001" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e23" source="n28" target="n18">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-2.000030429546655" anchorY="-99.48622193119718" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-1.0" upY="-6.123233995736766E-17" verticalTextPosition="bottom" visible="true" width="55.984375" x="-22.344144733135522" xml:space="preserve" y="-155.47059693119718">K3 opens<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e24" source="n29" target="n28">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="22.344083874042212" anchorY="-10.243393612998716" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-1.0" upY="-6.123233995736766E-17" verticalTextPosition="bottom" visible="true" width="50.236328125" x="1.9999695704533451" xml:space="preserve" y="-60.479721737998716">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e25" source="n30" target="n18">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="-4.644418613171297" ty="3.8960954734218376">
+            <y:Point x="913.5495752668096" y="1598.4359115261484"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="20.402772986958098" anchorY="-95.7266552045121" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.5909569293856483" upY="0.8067031099550106" verticalTextPosition="bottom" visible="true" width="128.14088439941406" x="-104.65003989273117" xml:space="preserve" y="-171.45239877795115">turn off
+inverters and open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.24999999999999953" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e26" source="n31" target="n29">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="38.68819817763108" anchorY="-8.293308863013408" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-1.0" upY="-6.123233995736766E-17" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="1.9999695704533451" xml:space="preserve" y="-62.42967666086497">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e27" source="n16" target="n6">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="24.92939609351697" anchorY="-21.690106143654248" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.9937122098932425" upY="-0.1119644761033088" verticalTextPosition="bottom" visible="true" width="55.792388916015625" x="4.713201310576952" xml:space="preserve" y="-79.40950232839855">K1 closes<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.4999999999999994" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="anywhere" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e28" source="n0" target="n22">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-24.918635525422495" anchorY="21.594702697511707" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.9937122098932426" upY="0.11196447610330804" verticalTextPosition="bottom" visible="true" width="55.984375" x="-31.18689674226863" xml:space="preserve" y="21.594702697511707">K1 opens<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.4999999999999979" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e29" source="n24" target="n21">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
+            <y:Point x="55.81460729275682" y="1523.820754474699"/>
+            <y:Point x="234.58612826807462" y="1404.8536338521308"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="88.28491738649134" anchorY="-52.831225794193415" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" upX="-0.5540104212935304" upY="-0.832509731533611" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="67.95925639931545" xml:space="preserve" y="-113.36664507338794">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5000000000000027" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e30" source="n7" target="n4">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="-12.103675434707498" ty="-0.8973455153978875">
+            <y:Point x="428.7937432135768" y="368.2394927091691"/>
+            <y:Point x="500.8846825648616" y="448.27634874029746"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-0.19439353507226542" anchorY="67.52807422348087" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.7430274178867984" upY="-0.669260977697398" verticalTextPosition="bottom" visible="true" width="58.68438720703125" x="-0.19439353507226542" xml:space="preserve" y="53.91255239427337">K3's open<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e31" source="n8" target="n2">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
+            <y:Point x="276.3712196468965" y="371.2720580009981"/>
+            <y:Point x="321.80474594577396" y="464.4058541099475"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" anchorX="-10.230074924814858" anchorY="67.35206739565979" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.8987592955167517" upY="-0.43844238928533374" verticalTextPosition="bottom" visible="true" width="58.68438720703125" x="-10.230074924814858" xml:space="preserve" y="58.43234531250035">K3's open<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d7">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/csharp/App_backup/SaliMax/Doc/SalimaxConfigReadme.txt b/csharp/App_backup/SaliMax/Doc/SalimaxConfigReadme.txt
new file mode 100644
index 000000000..559eb6ca8
--- /dev/null
+++ b/csharp/App_backup/SaliMax/Doc/SalimaxConfigReadme.txt
@@ -0,0 +1,110 @@
+"MinSoc": Number, 0 - 100 this is the minimum State of Charge that the batteries must not go below,
+  "ForceCalibrationCharge": Boolean (true or false), A flag to force a calibration charge,
+  "DisplayIndividualBatteries": Boolean (true or false), To display the indvidual batteries
+  "PConstant": Number 0 - 1,  P value of our controller.
+  "GridSetPoint": Number in Watts, The set point of our controller.
+  "BatterySelfDischargePower": Number, 200,  this a physical measurement of the self discharging power.
+  "HoldSocZone": Number, 1, This is magic number for the soft landing factor.
+  "IslandMode": { // Dc Link Voltage in Island mode
+    "AcDc": {
+      "MaxDcLinkVoltage": Number, 810, Max Dc Link Voltage,
+      "MinDcLinkVoltage": Number, 690, Min Dc Link Voltage,
+      "ReferenceDcLinkVoltage": Number, 750, Reference Dc Link
+    },
+    "DcDc": {
+      "LowerDcLinkVoltage": Number, 50, Lower Dc Link Window ,
+      "ReferenceDcLinkVoltage": 750, reference Dc Link
+      "UpperDcLinkVoltage": Number, 50, Upper Dc Link Window ,
+    }
+  },
+  "GridTie": {// Dc Link Voltage in GrieTie mode
+    "AcDc": {
+      "MaxDcLinkVoltage":Number, 780, Max Dc Link Voltage,
+      "MinDcLinkVoltage": Number, 690, Min Dc Link Voltage,
+      "ReferenceDcLinkVoltage": Number, 750, Reference Dc Link
+    },
+    "DcDc": {
+     "LowerDcLinkVoltage": Number, 20, Lower Dc Link Window ,
+      "ReferenceDcLinkVoltage": 750, reference Dc Link
+      "UpperDcLinkVoltage": Number, 20, Upper Dc Link Window ,
+    }
+  },
+  "MaxBatteryChargingCurrent":Number, 0 - 210, Max Charging current by DcDc
+  "MaxBatteryDischargingCurrent":Number, 0 - 210, Max Discharging current by DcDc
+  "MaxDcPower": Number, 0 - 10000, Max Power exported/imported by DcDc (10000 is the maximum)
+  "MaxChargeBatteryVoltage": Number, 57, Max Charging battery Voltage
+  "MinDischargeBatteryVoltage": Number, 0, Min Charging Battery Voltage
+  "Devices": { This is All Salimax devices (including offline ones)
+    "RelaysIp": {
+      "DeviceState": 1, // 0: is not present, 1: Present and Can be mesured, 2: Present but must be computed/calculted
+      "Host": "10.0.1.1", // Ip @ of the device in the local network
+      "Port": 502   // port
+    },
+    "GridMeterIp": {
+      "DeviceState": 1,
+      "Host": "10.0.4.1",
+      "Port": 502
+    },
+    "PvOnAcGrid": {
+      "DeviceState": 0, // If a device is not present
+      "Host": "false", // this is not important
+      "Port": 0 // this is not important
+    },
+    "LoadOnAcGrid": {
+      "DeviceState": 2, // this is a computed device
+      "Host": "true",
+      "Port": 0
+    },
+    "PvOnAcIsland": {
+      "DeviceState": 0,
+      "Host": "false",
+      "Port": 0
+    },
+    "IslandBusLoadMeterIp": {
+      "DeviceState": 1,
+      "Host": "10.0.4.2",
+      "Port": 502
+    },
+    "TruConvertAcIp": {
+      "DeviceState": 1,
+      "Host": "10.0.2.1",
+      "Port": 502
+    },
+    "PvOnDc": {
+      "DeviceState": 1,
+      "Host": "10.0.5.1",
+      "Port": 502
+    },
+    "LoadOnDc": {
+      "DeviceState": 0,
+      "Host": "false",
+      "Port": 0
+    },
+    "TruConvertDcIp": {
+      "DeviceState": 1,
+      "Host": "10.0.3.1",
+      "Port": 502
+    },
+    "BatteryIp": {
+      "DeviceState": 1,
+      "Host": "localhost",
+      "Port": 6855
+    },
+    "BatteryNodes": [ // this is a list of nodes
+      2,
+      3,
+      4,
+      5,
+      6
+    ]
+  },
+  "S3": { // this is parameters of S3 Buckets and co
+    "Bucket": "8-3e5b3069-214a-43ee-8d85-57d72000c19d",
+    "Region": "sos-ch-dk-2",
+    "Provider": "exo.io",
+    "Key": "EXO502627299197f83e8b090f63",
+    "Secret": "jUNYJL6B23WjndJnJlgJj4rc1i7uh981u5Aba5xdA5s",
+    "ContentType": "text/plain; charset=utf-8",
+    "Host": "8-3e5b3069-214a-43ee-8d85-57d72000c19d.sos-ch-dk-2.exo.io",
+    "Url": "https://8-3e5b3069-214a-43ee-8d85-57d72000c19d.sos-ch-dk-2.exo.io"
+  }
diff --git a/csharp/App_backup/SaliMax/Doc/States_Table.xlsx b/csharp/App_backup/SaliMax/Doc/States_Table.xlsx
new file mode 100644
index 000000000..776df481c
Binary files /dev/null and b/csharp/App_backup/SaliMax/Doc/States_Table.xlsx differ
diff --git a/csharp/App_backup/SaliMax/Doc/TransitionToGridTied.graphml b/csharp/App_backup/SaliMax/Doc/TransitionToGridTied.graphml
new file mode 100644
index 000000000..b73645cf6
--- /dev/null
+++ b/csharp/App_backup/SaliMax/Doc/TransitionToGridTied.graphml
@@ -0,0 +1,501 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
+  <!--Created by yEd 3.20.1-->
+  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
+  <key for="port" id="d1" yfiles.type="portgraphics"/>
+  <key for="port" id="d2" yfiles.type="portgeometry"/>
+  <key for="port" id="d3" yfiles.type="portuserdata"/>
+  <key attr.name="url" attr.type="string" for="node" id="d4"/>
+  <key attr.name="description" attr.type="string" for="node" id="d5"/>
+  <key for="node" id="d6" yfiles.type="nodegraphics"/>
+  <key for="graphml" id="d7" yfiles.type="resources"/>
+  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
+  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
+  <graph edgedefault="directed" id="G">
+    <data key="d0"/>
+    <node id="n0">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="1093.125" y="-372.5"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">19</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="923.125" y="-372.5"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">3</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="561.625" y="-350.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">9</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="753.125" y="-372.5"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">1</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="361.5" y="-207.5"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">13</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n5">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-230.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">29</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n6">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="1284.625" y="-395.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">23</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n7">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="923.125" y="-492.5"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">5</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n8">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="1093.125" y="-492.5"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">7</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n9">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="361.5" y="-350.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">11</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n10">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-110.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">15</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n11">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="753.125" y="-492.5"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">21</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n12">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="561.625" y="-470.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">17</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n13">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="361.5" y="-470.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">25</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n14">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-350.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">27</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n15">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="0.0" y="-110.0"/>
+          <y:Fill color="#FFEB9C" color2="#FF0000" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">31</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <edge id="e0" source="n5" target="n4">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-22.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="side_slider" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="33.19581604003906" xml:space="preserve" y="2.0">turn off
+Inverters<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n2" target="n3">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="22.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="57.61639404296875" x="31.941802978515625" xml:space="preserve" y="2.0">switch to 
+grid tie<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n4" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="30.0">
+            <y:Point x="482.0" y="-162.5"/>
+            <y:Point x="511.125" y="-275.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="52.86815850220398" anchorY="-29.097424414959733" configuration="AutoFlippingLabel" distance="5.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="-0.9680839425312168" upY="-0.25062617623308175" verticalTextPosition="bottom" visible="true" width="58.68438720703125" x="33.173348119879954" xml:space="preserve" y="-91.00760492412827">K3's open<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="5.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n3" target="n1">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.044342041015625" x="24.977828979492188" xml:space="preserve" y="2.0">close K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n1" target="n0">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="22.445816040039062" xml:space="preserve" y="2.0">turn on 
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e5" source="n0" target="n6">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="22.5"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="58.492401123046875" x="31.503799438476562" xml:space="preserve" y="2.0">K3's close<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e6" source="n9" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.236328125" x="39.9443359375" xml:space="preserve" y="-22.344114303588867">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e7" source="n10" target="n4">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="22.5">
+            <y:Point x="290.5" y="-65.0"/>
+            <y:Point x="311.0" y="-140.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="75.68078078809617" anchorY="-7.379352680589193" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="-0.9646152654368906" upY="-0.26366150588608345" verticalTextPosition="bottom" visible="true" width="50.236328125" x="56.056537569061355" xml:space="preserve" y="-61.202041482663645">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e8" source="n8" target="n6">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-22.5">
+            <y:Point x="1213.625" y="-447.5"/>
+            <y:Point x="1234.125" y="-372.5"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="16.16576645645864" anchorY="21.121410140198805" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.9646152654368907" upY="-0.263661505886083" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="16.16576645645864" xml:space="preserve" y="11.448136537337454">turn on 
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e9" source="n7" target="n8">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.044342041015625" x="24.977828979492188" xml:space="preserve" y="2.0">close K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e10" source="n12" target="n3">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-22.5">
+            <y:Point x="682.125" y="-425.0"/>
+            <y:Point x="702.625" y="-350.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="17.05167531189511" anchorY="24.362540099112607" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.9646152654368907" upY="-0.263661505886083" verticalTextPosition="bottom" visible="true" width="48.38832092285156" x="17.05167531189511" xml:space="preserve" y="14.689266496251257">turn off
+inverter<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e11" source="n15" target="n10">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="side_slider" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="22.93181610107422" xml:space="preserve" y="2.0">turn off
+inverters<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e12" source="n11" target="n7">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="22.93181610107422" xml:space="preserve" y="2.0">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e13" source="n14" target="n9">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="33.68181610107422" xml:space="preserve" y="2.0">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e14" source="n13" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-30.0">
+            <y:Point x="482.0" y="-425.0"/>
+            <y:Point x="511.125" y="-335.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="19.919637075116952" anchorY="31.15848493507559" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.9514217507056354" upY="-0.3078906498811288" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="19.919637075116952" xml:space="preserve" y="19.86252238622422">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d7">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/csharp/App_backup/SaliMax/Doc/TransitionToIsland.graphml b/csharp/App_backup/SaliMax/Doc/TransitionToIsland.graphml
new file mode 100644
index 000000000..800dffa96
--- /dev/null
+++ b/csharp/App_backup/SaliMax/Doc/TransitionToIsland.graphml
@@ -0,0 +1,487 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
+  <!--Created by yEd 3.20.1-->
+  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
+  <key for="port" id="d1" yfiles.type="portgraphics"/>
+  <key for="port" id="d2" yfiles.type="portgeometry"/>
+  <key for="port" id="d3" yfiles.type="portuserdata"/>
+  <key attr.name="url" attr.type="string" for="node" id="d4"/>
+  <key attr.name="description" attr.type="string" for="node" id="d5"/>
+  <key for="node" id="d6" yfiles.type="nodegraphics"/>
+  <key for="graphml" id="d7" yfiles.type="resources"/>
+  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
+  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
+  <graph edgedefault="directed" id="G">
+    <data key="d0"/>
+    <node id="n0">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="1099.0" y="-470.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">28</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="929.0" y="-470.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">24</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="759.0" y="-470.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">8</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-110.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">6</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="534.5" y="-230.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">0</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n5">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="340.0" y="-110.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">4</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n6">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="0.0" y="-110.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">22</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n7">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="340.0" y="-230.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">16</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n8">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-230.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">20</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n9">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-350.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">18</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n10">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="340.0" y="-350.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">2</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n11">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="534.5" y="-470.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">10</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n12">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="534.5" y="-590.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">12</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n13">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="340.0" y="-590.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">14</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n14">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="340.0" y="-470.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">26</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n15">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-590.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">30</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <edge id="e0" source="n8" target="n7">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="58.68438720703125" x="20.657806396484375" xml:space="preserve" y="-22.344114303588896">K3's open<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n1" target="n0">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="58.492401123046875" x="20.753799438476562" xml:space="preserve" y="2.0">K3's close<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n7" target="n4">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="5.0" xml:space="preserve" y="-38.688228607177734">turn off 
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.0" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n4" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="30.0">
+            <y:Point x="655.0" y="-185.0"/>
+            <y:Point x="708.5" y="-395.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="105.70300540936205" anchorY="-59.97368347153517" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.9690470117615817" upY="-0.24687626252021255" verticalTextPosition="bottom" visible="true" width="73.21649169921875" x="70.1503871107507" xml:space="preserve" y="-139.9813587213569">switch to 
+island mode<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5000000000000004" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n2" target="n1">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="22.93181610107422" xml:space="preserve" y="2.0">turn on 
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e5" source="n6" target="n3">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="22.445816040039062" xml:space="preserve" y="1.999999999999993">turn off
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e6" source="n5" target="n4">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="30.0">
+            <y:Point x="460.5" y="-65.0"/>
+            <y:Point x="484.0" y="-155.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="53.242913557579016" anchorY="-18.421155878144106" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.9675601644592253" upY="-0.25264070960879775" verticalTextPosition="bottom" visible="true" width="55.984375" x="33.55875897622129" xml:space="preserve" y="-77.72915843431224">K3 opens<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e7" source="n3" target="n5">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.236328125" x="24.8818359375" xml:space="preserve" y="2.0">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e8" source="n9" target="n10">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="22.93181610107422" xml:space="preserve" y="-38.688228607177734">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e9" source="n10" target="n4">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-30.0">
+            <y:Point x="460.5" y="-305.0"/>
+            <y:Point x="484.0" y="-215.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="57.83924953609818" anchorY="20.191383629556128" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.9675601644592253" upY="-0.25264070960879753" verticalTextPosition="bottom" visible="true" width="50.236328125" x="57.83924953609818" xml:space="preserve" y="15.05163215553495">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e10" source="n11" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.236328125" x="52.1318359375" xml:space="preserve" y="2.0">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e11" source="n12" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-30.0">
+            <y:Point x="655.0" y="-545.0"/>
+            <y:Point x="708.5" y="-455.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="64.66573623876036" anchorY="19.916163239593743" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.8595925806889443" upY="-0.5109800340762061" verticalTextPosition="bottom" visible="true" width="55.984375" x="64.66573623876036" xml:space="preserve" y="9.520727019495672">K3 opens<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e12" source="n13" target="n12">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.236328125" x="37.1318359375" xml:space="preserve" y="2.0">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e13" source="n14" target="n11">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="35.18181610107422" xml:space="preserve" y="2.0">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e14" source="n15" target="n13">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="22.93181610107422" xml:space="preserve" y="2.0">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d7">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/csharp/App_backup/SaliMax/HostList.txt b/csharp/App_backup/SaliMax/HostList.txt
new file mode 100755
index 000000000..9a03162c9
--- /dev/null
+++ b/csharp/App_backup/SaliMax/HostList.txt
@@ -0,0 +1,14 @@
+
+Prototype          ie-entwicklung@10.2.3.115         Prototype
+Salimax0001        ie-entwicklung@10.2.3.104         Marti Technik (Bern)
+Salimax0002        ie-entwicklung@10.2.4.29          Weidmann d (ZG)
+Salimax0003        ie-entwicklung@10.2.4.33          Elektrotechnik Stefan GmbH
+Salimax0004        ie-entwicklung@10.2.4.32          Biohof Gubelmann (Walde)
+Salimax0004A       ie-entwicklung@10.2.4.153
+Salimax0005        ie-entwicklung@10.2.4.36          Schreinerei Schönthal (Thun)
+Salimax0006        ie-entwicklung@10.2.4.35          Steakhouse Mettmenstetten
+Salimax0007        ie-entwicklung@10.2.4.154         LerchenhofHerr Twannberg 
+Salimax0008        ie-entwicklung@10.2.4.113         Wittmann Kottingbrunn
+Salimax0010        ie-entwicklung@10.2.4.211         Mohatech 1 (Beat Moser)
+Salimax0011        ie-entwicklung@10.2.4.239         Thomas Tschirren (Enggistein)
+SalidomoServer     ig@134.209.238.170
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/SaliMax.csproj b/csharp/App_backup/SaliMax/SaliMax.csproj
new file mode 100644
index 000000000..a62f192ea
--- /dev/null
+++ b/csharp/App_backup/SaliMax/SaliMax.csproj
@@ -0,0 +1,44 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <Import Project="../InnovEnergy.App.props" />
+
+    <PropertyGroup>
+        <RootNamespace>InnovEnergy.App.SaliMax</RootNamespace>
+    </PropertyGroup>
+    
+    <ItemGroup>
+        <PackageReference Version="3.2.4" Include="Flurl.Http" />
+        <PackageReference Version="7.0.0" Include="Microsoft.Extensions.Logging" />
+        <PackageReference Include="RabbitMQ.Client" Version="6.6.0" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="../../Lib/Devices/Adam6360D/Adam6360D.csproj" />
+      <ProjectReference Include="../../Lib/Devices/AMPT/Ampt.csproj" />
+      <ProjectReference Include="../../Lib/Devices/Battery48TL/Battery48TL.csproj" />
+      <ProjectReference Include="../../Lib/Devices/EmuMeter/EmuMeter.csproj" />
+      <ProjectReference Include="../../Lib/Devices/Trumpf/SystemControl/SystemControl.csproj" />
+      <ProjectReference Include="../../Lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj" />
+      <ProjectReference Include="../../Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj" />
+      <ProjectReference Include="../../Lib/Units/Units.csproj" />
+      <ProjectReference Include="../../Lib/Utils/Utils.csproj" />
+      <ProjectReference Include="../../Lib/Devices/Amax5070/Amax5070.csproj" />
+      <ProjectReference Include="../../Lib/Devices/Adam6060/Adam6060.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <Folder Include="resources\" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <Compile Remove="src\temp\**" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <EmbeddedResource Remove="src\temp\**" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <None Remove="src\temp\**" />
+    </ItemGroup>
+
+</Project>
diff --git a/csharp/App_backup/SaliMax/deploy.sh b/csharp/App_backup/SaliMax/deploy.sh
new file mode 100755
index 000000000..2e839ec08
--- /dev/null
+++ b/csharp/App_backup/SaliMax/deploy.sh
@@ -0,0 +1,73 @@
+#!/bin/bash
+
+dotnet_version='net6.0'
+salimax_ip="$1"
+username='ie-entwicklung'
+root_password='Salimax4x25'
+set -e
+
+# Define directories and configurations
+DEST_DIR="/home/$username/salimax/SaliMax"  # Path to the specific file
+BACKUP_ROOT="/home/$username/salimax/salimax_backup"  # Base backup directory
+TIMESTAMP=$(date +'%Y%m%d_%H%M%S')
+BACKUP_DIR="${BACKUP_ROOT}/script_backup_${TIMESTAMP}"  
+# Backup directory with unique name
+MAX_BACKUPS=5  # Limit to 5 backups
+
+echo -e "\n============================ Build ============================\n"
+
+# Build the project
+dotnet publish \
+    ./SaliMax.csproj \
+    -p:PublishTrimmed=false \
+    -c Release \
+    -r linux-x64
+
+echo -e "\n============================ Backup Old SaliMax File ============================\n"
+
+# SSH into the remote machine and create the backup directory
+ssh $username@$salimax_ip "mkdir -p $BACKUP_DIR"
+
+# Move the 'SaliMax' file to the backup folder on the remote machine
+ssh $username@$salimax_ip "rsync -av --exclude='${BACKUP_ROOT}/script_backup_*' --exclude='salimax_backup/*' $DEST_DIR $BACKUP_DIR/"
+
+echo -e "\n============================ Cleanup Old Backups ============================\n"
+
+# Cleanup old backups if the total number exceeds the maximum allowed
+echo "Existing backups:"
+ssh $username@$salimax_ip "ls -1d ${BACKUP_ROOT}/script_backup_* 2>/dev/null"
+
+# Count the backups created by this script
+BACKUP_COUNT=$(ssh $username@$salimax_ip "ls -1d ${BACKUP_ROOT}/script_backup_* 2>/dev/null | wc -l")
+echo "Current number of backups: $BACKUP_COUNT"
+
+if [ "$BACKUP_COUNT" -gt "$MAX_BACKUPS" ]; then
+    # Find and delete the oldest backup created by the script
+    OLD_BACKUP=$(ssh $username@$salimax_ip "find ${BACKUP_ROOT} -type d -name 'script_backup_*' | sort | head -n 1")
+    echo "Old backup to delete: $OLD_BACKUP"
+
+    if [ -n "$OLD_BACKUP" ]; then
+        echo "Backup limit reached. Deleting old backup: $OLD_BACKUP"
+        ssh $username@$salimax_ip "rm -rf $OLD_BACKUP" && echo "Deleted: $OLD_BACKUP" || echo "Failed to delete: $OLD_BACKUP"
+    else
+        echo "No valid backups to delete."
+    fi
+else
+    echo "Backup limit not reached. Current number of backups: $BACKUP_COUNT"
+fi
+
+
+echo -e "\n============================ Deploy ============================\n"
+
+# Deploy new files to the remote destination
+rsync -v \
+    --exclude '*.pdb' \
+    ./bin/Release/$dotnet_version/linux-x64/publish/* \
+    $username@"$salimax_ip":~/salimax
+
+echo -e "\nDeployment complete!\n"
+
+
+#echo -e "\n============================ Execute ============================\n" 
+
+#sshpass -p "$root_password" ssh -o StrictHostKeyChecking=no -t "$username"@"$salimax_ip" "echo '$root_password' | sudo -S sh -c 'cd salimax && ./restart'" 2>/dev/null
diff --git a/csharp/App_backup/SaliMax/deploy_all_installations.sh b/csharp/App_backup/SaliMax/deploy_all_installations.sh
new file mode 100755
index 000000000..533946856
--- /dev/null
+++ b/csharp/App_backup/SaliMax/deploy_all_installations.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+dotnet_version='net6.0'
+salimax_ip="$1"
+username='ie-entwicklung'
+root_password='Salimax4x25'
+
+set -e
+
+echo -e "\n============================ Build ============================\n" 
+
+dotnet publish  \
+       ./SaliMax.csproj \
+       -p:PublishTrimmed=false \
+       -c Release \
+       -r linux-x64 
+
+echo -e "\n============================ Deploy ============================\n" 
+#ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.29" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.211")
+#ip_addresses=("10.2.4.154" "10.2.4.29")
+ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.29" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" )
+
+
+
+for ip_address in "${ip_addresses[@]}"; do
+    rsync -v \
+          --exclude '*.pdb' \
+          ./bin/Release/$dotnet_version/linux-x64/publish/* \
+           $username@"$ip_address":~/salimax
+
+    ssh "$username"@"$ip_address" "cd salimax && echo '$root_password' | sudo -S ./restart"
+
+    echo "Deployed and ran commands on $ip_address"
+done
+
+
+
diff --git a/csharp/App_backup/SaliMax/downloadBatteryLogs/download-bms-log b/csharp/App_backup/SaliMax/downloadBatteryLogs/download-bms-log
new file mode 100644
index 000000000..b9c2e8f23
--- /dev/null
+++ b/csharp/App_backup/SaliMax/downloadBatteryLogs/download-bms-log
@@ -0,0 +1,284 @@
+#!/usr/bin/python2 -u
+# coding=utf-8
+import os
+import re
+import struct
+import serial
+import logging
+from sys import argv, exit
+from datetime import datetime
+from pymodbus.pdu import ModbusRequest, ModbusResponse, ExceptionResponse
+from pymodbus.other_message import ReportSlaveIdRequest
+from pymodbus.exceptions import ModbusException
+from pymodbus.pdu import ExceptionResponse
+from pymodbus.factory import ClientDecoder
+from pymodbus.client import ModbusSerialClient as Modbus
+logging.basicConfig(level=logging.INFO)
+
+
+
+# trick the pycharm type-checker into thinking Callable is in scope, not used at runtime
+# noinspection PyUnreachableCode
+if False:
+    from typing import List, Optional, NoReturn
+
+RESET_REGISTER = 0x2087
+FIRMWARE_VERSION_REGISTER = 1054
+SERIAL_STARTER_DIR = '/opt/victronenergy/serial-starter/'
+INSTALLATION_NAME_FILE = '/data/innovenergy/openvpn/installation-name'
+OUTPUT_DIR = '/data/innovenergy'
+
+
+class ReadLogRequest(ModbusRequest):
+
+    function_code = 0x42
+    _rtu_frame_size = 5  # not used
+
+    def __init__(self, address = None, **kwargs):
+
+        ModbusRequest.__init__(self, **kwargs)
+        self.sub_function = 0 if address is None else 1
+        self.address = address
+
+        # FUGLY as hell, but necessary bcs PyModbus cannot deal
+        # with responses that have lengths depending on the sub_function.
+        # it goes without saying that this isn't thread-safe
+        ReadLogResponse._rtu_frame_size = 9 if self.sub_function == 0 else 9+128
+
+    def encode(self):
+
+        if self.sub_function == 0:
+            return struct.pack('>B', self.sub_function)
+        else:
+            return struct.pack('>BI', self.sub_function, self.address)
+
+    def decode(self, data):
+        self.sub_function = struct.unpack('>B', data)
+
+    def execute(self, context):
+        print("EXECUTE1")
+
+    def get_response_pdu_size(self):
+        return ReadLogResponse._rtu_frame_size - 3
+
+    def __str__(self):
+        return "ReadLogAddressRequest"
+
+
+class ReadLogResponse(ModbusResponse):
+
+    function_code = 0x42
+    _rtu_frame_size = 9  # the WHOLE frame incl crc
+
+    def __init__(self, sub_function=0, address=b'\x00', data=None, **kwargs):
+        ModbusResponse.__init__(self, **kwargs)
+        self.sub_function = sub_function
+        self.address = address
+        self.data = data
+
+    def encode(self):
+        pass
+
+    def decode(self, data):
+        self.address, self.address = struct.unpack_from(">BI", data)
+        self.data = data[5:]
+
+    def __str__(self):
+        arguments = (self.function_code, self.address)
+        return "ReadLogAddressResponse(%s, %s)" % arguments
+
+# unfortunately we have to monkey-patch this global table because
+# the current (victron) version of PyModbus does not have a
+# way to "register" new function-codes yet
+ClientDecoder.function_table.append(ReadLogResponse)
+
+
+class LockTTY(object):
+
+    def __init__(self, tty):
+        # type: (str) -> None
+        self.tty = tty
+
+    def __enter__(self):
+        os.system(SERIAL_STARTER_DIR + 'stop-tty.sh ' + self.tty)
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        os.system(SERIAL_STARTER_DIR + 'start-tty.sh ' + self.tty)
+
+
+def wrap_try_except(error_msg):
+    def decorate(f):
+        def applicator(*args, **kwargs):
+            try:
+                return f(*args, **kwargs)
+            except:
+                print(error_msg)
+                exit(1)
+        return applicator
+    return decorate
+
+
+def init_modbus(tty):
+    # type: (str) -> Modbus
+
+    return Modbus(
+        port='/dev/' + tty,
+        method='rtu',
+        baudrate=115200,
+        stopbits=1,
+        bytesize=8,
+        timeout=0.5,  # seconds
+        parity=serial.PARITY_ODD)
+
+
+@wrap_try_except("Failed to download BMS log!")
+def download_log(modbus, node_id, battery_id):
+    # type: (Modbus, int, str) -> NoReturn
+
+    # Get address of latest log entry
+    # request = ReadLogRequest(unit=slave_id)
+
+    print ('downloading BMS log from node ' + str(node_id) + ' ...')
+
+    progress = -1
+    log_file = battery_id + "-node" + str(node_id) + "-"  + datetime.now().strftime('%d-%m-%Y') + ".bin"
+    print(log_file)
+
+    with open(log_file, 'w') as f:
+
+        eof = 0x200000
+        record = 0x40
+        for address in range(0, eof, 2*record):
+
+            percent = int(100*address/eof)
+
+            if percent != progress:
+                progress = percent
+                print('\r{}% '.format(progress),end='')
+
+            request = ReadLogRequest(address, slave=node_id)
+            result = modbus.execute(request)  # type: ReadLogResponse
+
+            address1 = "{:06X}".format(address)
+            address2 = "{:06X}".format(address+record)
+
+            data1 = result.data[:record]
+            data2 = result.data[record:]
+
+            line1 = address1 + ":" + ''.join('{:02X}'.format(byte) for byte in data1)
+            line2 = address2 + ":" + ''.join('{:02X}'.format(byte) for byte in data2)
+
+            lines = line1 + "\n" + line2 + "\n"
+            f.write(lines)
+
+    print("\r100%")
+    print("done")
+    print("wrote log to " + log_file)
+
+    return True
+
+
+@wrap_try_except("Failed to contact battery!")
+def identify_battery(modbus, node_id):
+    # type: (Modbus, int) -> str
+
+    target = 'battery #' + str(node_id)
+    print('contacting ' + target + ' ...')
+
+    request = ReportSlaveIdRequest(slave=node_id)
+    response = modbus.execute(request)
+
+    index_of_ff = response.identifier.find(b'\xff')
+    sid_response = response.identifier[index_of_ff + 1:].decode('utf-8').split(' ')
+
+    response = modbus.read_input_registers(address=FIRMWARE_VERSION_REGISTER, count=1, slave=node_id)
+
+    fw = '{0:0>4X}'.format(response.registers[0])
+    print("log string is",sid_response[0]+"-"+sid_response[1]+"-"+fw)
+        
+    #return re.sub(" +", "-", sid + " " + fw)
+    return sid_response[0]+"-"+sid_response[1]+"-"+fw
+
+
+def is_int(value):
+    # type: (str) -> bool
+    try:
+        _ = int(value)
+        return True
+    except ValueError:
+        return False
+
+
+def print_usage():
+    print ('Usage:   ' + __file__ + ' <slave id> <serial device>')
+    print ('Example: ' + __file__ + ' 2 ttyUSB0')
+    print ('')
+    print ('You can omit the "ttyUSB" prefix of the serial device:')
+    print ('  ' + __file__ + ' 2 0')
+    print ('')
+    print ('You can omit the serial device entirely when the "com.victronenergy.battery.<serial device>" service is running:')
+    print ('  ' + __file__ + ' 2')
+    print ('')
+
+
+def get_tty_from_battery_service_name():
+    # type: () -> Optional[str]
+
+    import dbus
+    bus = dbus.SystemBus()
+
+    tty = (
+        name.split('.')[-1]
+        for name in bus.list_names()
+        if name.startswith('com.victronenergy.battery.')
+    )
+
+    return next(tty, None)
+
+
+def parse_tty(tty):
+    # type: (Optional[str]) -> str
+
+    if tty is None:
+        return get_tty_from_battery_service_name()
+
+    if is_int(tty):
+        return 'ttyUSB' + argv[1]
+    else:
+        return tty
+
+
+def parse_cmdline_args(argv):
+    # type: (List[str]) -> (str, int)
+
+    slave_id = element_at_or_none(argv, 0)
+    tty = parse_tty(element_at_or_none(argv, 1))
+
+    if slave_id is None or tty is None:
+        print_usage()
+        exit(2)
+
+    print("tty=",tty)
+    print("slave id= ",slave_id)
+
+    return tty, int(slave_id)
+
+
+def element_at_or_none(lst, index):
+    return next(iter(lst[index:]), None)
+
+
+def main(argv):
+    # type: (List[str]) -> ()
+
+    tty, node_id = parse_cmdline_args(argv)
+
+    with init_modbus(tty) as modbus:
+        battery_id = identify_battery(modbus, node_id)
+        download_log(modbus, node_id, battery_id)
+
+    exit(0)
+
+
+main(argv[1:])
diff --git a/csharp/App_backup/SaliMax/downloadBatteryLogs/download_battery_logs.sh b/csharp/App_backup/SaliMax/downloadBatteryLogs/download_battery_logs.sh
new file mode 100755
index 000000000..20c3f05b4
--- /dev/null
+++ b/csharp/App_backup/SaliMax/downloadBatteryLogs/download_battery_logs.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+
+#Prototype          10.2.3.115         Prototype
+#Salimax0001        10.2.3.104         Marti Technik (Bern)
+#Salimax0002        10.2.4.29          Weidmann d (ZG)
+#Salimax0003        10.2.4.33          Elektrotechnik Stefan GmbH
+#Salimax0004        10.2.4.32          Biohof Gubelmann (Walde)
+#Salimax0005        10.2.4.36          Schreinerei Schönthal (Thun)
+#Salimax0006        10.2.4.35          Steakhouse Mettmenstetten
+#Salimax0007        10.2.4.154         LerchenhofHerr Twannberg 
+#Salimax0008        10.2.4.113         Wittmann Kottingbrunn
+
+dotnet_version='net6.0'
+ip_address="$1"
+battery_ids="$2"
+username='ie-entwicklung'
+root_password='Salimax4x25'
+
+if [ "$#" -lt 2 ]; then
+  echo "Error: Insufficient arguments. Usage: $0 <ip_address> <battery_ids>"
+  exit 1
+fi
+
+# Function to expand battery ids from a range
+expand_battery_ids() {
+  local range="$1"
+  local expanded_ids=()
+
+  IFS='-' read -r start end <<< "$range"
+  for ((i = start; i <= end; i++)); do
+    expanded_ids+=("$i")
+  done
+
+  echo "${expanded_ids[@]}"
+}
+
+# Check if battery_ids_arg contains a hyphen indicating a range
+if [[ "$battery_ids" == *-* ]]; then
+  # Expand battery ids from the range
+  battery_ids=$(expand_battery_ids "$battery_ids")
+else
+  # Use the provided battery ids
+  battery_ids=("$battery_ids")
+fi
+
+echo "ip_address: $ip_address"
+echo "Battery_ids: ${battery_ids[@]}"
+
+#ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.29")
+#battery_ids=("2" "3" "4" "5" "6" "7" "8" "9" "10" "11")
+
+set -e
+
+scp download-bms-log "$username"@"$ip_address":/home/"$username"
+ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S systemctl stop battery.service"
+ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S apt install python3-pip -y"
+ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S pip3 install pymodbus"
+
+for battery in "${battery_ids[@]}"; do
+    ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S python3 download-bms-log " "$battery" " ttyUSB0" 
+done
+ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S systemctl start battery.service"
+ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S rm download-bms-log"
+scp "$username"@"$ip_address":/home/"$username/*.bin" .
+ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S rm *.bin"
+
+echo "Deployed and ran commands on $ip_address"
+done
+
+
diff --git a/csharp/App_backup/SaliMax/resources/Battery.Service b/csharp/App_backup/SaliMax/resources/Battery.Service
new file mode 100644
index 000000000..ebead9a53
--- /dev/null
+++ b/csharp/App_backup/SaliMax/resources/Battery.Service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Battery48TL_TCP_Bridge
+
+[Service]
+Type=simple
+ExecStart=socat -x -d -d TCP-LISTEN:6855 GOPEN:/dev/ttyUSB0,rawer,b115200,cs8,parenb=1,parodd=1
+Restart=always
+RestartSec=500ms
+StartLimitInterval=0
+
+[Install]
+WantedBy=multi-user.target
diff --git a/csharp/App_backup/SaliMax/resources/PublicKey b/csharp/App_backup/SaliMax/resources/PublicKey
new file mode 100644
index 000000000..ae41b2935
--- /dev/null
+++ b/csharp/App_backup/SaliMax/resources/PublicKey
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCed5ANekhbdV/8nEwFyaqxbPGON+NZKAkZXKx2aMAbX6jYQpusXSf4lKxEp4vHX9q2ScWycluUEhlzwe9vaWIK6mxEG9gjtU0/tKIavqZ6qpcuiglal750e8tlDh+lAgg5K3v4tvV4uVEfFc42UzSC9cIBBKPBC41dc0xQKyFIDsSH6Qha1nyncKRC3OXUkOiiRvmbd4PVc9A5ah2vt+661pghZE19Qeh5ROn/Sma9C+9QIyUDCylezqptnT+Jdvs+JMCHk8nKK2A0bz1w0a8zzO7M1RLHfBLQ6o1SQAdV/Pmon8uQ9vLHc86l5r7WSTMEcjAqY3lGE9mdxsSZWNmp InnovEnergy
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/resources/Salimax.Service b/csharp/App_backup/SaliMax/resources/Salimax.Service
new file mode 100644
index 000000000..d823b591f
--- /dev/null
+++ b/csharp/App_backup/SaliMax/resources/Salimax.Service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Salimax Controller
+Wants=battery.service
+
+[Service]
+WorkingDirectory=/home/ie-entwicklung/salimax
+ExecStart=/home/ie-entwicklung/salimax/SaliMax
+WatchdogSec=30s
+Restart=always
+RestartSec=500ms
+
+[Install]
+WantedBy=multi-user.target
diff --git a/csharp/App_backup/SaliMax/src/AggregationService/Aggregator.cs b/csharp/App_backup/SaliMax/src/AggregationService/Aggregator.cs
new file mode 100644
index 000000000..6c32e435b
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/AggregationService/Aggregator.cs
@@ -0,0 +1,355 @@
+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;
+
+public static class Aggregator
+{
+    
+    public static async Task HourlyDataAggregationManager()
+    {
+        var currentDateTime = DateTime.Now;
+        var nextRoundedHour = currentDateTime.AddHours(1).AddMinutes(-currentDateTime.Minute).AddSeconds(-currentDateTime.Second);
+
+        // 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);
+        Console.WriteLine("Next Rounded Hour: " + nextRoundedHour);
+        // 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);
+        
+        while (true)
+        {
+            try
+            {
+                AggregatedData hourlyAggregatedData = CreateHourlyData("JsonLogDirectory",DateTime.Now.AddHours(-1).ToUnixTime(),DateTime.Now.ToUnixTime());
+                hourlyAggregatedData.Save("HourlyData");
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine("An error has occured when calculating hourly aggregated data, exception is:\n" + e);
+            }
+            await Task.Delay(TimeSpan.FromHours(1));
+        }
+    }
+    
+    public static async Task DailyDataAggregationManager()
+    {
+        var currentDateTime = DateTime.Now;
+        var nextRoundedHour = currentDateTime.AddDays(1).AddHours(-currentDateTime.Hour).AddMinutes(-currentDateTime.Minute).AddSeconds(-currentDateTime.Second);
+
+        // Calculate the time until the next rounded hour
+        var timeUntilNextDay = nextRoundedHour - currentDateTime;
+        Console.WriteLine("------------------------------------------DailyDataAggregationManager-------------------------------------------");
+        // Output the current and next rounded hour times
+        Console.WriteLine("Current Date and Time: " + currentDateTime);
+        Console.WriteLine("Next Rounded Hour: " + nextRoundedHour);
+        // Output the time until the next rounded hour
+        Console.WriteLine("Waiting for " + timeUntilNextDay.TotalHours + " hours...");
+        Console.WriteLine("-----------------------------------------------------------------------------------------------------------------");
+        
+        // Wait until the next rounded hour
+        await Task.Delay(timeUntilNextDay);
+        
+        while (true)
+        {
+            try
+            {
+                var currentTime = DateTime.Now;
+                AggregatedData dailyAggregatedData = CreateDailyData("HourlyData",currentTime.AddDays(-1).ToUnixTime(),currentTime.ToUnixTime());
+                dailyAggregatedData.Save("DailyData");
+                if (await dailyAggregatedData.PushToS3())
+                {
+                    //DeleteHourlyData("HourlyData",currentTime.ToUnixTime());
+                    //AggregatedData.DeleteDailyData("DailyData");
+                }
+                
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine("An error has occured when calculating daily aggregated data, exception is:\n" + e);
+            }
+            await Task.Delay(TimeSpan.FromDays(1));
+        }
+    }
+
+    private static void DeleteHourlyData(String myDirectory, Int64 beforeTimestamp)
+    {
+        var jsonFiles            = Directory.GetFiles(myDirectory, "*.json");
+        Console.WriteLine("Delete data before"+beforeTimestamp);
+        foreach (var jsonFile in jsonFiles)
+        {
+            if (IsFileWithinTimeRange(jsonFile, 0, beforeTimestamp))
+            {
+                File.Delete(jsonFile);
+                Console.WriteLine($"Deleted hourly data file: {jsonFile}");
+            }
+        }
+    }
+
+    // this for test
+    private static AggregatedData CreateHourlyData(String myDirectory, Int64 afterTimestamp, Int64 beforeTimestamp)
+    {
+        // Get all CSV files in the specified directory
+        var jsonFiles              = Directory.GetFiles(myDirectory, "*.json");
+        var batterySoc            = new List<Double>();
+        var pvPowerSum            = new List<Double>(); 
+        var heatingPower          = new List<Double>(); 
+        var gridPowerImport       = new List<Double>();
+        var gridPowerExport       = new List<Double>();
+        var batteryDischargePower = new List<Double>();
+        var batteryChargePower    = new List<Double>();
+
+        
+        Console.WriteLine("File timestamp should start after "+ afterTimestamp);
+        
+        foreach (var jsonFile in jsonFiles)
+        {
+            if (jsonFile == "LogDirectory/log.json")
+            {
+                continue;
+            }
+            
+            if (IsFileWithinTimeRange(jsonFile, afterTimestamp, beforeTimestamp))
+            {
+                try
+                {
+                    // Read and parse JSON
+
+                    var jsonData = File.ReadAllText(jsonFile);
+                    
+                    // Step 2: Find the first '{' character and trim everything before it
+                    int startIndex = jsonData.IndexOf('{');
+                    if (startIndex != -1)
+                    {
+                        jsonData = jsonData.Substring(startIndex); // Trim everything before '{'
+                    }
+
+                    var jsonObject = JObject.Parse(jsonData);
+
+
+                    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
+                            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}");
+                }
+            }
+        }
+
+        //Average Power (Watts)= Sum of Power Readings/Number of Readings
+                                    
+        //Then, you can use the average power in the energy formula:
+        //
+        //Energy (kWh)= (Average Power / 3600) × Time (1 seconds) 
+        //  
+        // Dividing the Average power readings by 3600 converts the result from watt-seconds to kilowatt-hours.
+
+        var dischargingEnergy           = (batteryDischargePower.Any() ? batteryDischargePower.Average() : 0.0)  / 3600;
+        var chargingEnergy              = (batteryChargePower.Any()    ? batteryChargePower.Average()    : 0.0)  / 3600;
+        var heatingPowerAvg             = (heatingPower.Any()          ? heatingPower.Average()          : 0.0)  / 3600;
+        
+        var dMaxSoc                     = batterySoc.Any()            ? batterySoc.Max()                 : 0.0;   
+        var dMinSoc                     = batterySoc.Any()            ? batterySoc.Min()                 : 0.0;            
+        var dSumGridExportPower         = gridPowerExport.Any()       ? gridPowerExport.Max() - gridPowerExport.Min(): 0.0;               
+        var dSumGridImportPower         = gridPowerImport.Any()       ? gridPowerImport.Max() - gridPowerImport.Min(): 0.0;
+        var dSumPvPower                 = pvPowerSum.Any()            ? pvPowerSum.Max()                 : 0.0; 
+        
+
+        AggregatedData aggregatedData = new AggregatedData
+        {
+            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
+        Console.WriteLine($"Max SOC:                   {aggregatedData.MaxSoc}");
+        Console.WriteLine($"Min SOC:                   {aggregatedData.MinSoc}");
+
+        Console.WriteLine($"DischargingBatteryBattery:      {aggregatedData.DischargingBatteryPower}");
+        Console.WriteLine($"ChargingBatteryPower:           {aggregatedData.ChargingBatteryPower}");
+        
+        Console.WriteLine($"SumGridExportPower:        {aggregatedData.GridExportPower}");
+        Console.WriteLine($"SumGridImportPower:        {aggregatedData.GridImportPower}");
+        
+        Console.WriteLine($"Min SOC:                   {aggregatedData.MinSoc}");
+
+        
+        Console.WriteLine("CSV data reading and storage completed.");
+        Console.WriteLine("-----------------------------------------------------------------------------------------------------------------");
+
+        return aggregatedData;
+    }
+
+    private static AggregatedData CreateDailyData(String myDirectory, Int64 afterTimestamp, Int64 beforeTimestamp)
+    {
+        // Get all CSV files in the specified directory
+        var jsonFiles              = Directory.GetFiles(myDirectory, "*.json");
+        var batterySoc            = new List<Double>();
+        var pvPower               = new List<Double>();
+        var gridPowerImport       = new List<Double>();
+        var gridPowerExport       = new List<Double>();
+        var batteryDischargePower = new List<Double>();
+        var batteryChargePower    = new List<Double>();
+        var heatingPowerAvg       = new List<Double>();
+
+        
+        
+        Console.WriteLine("File timestamp should start after "+ afterTimestamp);
+        
+        foreach (var jsonFile in jsonFiles)
+        {
+            if (jsonFile == "LogDirectory/log.json")
+            {
+                continue;
+            }
+
+            if (IsFileWithinTimeRange(jsonFile, afterTimestamp, beforeTimestamp))
+            {
+
+                try
+                {
+                    var jsonData = File.ReadAllText(jsonFile);
+                    //Console.WriteLine("Parse file "+jsonFile);
+                    
+                    // Parse JSON into a Dictionary
+                    var jsonDict = JsonConvert.DeserializeObject<Dictionary<string, Double>>(jsonData);
+                    
+                    // Process values
+                    foreach (var (variableName, value) in jsonDict)
+                    {
+                        switch (variableName)
+                        {
+                            case "MinSoc":
+                            case "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;
+
+                            default:
+                                // Ignore unknown variables
+                                break;
+                        }
+                    }
+                
+                }
+                catch (Exception e)
+                {
+                    Console.WriteLine($"Failed to parse JSON file {jsonFile}: {e.Message}");
+                }
+
+             
+            }
+        }
+
+        AggregatedData aggregatedData = new AggregatedData
+        {
+            MaxSoc                  = batterySoc.Any()            ? batterySoc.Max()               : 0.0,
+            MinSoc                  = batterySoc.Any()            ? batterySoc.Min()               : 0.0,
+            DischargingBatteryPower = batteryDischargePower.Any() ? batteryDischargePower.Average(): 0.0,
+            ChargingBatteryPower    = batteryChargePower.Any()    ? batteryChargePower.Average()   : 0.0,
+            GridExportPower         = gridPowerExport.Any()       ? gridPowerExport.Sum()          : 0.0,  
+            GridImportPower         = gridPowerImport.Any()       ? gridPowerImport.Sum()          : 0.0,  
+            PvPower                 = pvPower.Any()               ? pvPower.Last()                 : 0.0,
+            HeatingPower            = heatingPowerAvg.Any()       ? heatingPowerAvg.Average()      : 0.0, 
+        };
+        
+        // Print the stored CSV data for verification
+        Console.WriteLine($"Pv Power:                  {aggregatedData.PvPower}");
+        Console.WriteLine($"Heating Power:             {aggregatedData.HeatingPower}");
+        Console.WriteLine($"Max SOC:                   {aggregatedData.MaxSoc}");
+        Console.WriteLine($"Min SOC:                   {aggregatedData.MinSoc}");
+
+        Console.WriteLine($"ChargingBatteryPower:      {aggregatedData.DischargingBatteryPower}");
+        Console.WriteLine($"DischargingBatteryBattery: {aggregatedData.ChargingBatteryPower}");
+        
+        Console.WriteLine($"SumGridExportPower:        {aggregatedData.GridExportPower}");
+        Console.WriteLine($"SumGridImportPower:        {aggregatedData.GridImportPower}");
+        
+
+        
+        Console.WriteLine("CSV data reading and storage completed.");
+        Console.WriteLine("-----------------------------------------------------------------------------------------------------------------");
+
+        return aggregatedData;
+    }
+
+    // Custom method to check if a string is numeric
+    private static Boolean GetVariable(String value, String path)
+    {
+        return value == path;
+    }
+  
+    private static Boolean IsFileWithinTimeRange(string filePath, long startTime, long endTime)
+    {
+        var fileTimestamp  = long.TryParse(Path.GetFileNameWithoutExtension(filePath).Replace("log_", ""), out var fileTimestamp1) ? fileTimestamp1 : -1;
+        
+        return fileTimestamp >= startTime && fileTimestamp < endTime;
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/AggregationService/HourlyData.cs b/csharp/App_backup/SaliMax/src/AggregationService/HourlyData.cs
new file mode 100644
index 000000000..a7e50f83c
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/AggregationService/HourlyData.cs
@@ -0,0 +1,118 @@
+using System.IO.Compression;
+using System.Text;
+using System.Text.Json;
+using Flurl.Http;
+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;
+// shut up trim warnings
+#pragma warning disable IL2026  
+
+public class AggregatedData
+{
+    public required Double  MinSoc                      { get; set; } 
+    public required Double  MaxSoc                      { get; set; } 
+    public required Double  PvPower                     { get; set; }
+    public required Double  DischargingBatteryPower     { get; set; }
+    public required Double  ChargingBatteryPower        { get; set; }
+    public required Double  GridExportPower             { get; set; }
+    public required Double  GridImportPower             { get; set; }
+    public required Double  HeatingPower                { get; set; }
+
+    
+    private readonly S3Config? _S3Config = Config.Load().S3;
+
+    public void Save(String directory)
+    {
+        var date             = DateTime.Now.ToUnixTime();
+        var defaultHDataPath = Environment.CurrentDirectory + "/" + directory + "/";
+        var dataFilePath     = defaultHDataPath + date + ".json";
+        
+        if (!Directory.Exists(defaultHDataPath))
+        {
+            Directory.CreateDirectory(defaultHDataPath);
+            Console.WriteLine("Directory created successfully.");
+        }
+        Console.WriteLine("data file path is " + dataFilePath);
+        
+        try
+        {
+            // 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)
+        {
+            $"Failed to write config file {dataFilePath}\n{e}".WriteLine();
+            throw;
+        }
+    }
+    
+    public static void DeleteDailyData(String directory)
+    {
+        
+        var jsonFiles            = Directory.GetFiles(directory, "*.json");
+        foreach (var jsonFile in jsonFiles)
+        {
+            File.Delete(jsonFile);
+            Console.WriteLine($"Deleted daily data file: {jsonFile}");
+        }
+    }
+    
+    public async Task<Boolean> PushToS3()
+    {
+        var jsonString = JsonConvert.SerializeObject(this, Formatting.None);
+        if (_S3Config is null)
+            return false;
+
+        var s3Path   = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd") + ".json";
+        var request  = _S3Config.CreatePutRequest(s3Path);
+        
+        // Compress CSV data to a byte array
+        byte[] compressedBytes;
+        using (var memoryStream = new MemoryStream())
+        {
+            //Create a zip directory and put the compressed file inside
+            using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
+            {
+                var entry = archive.CreateEntry("data.json", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
+                using (var entryStream = entry.Open())
+                using (var writer = new StreamWriter(entryStream))
+                {
+                    writer.Write(jsonString);
+                }
+            }
+
+            compressedBytes = memoryStream.ToArray();
+        }
+
+        // Encode the compressed byte array as a Base64 string
+        string base64String = Convert.ToBase64String(compressedBytes);
+
+        // Create StringContent from Base64 string
+        var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64");
+
+        // Upload the compressed data (ZIP archive) to S3
+        var response = await request.PutAsync(stringContent);
+        
+
+        if (response.StatusCode != 200)
+        {
+            Console.WriteLine("ERROR: PUT");
+            var error = await response.GetStringAsync();
+            Console.WriteLine(error);
+            return false;
+        }
+
+        return true;
+    }
+    
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/DataTypes/AlarmOrWarning.cs b/csharp/App_backup/SaliMax/src/DataTypes/AlarmOrWarning.cs
new file mode 100644
index 000000000..e316d977f
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/DataTypes/AlarmOrWarning.cs
@@ -0,0 +1,9 @@
+namespace InnovEnergy.App.SaliMax.DataTypes;
+
+public class AlarmOrWarning
+{
+    public String? Date                   { get; set; }
+    public String? Time                   { get; set; }
+    public String? Description            { get; set; }
+    public String? CreatedBy              { get; set; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/DataTypes/Configuration.cs b/csharp/App_backup/SaliMax/src/DataTypes/Configuration.cs
new file mode 100644
index 000000000..57f023816
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/DataTypes/Configuration.cs
@@ -0,0 +1,12 @@
+using InnovEnergy.App.SaliMax.SystemConfig;
+
+namespace InnovEnergy.App.SaliMax.DataTypes;
+
+public class Configuration
+{
+    public Double                MinimumSoC                   { get; set; }
+    public Double                GridSetPoint                 { get; set; } 
+    public CalibrationChargeType CalibrationChargeState       { get; set; } 
+    public DateTime              CalibrationChargeDate        { get; set; } 
+}
+
diff --git a/csharp/App_backup/SaliMax/src/DataTypes/StatusMessage.cs b/csharp/App_backup/SaliMax/src/DataTypes/StatusMessage.cs
new file mode 100644
index 000000000..42655a543
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/DataTypes/StatusMessage.cs
@@ -0,0 +1,20 @@
+using InnovEnergy.App.SaliMax.Ess;
+
+namespace InnovEnergy.App.SaliMax.DataTypes;
+
+public class StatusMessage
+{
+    public required Int32                    InstallationId  { get; set; }
+    public required Int32                    Product         { get; set; }
+    public required SalimaxAlarmState        Status          { get; set; }
+    public required MessageType              Type            { get; set; }
+    public List<AlarmOrWarning>?             Warnings        { get; set; }
+    public List<AlarmOrWarning>?             Alarms          { get; set; }
+    public Int32                             Timestamp       { get; set; }
+}
+
+public enum MessageType
+{
+    AlarmOrWarning,
+    Heartbit
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Devices/AcPowerDevice.cs b/csharp/App_backup/SaliMax/src/Devices/AcPowerDevice.cs
new file mode 100644
index 000000000..ca375347a
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Devices/AcPowerDevice.cs
@@ -0,0 +1,8 @@
+using InnovEnergy.Lib.Units.Composite;
+
+namespace InnovEnergy.App.SaliMax.Devices;
+
+public class AcPowerDevice
+{
+    public required AcPower Power { get; init; } 
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Devices/DcPowerDevice.cs b/csharp/App_backup/SaliMax/src/Devices/DcPowerDevice.cs
new file mode 100644
index 000000000..486bc0c04
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Devices/DcPowerDevice.cs
@@ -0,0 +1,8 @@
+using InnovEnergy.Lib.Units.Power;
+
+namespace InnovEnergy.App.SaliMax.Devices;
+
+public class DcPowerDevice
+{
+    public required DcPower Power { get; init; } 
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Devices/DeviceState.cs b/csharp/App_backup/SaliMax/src/Devices/DeviceState.cs
new file mode 100644
index 000000000..728e3c3f1
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Devices/DeviceState.cs
@@ -0,0 +1,8 @@
+namespace InnovEnergy.App.SaliMax.Devices;
+
+public enum DeviceState
+{
+    Disabled,
+    Measured,
+    Computed
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Devices/SalimaxDevice.cs b/csharp/App_backup/SaliMax/src/Devices/SalimaxDevice.cs
new file mode 100644
index 000000000..ff7c8c6ca
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Devices/SalimaxDevice.cs
@@ -0,0 +1,8 @@
+using InnovEnergy.Lib.Utils.Net;
+
+namespace InnovEnergy.App.SaliMax.Devices;
+
+public class SalimaxDevice : Ip4Address
+{
+    public required DeviceState DeviceState { get; init; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Ess/Controller.cs b/csharp/App_backup/SaliMax/src/Ess/Controller.cs
new file mode 100644
index 000000000..5eab3ec60
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Ess/Controller.cs
@@ -0,0 +1,318 @@
+using InnovEnergy.App.SaliMax.SystemConfig;
+using InnovEnergy.Lib.Devices.Battery48TL;
+using InnovEnergy.Lib.Devices.Battery48TL.DataTypes;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes;
+using InnovEnergy.Lib.Units;
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.SaliMax.Ess;
+
+public static class Controller
+{
+    private static readonly Double BatteryHeatingPower = 200.0; // TODO: move to config
+    private static Boolean _hasRepetitiveCalibrationChargeChecked = false;
+    private static DateTime _nextDayAt10Am = DateTime.Now;
+
+    private static EssMode SelectControlMode(this StatusRecord s)
+    {
+        //return EssMode.OptimizeSelfConsumption;
+        
+        return s.StateMachine.State != 23  ? EssMode.Off
+             : s.MustHeatBatteries()       ? EssMode.HeatBatteries
+             : s.MustDoCalibrationCharge() ? EssMode.CalibrationCharge
+             : s.MustReachMinSoc()         ? EssMode.ReachMinSoc
+             : s.GridMeter is null         ? EssMode.NoGridMeter
+             :                               EssMode.OptimizeSelfConsumption;
+    }
+
+
+    public static EssControl ControlEss(this StatusRecord s)
+    {
+        var mode = s.SelectControlMode().WriteLine();
+
+        if (mode is EssMode.Off) // to test on prototype
+        {
+            if (s.StateMachine.State == 28 )
+            {
+                return new EssControl
+                {
+                    LimitedBy       = EssLimit.NoLimit,
+                    Mode            = EssMode.OffGrid,
+                    PowerCorrection = 0,
+                    PowerSetpoint   = 0 
+                };
+            }
+            return EssControl.Default;
+        }
+
+        // if we have no reading from the Grid meter, but we have a grid power (K1 is close),
+        // then we do only heat the battery to avoid discharging the battery and the oscillation between reach min soc and off mode
+        if (mode is EssMode.NoGridMeter)
+            return new EssControl
+            {
+                LimitedBy       = EssLimit.NoLimit,
+                Mode            = EssMode.NoGridMeter,
+                PowerCorrection = 0,
+                PowerSetpoint   = s.Battery == null ? 1000 : s.Battery.Devices.Count * s.Config.BatterySelfDischargePower // 1000 default value for heating the battery
+            };
+
+        var essDelta = s.ComputePowerDelta(mode);
+
+        var unlimitedControl = new EssControl
+        {
+            Mode            = mode,
+            LimitedBy       = EssLimit.NoLimit,
+            PowerCorrection = essDelta,
+            PowerSetpoint   = 0
+        };
+        
+        var limitedControl = unlimitedControl
+                            .LimitChargePower(s)
+                            .LimitDischargePower(s)
+                            .LimitInverterPower(s);
+
+        var currentPowerSetPoint = s.CurrentPowerSetPoint();
+
+        return limitedControl with { PowerSetpoint = currentPowerSetPoint + limitedControl.PowerCorrection };
+    }
+
+    private static EssControl LimitInverterPower(this EssControl control, StatusRecord s)
+    {
+        var powerDelta = control.PowerCorrection.Value;
+        
+        var acDcs = s.AcDc.Devices;
+
+        var nInverters = acDcs.Count;
+        
+        if (nInverters < 2)
+            return control;   // current loop cannot happen 
+
+        var nominalPower = acDcs.Average(d => d.Status.Nominal.Power);
+        var maxStep = nominalPower / 25; //TODO magic number to config
+        
+        var clampedPowerDelta = powerDelta.Clamp(-maxStep, maxStep);
+        
+        var dcLimited = acDcs.Any(d => d.Status.PowerLimitedBy == PowerLimit.DcLink);
+
+        if (!dcLimited)
+            return control with { PowerCorrection = clampedPowerDelta };
+
+        var maxPower = acDcs.Max(d => d.Status.Ac.Power.Active.Value);
+        var minPower = acDcs.Min(d => d.Status.Ac.Power.Active.Value);
+        
+        var powerDifference = maxPower - minPower;
+
+        if (powerDifference < maxStep)
+            return control with { PowerCorrection = clampedPowerDelta };
+
+        var correction = powerDifference / 4; //TODO magic number to config
+        
+        
+        // find out if we reach the lower or upper Dc limit by comparing the current Dc voltage to the reference voltage
+        return s.AcDc.Dc.Voltage > s.Config.GridTie.AcDc.ReferenceDcLinkVoltage
+             ? control with { PowerCorrection = clampedPowerDelta.ClampMax(-correction), LimitedBy = EssLimit.ChargeLimitedByMaxDcBusVoltage }
+             : control with { PowerCorrection = clampedPowerDelta.ClampMin(correction), LimitedBy = EssLimit.DischargeLimitedByMinDcBusVoltage };
+    }
+
+   
+    private static EssControl LimitChargePower(this EssControl control, StatusRecord s)
+    {
+                
+        //var maxInverterChargePower = s.ControlInverterPower(s.Config.MaxInverterPower);
+        var maxBatteryChargePower  = s.MaxBatteryChargePower();
+        maxBatteryChargePower.WriteLine("MaxBattery Charge Power");
+        
+        return control
+              //.LimitChargePower(, EssLimit.ChargeLimitedByInverterPower)
+              .LimitChargePower(maxBatteryChargePower, EssLimit.ChargeLimitedByBatteryPower);
+        
+    }
+    
+    private static EssControl LimitDischargePower(this EssControl control, StatusRecord s)
+    {
+        var maxBatteryDischargeDelta = s.Battery?.Devices.Where(b => b.IoStatus.ConnectedToDcBus).Sum(b => b.MaxDischargePower) ?? 0;
+        var keepMinSocLimitDelta     = s.ControlBatteryPower(s.HoldMinSocPower());
+
+        return control
+              .LimitDischargePower(maxBatteryDischargeDelta , EssLimit.DischargeLimitedByBatteryPower)
+              .LimitDischargePower(keepMinSocLimitDelta     , EssLimit.DischargeLimitedByMinSoc);
+    }
+
+    private static Double ComputePowerDelta(this StatusRecord s, EssMode mode)
+    {
+        var chargePower = s.AcDc.Devices.Sum(d => d.Status.Nominal.Power.Value);
+
+        return mode switch
+        {
+            EssMode.HeatBatteries            => s.ControlInverterPower(chargePower),
+            EssMode.ReachMinSoc              => s.ControlInverterPower(chargePower),
+            EssMode.CalibrationCharge        => s.ControlInverterPower(chargePower),
+            EssMode.OptimizeSelfConsumption  => s.ControlGridPower(s.Config.GridSetPoint),
+            _                                => throw new ArgumentException(null, nameof(mode))
+        };
+    }
+
+    private static Boolean MustHeatBatteries(this StatusRecord s)
+    {
+        var batteries = s.GetBatteries();
+
+        if (batteries.Count <= 0)
+            return true; // batteries might be there but BMS is without power 
+
+        return batteries
+              .Select(b => b.Temperatures.State)
+              .Contains(TemperatureState.Cold);
+    }
+
+    private static Double MaxBatteryChargePower(this StatusRecord s)
+    {
+        // This introduces a limit when we don't have communication with batteries
+        // Otherwise the limit will be 0 and the batteries will be not heated
+                                
+        var batteries = s.GetBatteries();
+        
+        var maxChargePower = batteries.Count == 0 
+                           ? s.Config.Devices.BatteryNodes.Length * BatteryHeatingPower
+                           : batteries.Sum(b => b.MaxChargePower);
+                
+        return maxChargePower;
+    }
+
+    private static Double CurrentPowerSetPoint(this StatusRecord s)
+    {
+        return s
+              .AcDc
+              .Devices
+              .Select(d =>
+              {
+                  var acPowerControl = d.Control.Ac.Power;
+                  
+                  return acPowerControl.L1.Active 
+                       + acPowerControl.L2.Active 
+                       + acPowerControl.L3.Active;
+              })
+              .Sum(p => p);
+    }
+
+    private static Boolean MustReachMinSoc(this StatusRecord s)
+    {
+        var batteries = s.GetBatteries();
+        
+        return batteries.Count > 0
+            && batteries.Any(b => b.Soc < s.Config.MinSoc);
+    }
+
+    private static IReadOnlyList<Battery48TlRecord> GetBatteries(this StatusRecord s)
+    {
+        return s.Battery?.Devices ?? Array.Empty<Battery48TlRecord>();
+    }
+
+    private static Boolean MustDoCalibrationCharge(this StatusRecord statusRecord)
+    {       
+        var calibrationChargeForced           = statusRecord.Config.ForceCalibrationChargeState;
+        var additionalCalibrationRequired     = AdditionalCalibrationDateHasBeenPassed(statusRecord.Config.DayAndTimeForAdditionalCalibration);
+        var repetitiveCalibrationRequired     = RepetitiveCalibrationDateHasBeenPassed(statusRecord.Config.DayAndTimeForRepetitiveCalibration);
+
+        var mustDoCalibrationCharge           =  calibrationChargeForced  == CalibrationChargeType.ChargePermanently || 
+                                                 (calibrationChargeForced == CalibrationChargeType.AdditionallyOnce  && additionalCalibrationRequired) || 
+                                                 (calibrationChargeForced == CalibrationChargeType.RepetitivelyEvery && repetitiveCalibrationRequired);
+        
+        Console.WriteLine("Next Repetitive calibration charge date is "+ statusRecord.Config.DayAndTimeForRepetitiveCalibration);
+        Console.WriteLine("Next Additional calibration charge date is "+ statusRecord.Config.DayAndTimeForAdditionalCalibration);
+        
+        if (statusRecord.Battery is not null)
+        {
+            if (calibrationChargeForced == CalibrationChargeType.AdditionallyOnce && statusRecord.Battery.Eoc )
+            {
+                statusRecord.Config.ForceCalibrationChargeState = CalibrationChargeType.RepetitivelyEvery;
+                
+            }
+            else if (calibrationChargeForced == CalibrationChargeType.RepetitivelyEvery && statusRecord.Battery.Eoc && _hasRepetitiveCalibrationChargeChecked)
+            {
+                statusRecord.Config.DayAndTimeForRepetitiveCalibration = statusRecord.Config.DayAndTimeForRepetitiveCalibration.AddDays(7);
+                _hasRepetitiveCalibrationChargeChecked                 = false;
+            }
+        }
+        
+        return mustDoCalibrationCharge;
+    }
+
+    private static Boolean RepetitiveCalibrationDateHasBeenPassed(DateTime calibrationChargeDate)
+    {
+        if (DateTime.Now >= calibrationChargeDate )
+        {
+            _hasRepetitiveCalibrationChargeChecked = true;
+            return true;
+        }
+        return false;   
+    }
+    private static Boolean AdditionalCalibrationDateHasBeenPassed(DateTime calibrationChargeDate)
+    {
+        if (DateTime.Now >= calibrationChargeDate )
+        {
+            return true;
+        }
+        return false;   
+    }
+    
+    private static Double ControlGridPower(this StatusRecord status, Double targetPower)
+    {
+        return ControlPower
+        (
+            measurement : status.GridMeter!.Ac.Power.Active,
+            target      : targetPower,
+            pConstant   : status.Config.PConstant
+        );
+    }
+
+    private static Double ControlInverterPower(this StatusRecord status, Double targetInverterPower)
+    {
+        return ControlPower
+        (
+            measurement : status.AcDc.Ac.Power.Active,
+            target      : targetInverterPower,
+            pConstant   : status.Config.PConstant
+        );
+    }
+
+    private static Double ControlBatteryPower(this StatusRecord status, Double targetBatteryPower)
+    {
+        return ControlPower
+        (
+            measurement: status.GetBatteries().Sum(b => b.Dc.Power),
+            target: targetBatteryPower,
+            pConstant: status.Config.PConstant
+        );
+    }
+
+    private static Double HoldMinSocPower(this StatusRecord s)
+    {
+        // TODO: explain LowSOC curve
+
+        var batteries = s.GetBatteries();
+
+        if (batteries.Count == 0)
+            return Double.NegativeInfinity;
+
+        var a = -2 * s.Config.BatterySelfDischargePower * batteries.Count / s.Config.HoldSocZone;
+        var b = -a * (s.Config.MinSoc + s.Config.HoldSocZone);
+
+        return batteries.Min(d => d.Soc.Value) * a + b;
+    }
+
+    private static Double ControlPower(Double measurement, Double target, Double pConstant)
+    {
+        var error = target - measurement;
+        return error * pConstant;
+    }
+
+    // ReSharper disable once UnusedMember.Local, TODO
+    private static Double ControlPowerWithIntegral(Double measurement, Double target, Double p, Double i)
+    {
+        var errorSum = 0; // this is must be sum of error
+        var error = target - measurement;
+        var kp = p * error;
+        var ki = i * errorSum;
+        return ki + kp;
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Ess/EssControl.cs b/csharp/App_backup/SaliMax/src/Ess/EssControl.cs
new file mode 100644
index 000000000..4886974f1
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Ess/EssControl.cs
@@ -0,0 +1,53 @@
+using InnovEnergy.Lib.Units.Power;
+
+namespace InnovEnergy.App.SaliMax.Ess;
+
+public record EssControl
+{
+    public required EssMode     Mode            { get; init; } 
+    public required EssLimit    LimitedBy       { get; init; } 
+    public required ActivePower PowerCorrection { get; init; }
+    public required ActivePower PowerSetpoint   { get; init; }
+
+    public static EssControl Default { get; } = new()
+    {
+        Mode            = EssMode.Off,
+        LimitedBy       = EssLimit.NoLimit,
+        PowerCorrection = 0,
+        PowerSetpoint   = 0
+    };
+    
+    
+    public EssControl LimitChargePower(Double controlDelta, EssLimit reason)
+    {
+        var overload = PowerCorrection - controlDelta;
+
+        if (overload <= 0)
+            return this;
+        
+        return this with
+        {
+            LimitedBy       = reason,
+            PowerCorrection = controlDelta,
+            PowerSetpoint   = PowerSetpoint - overload
+        };
+    }
+
+    public EssControl LimitDischargePower(Double controlDelta, EssLimit reason)
+    {
+        var overload = PowerCorrection - controlDelta;
+
+        if (overload >= 0)
+            return this;
+        
+        return this with
+        {
+            LimitedBy = reason, 
+            PowerCorrection = controlDelta,
+            PowerSetpoint = PowerSetpoint - overload
+        };
+    }
+}
+
+
+    
diff --git a/csharp/App_backup/SaliMax/src/Ess/EssLimit.cs b/csharp/App_backup/SaliMax/src/Ess/EssLimit.cs
new file mode 100644
index 000000000..3eb95b07c
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Ess/EssLimit.cs
@@ -0,0 +1,20 @@
+namespace InnovEnergy.App.SaliMax.Ess;
+
+public enum EssLimit
+{
+    NoLimit,
+    DischargeLimitedByMinSoc,
+    DischargeLimitedByBatteryPower,
+    DischargeLimitedByInverterPower,
+    ChargeLimitedByInverterPower,
+    ChargeLimitedByBatteryPower,
+    ChargeLimitedByMaxDcBusVoltage,
+    DischargeLimitedByMinDcBusVoltage,
+}
+
+
+// limitedBy = $"limiting discharging power in order to stay above min SOC: {s.Config.MinSoc}%";
+// limitedBy = $"limited by max battery discharging power: {maxDischargePower}";
+// limitedBy = $"limited by max inverter Dc to Ac power: {-s.Config.MaxInverterPower}W";
+// limitedBy = $"limited by max battery charging power: {maxChargePower}";
+// limitedBy = "limited by max inverter Ac to Dc power";
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Ess/EssMode.cs b/csharp/App_backup/SaliMax/src/Ess/EssMode.cs
new file mode 100644
index 000000000..08246ae77
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Ess/EssMode.cs
@@ -0,0 +1,12 @@
+namespace InnovEnergy.App.SaliMax.Ess;
+
+public enum EssMode
+{
+    Off,
+    OffGrid,
+    HeatBatteries,
+    CalibrationCharge,
+    ReachMinSoc,
+    NoGridMeter,
+    OptimizeSelfConsumption
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Ess/SalimaxAlarmState.cs b/csharp/App_backup/SaliMax/src/Ess/SalimaxAlarmState.cs
new file mode 100644
index 000000000..3eb4f0d95
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Ess/SalimaxAlarmState.cs
@@ -0,0 +1,8 @@
+namespace InnovEnergy.App.SaliMax.Ess;
+
+public enum SalimaxAlarmState
+{
+    Green,
+    Orange,
+    Red
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Ess/StatusRecord.cs b/csharp/App_backup/SaliMax/src/Ess/StatusRecord.cs
new file mode 100644
index 000000000..b7c72426c
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Ess/StatusRecord.cs
@@ -0,0 +1,55 @@
+using System.Text.Json;
+using InnovEnergy.App.SaliMax.Devices;
+using InnovEnergy.App.SaliMax.SaliMaxRelays;
+using InnovEnergy.App.SaliMax.System;
+using InnovEnergy.App.SaliMax.SystemConfig;
+using InnovEnergy.Lib.Devices.AMPT;
+using InnovEnergy.Lib.Devices.Battery48TL;
+using InnovEnergy.Lib.Devices.EmuMeter;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
+
+namespace InnovEnergy.App.SaliMax.Ess;
+
+public record StatusRecord
+{   
+    public required AcDcDevicesRecord   AcDc                { get; init; }  
+    public required DcDcDevicesRecord   DcDc                { get; init; }  
+    public required Battery48TlRecords? Battery             { get; init; } 
+    public required EmuMeterRegisters?  GridMeter           { get; init; } 
+    public required EmuMeterRegisters?  LoadOnAcIsland      { get; init; } 
+    public required AcPowerDevice?      LoadOnAcGrid        { get; init; } 
+    public required AmptStatus?         PvOnAcGrid          { get; init; } 
+    public required AmptStatus?         PvOnAcIsland        { get; init; } 
+    public required AcPowerDevice?      AcGridToAcIsland    { get; init; } 
+    public required DcPowerDevice?      AcDcToDcLink        { get; init; } 
+    public required DcPowerDevice?      LoadOnDc            { get; init; } 
+    public required IRelaysRecord?      Relays              { get; init; } 
+    public required AmptStatus?         PvOnDc              { get; init; } 
+    public required Config              Config              { get; set; } 
+    public required SystemLog           Log                 { get; init; }    // TODO: init only
+    
+    public required EssControl          EssControl          { get; set; }    // TODO: init only
+    public required StateMachine        StateMachine        { get; init; }
+
+
+    public string ToJson()
+    {
+        // Try to get the "Battery" property via reflection
+        // var batteryProperty = thing.GetType().GetProperty("Battery");
+        // if (batteryProperty == null)
+        //     throw new InvalidOperationException("The object does not have a 'Battery' property.");
+        //
+        // // Retrieve the value of the Battery property
+       // var batteryValue = Battery.GetValue(thing);
+        var jsonOptions = new JsonSerializerOptions { WriteIndented = true };
+
+        // Serialize the Battery property
+        Console.WriteLine("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
+        string json = JsonSerializer.Serialize(this.Battery, jsonOptions);
+        Console.WriteLine(json);
+
+        
+        return json;
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Ess/SystemLog.cs b/csharp/App_backup/SaliMax/src/Ess/SystemLog.cs
new file mode 100644
index 000000000..67e2cedb6
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Ess/SystemLog.cs
@@ -0,0 +1,11 @@
+using InnovEnergy.App.SaliMax.DataTypes;
+
+namespace InnovEnergy.App.SaliMax.Ess;
+
+public record SystemLog
+{
+    public required String?               Message                { get; init; }
+    public required SalimaxAlarmState     SalimaxAlarmState      { get; init; }
+    public required List<AlarmOrWarning>? SalimaxAlarms          { get; set; }
+    public required List<AlarmOrWarning>? SalimaxWarnings        { get; set; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Flow.cs b/csharp/App_backup/SaliMax/src/Flow.cs
new file mode 100644
index 000000000..f598b4fe2
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Flow.cs
@@ -0,0 +1,56 @@
+using System.Diagnostics.CodeAnalysis;
+using InnovEnergy.Lib.Units;
+using InnovEnergy.Lib.Utils;
+
+
+namespace InnovEnergy.App.SaliMax;
+
+public static class Flow
+{
+    private static readonly String RightArrowChar   = ">";
+    private static readonly String LeftArrowChar    = "<";
+    private static readonly String DownArrowChar    = "V";
+    private static readonly String UpArrowChar      = "^";
+    private static readonly String UnknownArrowChar = "?";
+
+    public static TextBlock Horizontal(Unit? amount) => Horizontal(amount, 10);
+
+    public static TextBlock Horizontal(Unit? amount, Int32 width)
+    {
+        var label = amount?.ToDisplayString() ?? "";
+        
+        var arrowChar = amount switch
+        {
+            { Value: < 0  } => LeftArrowChar,
+            { Value: >= 0 } => RightArrowChar,
+            _               => UnknownArrowChar,
+        };
+        
+        //var arrowChar = amount.Value < 0 ? LeftArrowChar : RightArrowChar;
+        var arrow     = Enumerable.Repeat(arrowChar, width).Join();
+
+        // note : appending "fake label" below to make it vertically symmetric
+        return TextBlock.AlignCenterHorizontal(label, arrow, ""); 
+    }
+
+    public static TextBlock Vertical(Unit? amount) => Vertical(amount, 4);
+
+    [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
+    [SuppressMessage("ReSharper", "CoVariantArrayConversion")]
+    public static TextBlock Vertical(Unit? amount, Int32 height)
+    {
+        var label     = amount?.ToDisplayString() ?? UnknownArrowChar;
+        var arrowChar = amount switch
+        {
+            { Value: < 0  } => UpArrowChar,
+            { Value: >= 0 } => DownArrowChar,
+            _               => UnknownArrowChar,
+        };
+        
+        // var arrowChar =  amount is null ? UnknownArrowChar
+        //                : amount.Value < 0 ? UpArrowChar 
+        //                : DownArrowChar;
+        
+        return TextBlock.AlignCenterHorizontal(arrowChar, arrowChar, label, arrowChar, arrowChar);
+    }
+}
diff --git a/csharp/App_backup/SaliMax/src/LogFileConcatenator.cs b/csharp/App_backup/SaliMax/src/LogFileConcatenator.cs
new file mode 100644
index 000000000..60a7f5486
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/LogFileConcatenator.cs
@@ -0,0 +1,34 @@
+using System.Text;
+
+namespace InnovEnergy.App.SaliMax;
+
+public class LogFileConcatenator
+{
+    private readonly string _logDirectory;
+
+    public LogFileConcatenator(String logDirectory = "JsonLogDirectory/")
+    {
+        _logDirectory = logDirectory;
+    }
+
+    public String ConcatenateFiles(int numberOfFiles)
+    {
+        var logFiles = Directory
+            .GetFiles(_logDirectory, "log_*.json")
+            .OrderByDescending(file => file)
+            .Take(numberOfFiles)
+            .OrderBy(file => file)
+            .ToList();
+
+        var concatenatedContent = new StringBuilder();
+
+        foreach (var fileContent in logFiles.Select(File.ReadAllText))
+        {
+            concatenatedContent.AppendLine(fileContent);
+            //concatenatedContent.AppendLine(); // Append an empty line to separate the files // maybe we don't need this 
+        }
+
+        return concatenatedContent.ToString();
+    }
+}
+
diff --git a/csharp/App_backup/SaliMax/src/Logfile.cs b/csharp/App_backup/SaliMax/src/Logfile.cs
new file mode 100644
index 000000000..e8e290424
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Logfile.cs
@@ -0,0 +1,49 @@
+using InnovEnergy.Lib.Utils;
+using Microsoft.Extensions.Logging;
+
+namespace InnovEnergy.App.SaliMax;
+
+public class CustomLogger : ILogger
+{
+    private readonly String _logFilePath;
+    //private readonly Int64  _maxFileSizeBytes;
+    private readonly Int32  _maxLogFileCount;
+    private          Int64  _currentFileSizeBytes;
+
+    public CustomLogger(String logFilePath, Int32 maxLogFileCount)
+    {
+        _logFilePath          = logFilePath;
+        _maxLogFileCount      = maxLogFileCount;
+        _currentFileSizeBytes = File.Exists(logFilePath) ? new FileInfo(logFilePath).Length : 0;
+    }
+
+    public IDisposable? BeginScope<TState>(TState state) where TState : notnull => throw new NotImplementedException();
+
+    public Boolean IsEnabled(LogLevel logLevel) => true; // Enable logging for all levels
+
+    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception, String> formatter)
+    {
+        var logMessage = formatter(state, exception!);
+        
+        // Check the log file count and delete the oldest file if necessary
+        var logFileDir      = Path.GetDirectoryName(_logFilePath)!;
+        var logFileExt      = Path.GetExtension(_logFilePath);
+        var logFileBaseName = Path.GetFileNameWithoutExtension(_logFilePath);
+        
+        var logFiles = Directory
+            .GetFiles(logFileDir, $"{logFileBaseName}_*{logFileExt}")
+            .OrderBy(file => file)
+            .ToList();
+
+        if (logFiles.Count >= _maxLogFileCount)
+        {
+            File.Delete(logFiles.First());
+        }
+
+        var roundedUnixTimestamp = DateTime.Now.ToUnixTime() % 2 == 0 ? DateTime.Now.ToUnixTime() : DateTime.Now.ToUnixTime() + 1;
+        var timestamp = "Timestamp;" + roundedUnixTimestamp + Environment.NewLine;
+        
+        var logFileBackupPath = Path.Combine(logFileDir, $"{logFileBaseName}_{DateTime.Now.ToUnixTime()}{logFileExt}");
+        File.AppendAllText(logFileBackupPath, timestamp + logMessage + Environment.NewLine);
+    }
+}
diff --git a/csharp/App_backup/SaliMax/src/Logger.cs b/csharp/App_backup/SaliMax/src/Logger.cs
new file mode 100644
index 000000000..800f92a34
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Logger.cs
@@ -0,0 +1,39 @@
+using Microsoft.Extensions.Logging;
+
+namespace InnovEnergy.App.SaliMax;
+
+public static class Logger
+{
+    // Specify the maximum log file size in bytes (e.g., 1 MB)
+    
+    //private const Int32  MaxFileSizeBytes = 2024 * 30;       // TODO: move to settings
+    private const Int32  MaxLogFileCount = 5000;               // TODO: move to settings
+    private const String LogFilePath = "JsonLogDirectory/log.json"; // TODO: move to settings
+    
+    // ReSharper disable once InconsistentNaming
+    private static readonly ILogger _logger = new CustomLogger(LogFilePath,  MaxLogFileCount);
+
+    public static T LogInfo<T>(this T t) where T : notnull
+    {
+        _logger.LogInformation(t.ToString());  // TODO: check warning
+        return t;
+    }
+    
+    public static T LogDebug<T>(this T t) where T : notnull
+    {
+        _logger.LogDebug(t.ToString());  // TODO: check warning
+        return t;
+    }
+    
+    public static T LogError<T>(this T t) where T : notnull
+    {
+        _logger.LogError(t.ToString());  // TODO: check warning
+        return t;
+    }
+    
+    public static T LogWarning<T>(this T t) where T : notnull
+    {
+        _logger.LogWarning(t.ToString());  // TODO: check warning
+        return t;
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/MiddlewareClasses/MiddlewareAgent.cs b/csharp/App_backup/SaliMax/src/MiddlewareClasses/MiddlewareAgent.cs
new file mode 100644
index 000000000..32950ff50
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/MiddlewareClasses/MiddlewareAgent.cs
@@ -0,0 +1,93 @@
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Text;
+using System.Text.Json;
+using InnovEnergy.App.SaliMax.DataTypes;
+
+namespace InnovEnergy.App.SaliMax.MiddlewareClasses;
+
+public static class MiddlewareAgent
+{
+    private static UdpClient   _udpListener = null!;
+    private static IPAddress? _controllerIpAddress;
+    private static EndPoint? _endPoint;
+    
+    public static void InitializeCommunicationToMiddleware()
+    {
+        _controllerIpAddress = FindVpnIp();
+        if (Equals(IPAddress.None, _controllerIpAddress))
+        {
+            Console.WriteLine("There is no VPN interface, exiting...");
+        }
+
+        const Int32 udpPort = 9000;
+        _endPoint = new IPEndPoint(_controllerIpAddress, udpPort);
+
+        _udpListener = new UdpClient();
+        _udpListener.Client.Blocking = false;
+        _udpListener.Client.Bind(_endPoint);
+    }
+    
+    private static IPAddress FindVpnIp()
+    {
+        const String interfaceName = "innovenergy";
+        
+        var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
+        
+        foreach (var networkInterface in networkInterfaces)
+        {
+            if (networkInterface.Name == interfaceName)
+            {
+                var ipProps         = networkInterface.GetIPProperties();
+                var uniCastIPs      = ipProps.UnicastAddresses;
+                var controllerIpAddress = uniCastIPs[0].Address;
+
+                Console.WriteLine("VPN IP is: "+ uniCastIPs[0].Address);
+                return controllerIpAddress;
+            }
+        }
+
+        return IPAddress.None;
+    }
+
+    public static Configuration? SetConfigurationFile()
+    {
+        if (_udpListener.Available > 0)
+        {
+
+            IPEndPoint? serverEndpoint = null;
+
+            var replyMessage = "ACK";
+            var replyData = Encoding.UTF8.GetBytes(replyMessage);
+
+            var udpMessage = _udpListener.Receive(ref serverEndpoint);
+            var message = Encoding.UTF8.GetString(udpMessage);
+
+            var config = JsonSerializer.Deserialize<Configuration>(message);
+
+            if (config != null)
+            {
+                Console.WriteLine($"Received a configuration message: GridSetPoint is " + config.GridSetPoint +
+                                  ", MinimumSoC is " + config.MinimumSoC + " and ForceCalibrationCharge is " +
+                                  config.CalibrationChargeState + " and CalibrationChargeDate is " +
+                                  config.CalibrationChargeDate);
+
+                // Send the reply to the sender's endpoint
+                _udpListener.Send(replyData, replyData.Length, serverEndpoint);
+                Console.WriteLine($"Replied to {serverEndpoint}: {replyMessage}");
+                return config;
+            }
+        }
+
+        if (_endPoint != null && !_endPoint.Equals(_udpListener.Client.LocalEndPoint as IPEndPoint))
+        {
+            Console.WriteLine("UDP address has changed, rebinding...");
+            InitializeCommunicationToMiddleware();
+        }
+        
+
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/MiddlewareClasses/RabbitMQManager.cs b/csharp/App_backup/SaliMax/src/MiddlewareClasses/RabbitMQManager.cs
new file mode 100644
index 000000000..35931424e
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/MiddlewareClasses/RabbitMQManager.cs
@@ -0,0 +1,61 @@
+using System.Text;
+using System.Text.Json;
+using InnovEnergy.App.SaliMax.DataTypes;
+using RabbitMQ.Client;
+
+namespace InnovEnergy.App.SaliMax.MiddlewareClasses;
+
+public static class RabbitMqManager
+{
+    public static ConnectionFactory?  Factory ;
+    public static IConnection ?       Connection;
+    public static IModel?             Channel;
+    
+    public static Boolean SubscribeToQueue(StatusMessage currentSalimaxState, String? s3Bucket,String VpnServerIp)
+    {
+        try
+        {
+            //_factory    = new ConnectionFactory { HostName = VpnServerIp };
+            
+            Factory = new ConnectionFactory
+            {
+                HostName = VpnServerIp,
+                Port = 5672,
+                VirtualHost = "/",
+                UserName = "producer",
+                Password = "b187ceaddb54d5485063ddc1d41af66f",
+               
+            };
+            
+            Connection = Factory.CreateConnection();
+            Channel    = Connection.CreateModel();
+            Channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
+            
+            Console.WriteLine("The controller sends its status to the middleware for the first time");
+            if (s3Bucket != null) InformMiddleware(currentSalimaxState);
+    
+            
+        }
+        catch (Exception ex)
+        {
+            Console.WriteLine("An error occurred while connecting to the RabbitMQ queue: " + ex.Message);
+            return false;
+        }
+        return true;
+    }
+    
+    public static void  InformMiddleware(StatusMessage status)
+    {        
+        var message = JsonSerializer.Serialize(status);
+        var body    = Encoding.UTF8.GetBytes(message);
+
+        Channel.BasicPublish(exchange: string.Empty,
+            routingKey: "statusQueue",
+            basicProperties: null,
+            body: body);
+        
+        Console.WriteLine($"Producer sent message: {message}");
+    }
+
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Program.cs b/csharp/App_backup/SaliMax/src/Program.cs
new file mode 100644
index 000000000..614ca5c25
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Program.cs
@@ -0,0 +1,1020 @@
+#define Amax
+#undef GridLimit
+
+using System.IO.Compression;
+using System.Reactive.Linq;
+using System.Reactive.Threading.Tasks;
+using System.Text;
+using Flurl.Http;
+using InnovEnergy.App.SaliMax.Devices;
+using InnovEnergy.App.SaliMax.Ess;
+using InnovEnergy.App.SaliMax.MiddlewareClasses;
+using InnovEnergy.App.SaliMax.SaliMaxRelays;
+using InnovEnergy.App.SaliMax.System;
+using InnovEnergy.App.SaliMax.SystemConfig;
+using InnovEnergy.Lib.Devices.AMPT;
+using InnovEnergy.Lib.Devices.Battery48TL;
+using InnovEnergy.Lib.Devices.EmuMeter;
+using InnovEnergy.Lib.Devices.Trumpf.SystemControl;
+using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control;
+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;
+using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.SystemConfig;
+using DeviceState = InnovEnergy.App.SaliMax.Devices.DeviceState;
+
+#pragma warning disable IL2026
+
+namespace InnovEnergy.App.SaliMax;
+
+internal static class Program
+{
+    private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(2);
+
+    private static readonly IReadOnlyList<Byte> BatteryNodes;
+
+    private static readonly Channel TruConvertAcChannel;
+    private static readonly Channel TruConvertDcChannel;
+    private static readonly Channel GridMeterChannel;
+    private static readonly Channel IslandBusLoadChannel;
+    private static readonly Channel PvOnDc;
+    private static readonly Channel PvOnAcGrid;
+    private static readonly Channel PvOnAcIsland;
+    private static readonly Channel RelaysChannel;
+    private static readonly Channel RelaysTsChannel;
+    private static readonly Channel BatteriesChannel;
+
+    private static Boolean              _curtailFlag                    = false;
+    private const  String               VpnServerIp                     = "10.2.0.11";
+    private static Boolean             _subscribedToQueue               = false;
+    private static Boolean             _subscribeToQueueForTheFirstTime = false;
+    private static SalimaxAlarmState   _prevSalimaxState                = SalimaxAlarmState.Green;
+    private const  UInt16              NbrOfFileToConcatenate           = 30;
+    private static UInt16              _counterOfFile                   = 0;
+    private static SalimaxAlarmState   _salimaxAlarmState               = SalimaxAlarmState.Green;
+    
+    static Program()
+    {
+        var config = Config.Load();
+        var d = config.Devices;
+
+        Channel CreateChannel(SalimaxDevice device) => device.DeviceState == DeviceState.Disabled 
+                                                     ? new NullChannel()
+                                                     : new TcpChannel(device);
+
+
+        TruConvertAcChannel  = CreateChannel(d.TruConvertAcIp);
+        TruConvertDcChannel  = CreateChannel(d.TruConvertDcIp);
+        GridMeterChannel     = CreateChannel(d.GridMeterIp);
+        IslandBusLoadChannel = CreateChannel(d.IslandBusLoadMeterIp);
+        PvOnDc               = CreateChannel(d.PvOnDc);
+        PvOnAcGrid           = CreateChannel(d.PvOnAcGrid);
+        PvOnAcIsland         = CreateChannel(d.PvOnAcIsland);
+        RelaysChannel        = CreateChannel(d.RelaysIp);
+        RelaysTsChannel      = CreateChannel(d.TsRelaysIp);
+        BatteriesChannel     = CreateChannel(d.BatteryIp);
+        
+        BatteryNodes         = config
+                                .Devices
+                                .BatteryNodes
+                                .Select(n => n.ConvertTo<Byte>())
+                                .ToArray(config.Devices.BatteryNodes.Length);
+    }
+
+    public static async Task Main(String[] args)
+    {
+        //Do not await
+        HourlyDataAggregationManager()
+                        .ContinueWith(t=>t.Exception.WriteLine(), TaskContinuationOptions.OnlyOnFaulted)
+                        .SupressAwaitWarning();
+        
+        DailyDataAggregationManager()
+                        .ContinueWith(t=>t.Exception.WriteLine(), TaskContinuationOptions.OnlyOnFaulted)
+                        .SupressAwaitWarning();
+        
+        InitializeCommunicationToMiddleware();
+        
+        while (true)
+        {
+            try
+            {
+                await Run();
+            }
+            catch (Exception e)
+            {
+                e.LogError();
+            }    
+        }
+    }
+
+
+    private static async Task Run()
+    {
+        "Starting SodiStore Max".WriteLine();
+
+        Watchdog.NotifyReady();
+
+        var battery48TlDevices = BatteryNodes
+                                .Select(n => new Battery48TlDevice(BatteriesChannel, n))
+                                .ToList();
+        
+        var batteryDevices        = new Battery48TlDevices(battery48TlDevices);
+        var acDcDevices           = new TruConvertAcDcDevices(TruConvertAcChannel);
+        var dcDcDevices           = new TruConvertDcDcDevices(TruConvertDcChannel);
+        var gridMeterDevice       = new EmuMeterDevice(GridMeterChannel);
+        var acIslandLoadMeter     = new EmuMeterDevice(IslandBusLoadChannel); 
+        var pvOnDcDevice          = new AmptDevices(PvOnDc);
+        var pvOnAcGridDevice      = new AmptDevices(PvOnAcGrid);
+        var pvOnAcIslandDevice    = new AmptDevices(PvOnAcIsland);
+        var saliMaxTsRelaysDevice = new RelaysDeviceAdam6060(RelaysTsChannel);
+
+        
+#if Amax
+        var saliMaxRelaysDevice   = new RelaysDeviceAmax(RelaysChannel);
+#else
+        var saliMaxRelaysDevice   = new RelaysDeviceAdam6360(RelaysChannel);
+#endif        
+        
+
+        StatusRecord ReadStatus()
+        {
+            var config             = Config.Load();
+            var devices            = config.Devices;
+            var acDc               = acDcDevices.Read();
+            var dcDc               = dcDcDevices.Read();
+            var relays             = saliMaxRelaysDevice.Read();
+            var tsRelays           = saliMaxTsRelaysDevice.Read();
+            var loadOnAcIsland     = acIslandLoadMeter.Read();
+            var gridMeter          = gridMeterDevice.Read();
+            var pvOnDc             = pvOnDcDevice.Read();
+            var battery            = batteryDevices.Read();
+
+            var pvOnAcGrid         = pvOnAcGridDevice.Read();   
+            var pvOnAcIsland       = pvOnAcIslandDevice.Read(); 
+
+            var gridBusToIslandBus = Topology.CalculateGridBusToIslandBusPower(pvOnAcIsland, loadOnAcIsland, acDc);
+
+            var gridBusLoad = devices.LoadOnAcGrid.DeviceState == DeviceState.Disabled
+                                                                 ? new AcPowerDevice { Power = 0 }
+                                                                 : Topology.CalculateGridBusLoad(gridMeter, pvOnAcGrid, gridBusToIslandBus);
+
+            var dcLoad = devices.LoadOnDc.DeviceState == DeviceState.Disabled
+                                                        ? new DcPowerDevice { Power = 0 }
+                                                        : Topology.CalculateDcLoad(acDc, pvOnDc, dcDc);
+
+            var acDcToDcLink = devices.LoadOnDc.DeviceState == DeviceState.Disabled ?
+                                                              Topology.CalculateAcDcToDcLink(pvOnDc, dcDc, acDc) 
+                                                              : new DcPowerDevice{ Power = acDc.Dc.Power};
+
+#if Amax
+            var combinedRelays = relays;
+#else
+            var combinedRelays = new CombinedAdamRelaysRecord(tsRelays, relays);
+#endif         
+
+            return new StatusRecord
+            {
+                AcDc             = acDc,
+                DcDc             = dcDc,
+                Battery          = battery,
+                Relays           = combinedRelays,
+                GridMeter        = gridMeter,
+                PvOnAcGrid       = pvOnAcGrid,
+                PvOnAcIsland     = pvOnAcIsland,
+                PvOnDc           = pvOnDc,
+                AcGridToAcIsland = gridBusToIslandBus,
+                AcDcToDcLink     = acDcToDcLink,
+                LoadOnAcGrid     = gridBusLoad,
+                LoadOnAcIsland   = loadOnAcIsland,
+                LoadOnDc         = dcLoad,
+                StateMachine     = StateMachine.Default,
+                EssControl       = EssControl.Default,
+                Log              = new SystemLog { SalimaxAlarmState = SalimaxAlarmState.Green, Message = null, SalimaxAlarms = null, SalimaxWarnings = null}, //TODO: Put real stuff
+                Config           = config // load from disk every iteration, so config can be changed while running
+            };
+        }
+
+        void WriteControl(StatusRecord r)
+        {
+            if (r.Relays is not null)
+            {
+#if Amax
+                saliMaxRelaysDevice.Write((RelaysRecordAmax)r.Relays);
+#else
+
+                if (r.Relays is CombinedAdamRelaysRecord adamRelays)
+                {
+                    saliMaxRelaysDevice.Write(adamRelays.GetAdam6360DRecord() ?? throw new InvalidOperationException());
+                    saliMaxTsRelaysDevice.Write(adamRelays.GetAdam6060Record() ?? throw new InvalidOperationException());
+                }
+#endif
+            }
+            
+            acDcDevices.Write(r.AcDc);
+            dcDcDevices.Write(r.DcDc);
+        }
+
+        Console.WriteLine("press ctrl-c to stop");
+
+        while (true)
+        {
+            await Observable
+                 .Interval(UpdateInterval)
+                 .Select(_ => RunIteration())
+                 .SelectMany(r => UploadCsv(r, DateTime.Now.Round(UpdateInterval)))
+                 .SelectError()
+                 .ToTask();
+        }
+
+
+        StatusRecord RunIteration()
+        {
+            Watchdog.NotifyAlive();
+
+            var record = ReadStatus();
+            /*
+            if (record.Relays != null)
+            {
+                record.Relays.Do0StartPulse = true;
+
+                record.Relays.PulseOut0HighTime  = 20000;
+                record.Relays.PulseOut0LowTime   = 20000;
+                record.Relays.DigitalOutput0Mode = 2;
+
+                record.Relays.LedGreen = false;
+                
+                record.Relays.Do0StartPulse.WriteLine(" = start pulse 0");
+                
+                record.Relays.PulseOut0HighTime.WriteLine(" = PulseOut0HighTime");
+                
+                record.Relays.PulseOut0LowTime.WriteLine(" = PulseOut0LowTime");
+                
+                record.Relays.DigitalOutput0Mode.WriteLine(" = DigitalOutput0Mode");
+
+                record.Relays.LedGreen.WriteLine(" = LedGreen");
+                
+                record.Relays.LedRed.WriteLine(" = LedRed");
+                
+            }
+            else
+            {
+                " Relays are null".WriteLine();
+            }*/
+
+            SendSalimaxStateAlarm(GetSalimaxStateAlarm(record), record);  // to improve
+              
+            record.ControlConstants();
+            record.ControlSystemState();
+
+            record.ControlPvPower(record.Config.CurtailP, record.Config.PvInstalledPower);
+
+            var essControl = record.ControlEss().WriteLine();
+
+            record.EssControl = essControl;
+
+            record.AcDc.SystemControl.ApplyAcDcDefaultSettings();
+            record.DcDc.SystemControl.ApplyDcDcDefaultSettings();
+
+            DistributePower(record, essControl);
+            
+            record.PerformLed();
+                
+            WriteControl(record);
+
+            $"{DateTime.Now.Round(UpdateInterval).ToUnixTime()} : {record.StateMachine.State}: {record.StateMachine.Message}".WriteLine();
+
+            record.CreateTopologyTextBlock().WriteLine();
+
+            (record.Relays is null ? "No relay Data available" : record.Relays.FiWarning ? "Alert: Fi Warning Detected" : "No Fi Warning Detected").WriteLine();
+            (record.Relays is null ? "No relay Data available" : record.Relays.FiError   ? "Alert: Fi Error Detected"   : "No Fi Error Detected")  .WriteLine();
+
+            record.Config.Save();
+
+            "===========================================".WriteLine();
+
+            return record;
+        }
+
+        // ReSharper disable once FunctionNeverReturns
+    }
+
+    private static void SendSalimaxStateAlarm(StatusMessage currentSalimaxState, StatusRecord record)
+    {
+        var s3Bucket = Config.Load().S3?.Bucket;
+        var subscribedNow = false;
+
+        //Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue
+        //_heartBitInterval++;
+
+        //When the controller boots, it tries to subscribe to the queue
+        if (_subscribeToQueueForTheFirstTime == false)
+        {
+            subscribedNow = true;
+            _subscribeToQueueForTheFirstTime = true;
+            _prevSalimaxState = currentSalimaxState.Status;
+            _subscribedToQueue = RabbitMqManager.SubscribeToQueue(currentSalimaxState, s3Bucket, VpnServerIp);
+        }
+
+        //If already subscribed to the queue and the status has been changed, update the queue
+        if (!subscribedNow && _subscribedToQueue && currentSalimaxState.Status != _prevSalimaxState)
+        {
+            _prevSalimaxState = currentSalimaxState.Status;
+            if (s3Bucket != null)
+                RabbitMqManager.InformMiddleware(currentSalimaxState);
+        }
+        // else if (_subscribedToQueue && _heartBitInterval >= 30)
+        // {
+        //     //Send a heartbit to the backend
+        //     Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
+        //     _heartBitInterval = 0;
+        //     currentSalimaxState.Type = MessageType.Heartbit;
+        //
+        //     if (s3Bucket != null)
+        //         RabbitMqManager.InformMiddleware(currentSalimaxState);
+        // }
+
+        //If there is an available message from the RabbitMQ Broker, apply the configuration file
+        Configuration? config = SetConfigurationFile();
+        if (config != null)
+        {
+            record.ApplyConfigFile(config);
+        }
+    }
+
+    // This preparing a message to send to salimax monitor
+    private static StatusMessage GetSalimaxStateAlarm(StatusRecord record)
+    {
+        var alarmCondition = record.DetectAlarmStates(); // this need to be emailed to support or customer
+        var s3Bucket       = Config.Load().S3?.Bucket;
+
+       var alarmList   = new List<AlarmOrWarning>();
+       var warningList = new List<AlarmOrWarning>();
+       var bAlarmList   = new List<String>();
+       var bWarningList = new List<String>();
+
+       
+        if (record.Battery != null)
+        {
+            var i = 0;
+           
+            foreach (var battery in record.Battery.Devices)
+            {
+                var devicesBatteryNode = record.Config.Devices.BatteryNodes[i];
+                
+                if (battery.LimpBitMap == 0)
+                {
+                   // "All String are Active".WriteLine();
+                }
+                else if (IsPowerOfTwo(battery.LimpBitMap))
+                {
+                    "1 String is disabled".WriteLine();
+                    Console.WriteLine(" ****************** ");
+
+                    warningList.Add(new AlarmOrWarning
+                    {
+                        Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                        Time = DateTime.Now.ToString("HH:mm:ss"),
+                        CreatedBy = "Battery node" + devicesBatteryNode,
+                        Description = "1 String is disabled"
+                    });
+                    
+                    bWarningList.Add("/"+i+1 + "/1 String is disabled"); // battery id instead ( i +1 ) of node id: requested from the frontend 
+                }
+                else
+                {
+                    "2 or more string are disabled".WriteLine();
+                    Console.WriteLine(" ****************** ");
+
+                    alarmList.Add(new AlarmOrWarning
+                    {
+                        Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                        Time = DateTime.Now.ToString("HH:mm:ss"),
+                        CreatedBy = "Battery node" + devicesBatteryNode,
+                        Description = "2 or more string are disabled"
+                    });
+                    bAlarmList.Add(i +";2 or more string are disabled");
+                }
+
+                foreach (var warning in record.Battery.Warnings)
+                {
+                    warningList.Add(new AlarmOrWarning
+                    {
+                        Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                        Time = DateTime.Now.ToString("HH:mm:ss"),
+                        CreatedBy = "Battery node" + devicesBatteryNode,
+                        Description = warning
+                    });
+                    bWarningList.Add(i +";" + warning);
+                }
+                
+                foreach (var alarm in battery.Alarms)
+                {
+                    alarmList.Add(new AlarmOrWarning
+                    {
+                        Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                        Time = DateTime.Now.ToString("HH:mm:ss"),
+                        CreatedBy = "Battery node" + devicesBatteryNode,
+                        Description = alarm
+                    });
+                    bWarningList.Add(i +";" + alarm);
+                }
+                i++;
+            }
+        }
+        
+        if (alarmCondition is not null)
+        {
+            alarmCondition.WriteLine();
+
+            alarmList.Add(new AlarmOrWarning
+            {
+                Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                Time = DateTime.Now.ToString("HH:mm:ss"),
+                CreatedBy = "Salimax",
+                Description = alarmCondition
+            });
+        }
+          
+        foreach (var alarm in record.AcDc.Alarms)
+        {
+            alarmList.Add(new AlarmOrWarning
+            {
+                Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                Time = DateTime.Now.ToString("HH:mm:ss"),
+                CreatedBy = "AcDc",
+                Description = alarm.ToString()
+            });
+        }
+       
+        foreach (var alarm in record.DcDc.Alarms)
+        {
+            alarmList.Add(new AlarmOrWarning
+            {
+                Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                Time = DateTime.Now.ToString("HH:mm:ss"),
+                CreatedBy = "DcDc",
+                Description = alarm.ToString()
+            });
+        }
+       
+        foreach (var warning in record.AcDc.Warnings)
+        {
+            warningList.Add(new AlarmOrWarning
+            {
+                Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                Time = DateTime.Now.ToString("HH:mm:ss"),
+                CreatedBy = "AcDc",
+                Description = warning.ToString()
+            });
+        }
+       
+        foreach (var warning in record.DcDc.Warnings)
+        {
+            warningList.Add(new AlarmOrWarning
+            {
+                Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                Time = DateTime.Now.ToString("HH:mm:ss"),
+                CreatedBy = "DcDc",
+                Description = warning.ToString()
+            });
+        }
+
+        _salimaxAlarmState = warningList.Any()
+            ? SalimaxAlarmState.Orange
+            : SalimaxAlarmState.Green; // this will be replaced by LedState
+
+        _salimaxAlarmState = alarmList.Any()
+            ? SalimaxAlarmState.Red
+            : _salimaxAlarmState; // this will be replaced by LedState
+
+        TryParse(s3Bucket?.Split("-")[0], out var installationId);
+
+        var returnedStatus = new StatusMessage
+        {
+            InstallationId = installationId,
+            Product        = 0,
+            Status         = _salimaxAlarmState,
+            Type           = MessageType.AlarmOrWarning,
+            Alarms         = alarmList,
+            Warnings       = warningList
+        };
+
+        return returnedStatus;     
+    }
+
+    private static String? DetectAlarmStates(this StatusRecord r) => r.Relays switch
+    {
+        { K2ConnectIslandBusToGridBus: false, K2IslandBusIsConnectedToGridBus: true } => " Contradiction: R0 is opening the K2 but the K2 is still close ",
+        { K1GridBusIsConnectedToGrid : false, K2IslandBusIsConnectedToGridBus: true } => " Contradiction: K1 is open but the K2 is still close ",
+        { FiError: true, K2IslandBusIsConnectedToGridBus: true                      } => " Contradiction: Fi error occured but the K2 is still close ",
+        _                                                                             => null
+    };
+
+    private static void ControlConstants(this StatusRecord r)
+    {
+        var inverters     = r.AcDc.Devices;
+        var dcDevices     = r.DcDc.Devices;
+        var configFile    = r.Config;
+        var maxBatteryDischargingCurrentLive = 0.0;
+        var devicesConfig = r.AcDc.Devices.All(d => d.Control.Ac.GridType == GridType.GridTied400V50Hz) ? configFile.GridTie : configFile.IslandMode; // TODO if any of the grid tie mode
+        
+        // This adapting the max discharging current to the current Active Strings
+        if (r.Battery != null)
+        {
+            const Int32 stringsByBattery         = 5;
+            var numberOfBatteriesConfigured      = r.Config.Devices.BatteryNodes.Length;
+            var numberOfTotalStrings             = stringsByBattery * numberOfBatteriesConfigured;
+            var dischargingCurrentByString       = devicesConfig.DcDc.MaxBatteryDischargingCurrent / numberOfTotalStrings;
+            
+            var boolList = new List<Boolean>();
+
+            foreach (var stringActive  in r.Battery.Devices.Select(b => b.BatteryStrings).ToList())
+            {
+                boolList.Add(stringActive.String1Active);
+                boolList.Add(stringActive.String2Active);
+                boolList.Add(stringActive.String3Active);
+                boolList.Add(stringActive.String4Active);
+                boolList.Add(stringActive.String5Active);
+            }
+            
+            var numberOfBatteriesStringActive = boolList.Count(b => b);
+            
+            if (numberOfTotalStrings != 0)
+            {
+                maxBatteryDischargingCurrentLive = dischargingCurrentByString * numberOfBatteriesStringActive;
+            }
+        }
+        
+        // TODO The discharging current is well calculated but not communicated to live. But Written in S3
+
+        
+        inverters.ForEach(d => d.Control.Dc.MaxVoltage                               = devicesConfig.AcDc.MaxDcLinkVoltage);
+        inverters.ForEach(d => d.Control.Dc.MinVoltage                               = devicesConfig.AcDc.MinDcLinkVoltage);
+        inverters.ForEach(d => d.Control.Dc.ReferenceVoltage                         = devicesConfig.AcDc.ReferenceDcLinkVoltage);
+        
+        inverters.ForEach(d => d.Control.Dc.PrechargeConfig                          = DcPrechargeConfig.PrechargeDcWithInternal);
+       
+        dcDevices.ForEach(d => d.Control.DroopControl.UpperVoltage                   = devicesConfig.DcDc.UpperDcLinkVoltage);
+        dcDevices.ForEach(d => d.Control.DroopControl.LowerVoltage                   = devicesConfig.DcDc.LowerDcLinkVoltage);
+        dcDevices.ForEach(d => d.Control.DroopControl.ReferenceVoltage               = devicesConfig.DcDc.ReferenceDcLinkVoltage);
+        
+        dcDevices.ForEach(d => d.Control.CurrentControl.MaxBatteryChargingCurrent    = devicesConfig.DcDc.MaxBatteryChargingCurrent);
+        dcDevices.ForEach(d => d.Control.CurrentControl.MaxBatteryDischargingCurrent = maxBatteryDischargingCurrentLive);
+        dcDevices.ForEach(d => d.Control.MaxDcPower                                  = devicesConfig.DcDc.MaxDcPower);
+
+        dcDevices.ForEach(d => d.Control.VoltageLimits.MaxBatteryVoltage             = devicesConfig.DcDc.MaxChargeBatteryVoltage);
+        dcDevices.ForEach(d => d.Control.VoltageLimits.MinBatteryVoltage             = devicesConfig.DcDc.MinDischargeBatteryVoltage);
+        dcDevices.ForEach(d => d.Control.ControlMode                                 = DcControlMode.VoltageDroop);
+
+        r.DcDc.ResetAlarms();
+        r.AcDc.ResetAlarms();
+    }
+
+    // This will be used for provider throttling, this example is only for either 100% or 0 %
+    private static void ControlPvPower(this StatusRecord r, UInt16 exportLimit = 0, UInt16 pvInstalledPower = 20)
+    {   
+        // Maybe add a condition to do this only if we are in optimised Self consumption, this is not true
+        
+        if (r.GridMeter?.Ac.Power.Active == null)
+        {
+            Console.WriteLine(" No reading from Grid meter");
+            return;
+        }
+
+        if (pvInstalledPower == 0)
+        {
+            Console.WriteLine(" No curtailing, because Pv installed is equal to 0");
+            return;
+        }
+        
+        const Int32 constantDeadBand = 5000; // magic number
+        const Double voltageRange    = 100;  // 100 Voltage configured rang for PV slope, if the configured slope change this must change also 
+        var configFile               = r.Config;
+        var inverters                = r.AcDc.Devices;
+        var systemExportLimit        = - exportLimit * 1000 ; // Conversion from Kw in W // the config file value is positive and limit should be negative from 0 to ...
+        var stepSize                 = ClampStepSize((UInt16)Math.Floor(voltageRange/ pvInstalledPower));  // in Voltage per 1 Kw
+        var deadBand                 = constantDeadBand/stepSize;
+
+        // LINQ query to select distinct ActiveUpperVoltage
+        var result = r.AcDc.Devices
+                    .Select(device => device?.Status?.DcVoltages?.Active?.ActiveUpperVoltage)
+                    .Select(voltage => voltage.Value)    // Extract the value since we've confirmed it's non-null
+                    .Distinct()
+                    .ToList();
+
+        Double upperVoltage;
+        
+        if (result.Count == 1)
+        {
+            upperVoltage = result[0];
+        }
+        else
+        {
+            Console.WriteLine(" Different ActiveUpperVoltage between inverters "); // this should be reported to salimax Alarm
+            return;
+        }
+        
+        /************* For debugging purposes ********************/ 
+        
+        systemExportLimit.WriteLine(" Export Limit in W");
+        upperVoltage.WriteLine(" Upper Voltage");
+        r.GridMeter.Ac.Power.Active.WriteLine(" Active Export");
+        Console.WriteLine(" ****************** ");
+        
+        /*********************************************************/
+        
+        if (r.GridMeter.Ac.Power.Active < systemExportLimit)
+        {
+            _curtailFlag = true;
+            upperVoltage = IncreaseInverterUpperLimit(upperVoltage, stepSize);
+            upperVoltage.WriteLine("Upper Voltage Increased: New Upper limit");
+        }
+        else
+        {
+            if (_curtailFlag)
+            {
+                if (r.GridMeter.Ac.Power.Active > (systemExportLimit + deadBand))
+                {
+                    upperVoltage = DecreaseInverterUpperLimit(upperVoltage, stepSize);
+
+                    if (upperVoltage <= configFile.GridTie.AcDc.MaxDcLinkVoltage)
+                    {
+                        _curtailFlag     = false;
+                        upperVoltage     = configFile.GridTie.AcDc.MaxDcLinkVoltage;
+                        upperVoltage.WriteLine(" New Upper limit");
+                        Console.WriteLine("Upper Voltage decreased: Smaller than the default value, value clamped");
+                    }
+                    else
+                    {
+                        Console.WriteLine("Upper Voltage decreased: New Upper limit");
+                        upperVoltage.WriteLine(" New Upper limit");                    
+                    }
+                }
+                else
+                { 
+                    deadBand.WriteLine("W  :We are in Dead band area");
+                    upperVoltage.WriteLine(" same Upper limit from last cycle");
+                }
+            }
+            else
+            {
+                Console.WriteLine("Curtail Flag is false , no need to curtail");
+                upperVoltage.WriteLine(" same Upper limit from last cycle");
+            }
+        }
+        inverters.ForEach(d => d.Control.Dc.MaxVoltage = upperVoltage);
+        Console.WriteLine(" ****************** ");
+    }
+
+    // why this is not in Controller?
+    private static void DistributePower(StatusRecord record, EssControl essControl)
+    {
+        var nInverters = record.AcDc.Devices.Count;
+        
+        var powerPerInverterPhase = nInverters > 0
+                                  ? essControl.PowerSetpoint / nInverters / 3
+                                  : 0;
+                
+        record.AcDc.Devices.ForEach(d =>
+        {
+            d.Control.Ac.PhaseControl = PhaseControl.Asymmetric;
+            d.Control.Ac.Power.L1 = powerPerInverterPhase;
+            d.Control.Ac.Power.L2 = powerPerInverterPhase;
+            d.Control.Ac.Power.L3 = powerPerInverterPhase;
+        });
+    }
+
+    // To test, most probably the curtailing flag will not work
+    private static void PerformLed(this StatusRecord record)
+    {
+        if (record.StateMachine.State == 23)
+        {
+            switch (record.EssControl.Mode)
+            {
+                case EssMode.CalibrationCharge:
+                    record.Relays?.PerformSlowFlashingGreenLed();
+                    break;
+                case EssMode.OptimizeSelfConsumption when !_curtailFlag:
+                    record.Relays?.PerformSolidGreenLed();
+                    break;
+                case EssMode.Off:
+                    break;
+                case EssMode.OffGrid:
+                    break;
+                case EssMode.HeatBatteries:
+                    break;
+                case EssMode.ReachMinSoc:
+                    break;
+                case EssMode.NoGridMeter:
+                    break;
+                default:
+                {
+                    if (_curtailFlag)
+                    {
+                        record.Relays?.PerformFastFlashingGreenLed();
+                    }
+
+                    break;
+                }
+            }
+        }
+        else if (record.Battery?.Soc != null && record.StateMachine.State != 23 && record.Battery.Soc > 50)
+        {
+            record.Relays?.PerformSolidOrangeLed();
+        }
+        else if (record.Battery?.Soc != null && record.StateMachine.State != 23 && record.Battery.Soc < 50 && record.Battery.Soc > 20)
+        {
+            record.Relays?.PerformSlowFlashingOrangeLed();
+        }
+        else if (record.Battery?.Soc != null && record.StateMachine.State != 23 && record.Battery.Soc < 20)
+        {
+            record.Relays?.PerformFastFlashingOrangeLed();
+        }
+
+        var criticalAlarm = record.DetectAlarmStates();
+        
+        if (criticalAlarm is not null)
+        {
+            record.Relays?.PerformFastFlashingRedLed();
+        }
+    }
+    
+    private static Double IncreaseInverterUpperLimit(Double upperLimit, Double stepSize)
+    {
+        return upperLimit + stepSize;
+    }
+
+    private static Double DecreaseInverterUpperLimit(Double upperLimit, Double stepSize)
+    {
+        return upperLimit - stepSize;
+    }
+    
+    private static UInt16 ClampStepSize(UInt16 stepSize)
+    {
+        return stepSize switch
+        {
+            > 5 => 5,
+            <= 1 => 1,
+            _ => stepSize
+        };
+    }
+    
+    private static void ApplyAcDcDefaultSettings(this SystemControlRegisters? sc)
+    {
+        if (sc is null)
+            return;
+        
+        sc.ReferenceFrame          = ReferenceFrame.Consumer;
+        sc.SystemConfig            = AcDcAndDcDc;
+
+        #if DEBUG
+        sc.CommunicationTimeout    = TimeSpan.FromMinutes(2);    
+        #else
+        sc.CommunicationTimeout    = TimeSpan.FromSeconds(20);
+        #endif
+        
+        sc.PowerSetPointActivation = PowerSetPointActivation.Immediate;
+        sc.UseSlaveIdForAddressing = true;
+        sc.SlaveErrorHandling      = SlaveErrorHandling.Relaxed;
+        sc.SubSlaveErrorHandling   = SubSlaveErrorHandling.Off;
+        
+        sc.ResetAlarmsAndWarnings  = true;
+    }
+    
+    private static void ApplyDcDcDefaultSettings(this SystemControlRegisters? sc)
+    {
+
+        if (sc is null)
+            return;
+        
+        sc.SystemConfig            = DcDcOnly;
+        #if DEBUG
+        sc.CommunicationTimeout    = TimeSpan.FromMinutes(2);    
+        #else
+        sc.CommunicationTimeout    = TimeSpan.FromSeconds(20);
+        #endif
+        
+        sc.PowerSetPointActivation = PowerSetPointActivation.Immediate;
+        sc.UseSlaveIdForAddressing = true;
+        sc.SlaveErrorHandling      = SlaveErrorHandling.Relaxed;
+        sc.SubSlaveErrorHandling   = SubSlaveErrorHandling.Off;
+                
+        sc.ResetAlarmsAndWarnings  = true; 
+    }
+    
+    private static void InsertIntoJson(Dictionary<string, object> jsonDict, String[] keys, string value)
+    {
+        
+        Dictionary<string, object> 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<string, object>();
+            }
+            
+            
+
+            if (i == keys.Length - 1) // Last key, store the value
+            {
+                
+                if (!value.Contains(",") && double.TryParse(value, out double doubleValue)) // Try to parse value as a number
+                {
+                    currentDict[key] = Math.Round(doubleValue, 2); // Round to 2 decimal places
+                   
+                }
+                else
+                {
+                    currentDict[key] = value; // Store as string if not a number
+                }
+               
+                
+            }
+            else
+            {
+                currentDict = (Dictionary<string, object>)currentDict[key];
+            }
+        }
+        
+    }
+
+    private static async Task<Boolean> UploadCsv(StatusRecord status, DateTime timeStamp)
+    {
+
+        //status.ToJson();
+        var csv  = status.ToCsv();
+        
+        
+        Dictionary<string, object> jsonData = new Dictionary<string, object>();
+        //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;
+        
+        if (s3Config is null)
+            return false;
+
+        //Concatenating 15 files in one file 
+        return await ConcatinatingAndCompressingFiles(timeStamp, s3Config);
+    }
+
+    private static async Task<Boolean> ConcatinatingAndCompressingFiles(DateTime timeStamp, S3Config s3Config)
+    {
+        if (_counterOfFile >= NbrOfFileToConcatenate)
+        {
+            _counterOfFile = 0;
+            
+            var logFileConcatenator = new LogFileConcatenator();
+                        
+            var s3Path              = timeStamp.ToUnixTime() + ".json";
+            s3Path.WriteLine("");
+            var csvToSend           = logFileConcatenator.ConcatenateFiles(NbrOfFileToConcatenate);
+
+            var request             = s3Config.CreatePutRequest(s3Path);
+      
+            //Use this for no compression
+            //var response = await request.PutAsync(new StringContent(csv));
+            var compressedBytes = CompresseBytes(csvToSend);
+
+            // Encode the compressed byte array as a Base64 string
+            string base64String = Convert.ToBase64String(compressedBytes);
+        
+            // Create StringContent from Base64 string
+            var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64");
+        
+            // Upload the compressed data (ZIP archive) to S3
+            var response = await request.PutAsync(stringContent);
+        
+            if (response.StatusCode != 200)
+            {
+                Console.WriteLine("ERROR: PUT");
+                var error = await response.GetStringAsync();
+                Console.WriteLine(error);
+                Heartbit(new DateTime(0));
+                return false;
+            }   
+            
+            Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
+            
+            Heartbit(timeStamp);
+        }
+        _counterOfFile++;
+
+        return true;
+    }
+
+    private static void Heartbit(DateTime timeStamp)
+    {
+        var s3Bucket = Config.Load().S3?.Bucket;
+        var tryParse = TryParse(s3Bucket?.Split("-")[0], out var installationId);
+        var parse    = TryParse(timeStamp.ToUnixTime().ToString(), out var nameOfCsvFile);
+
+        if (tryParse)
+        {
+            var returnedStatus = new StatusMessage
+            {
+                InstallationId = installationId,
+                Product        = 0, // Salimax is always 0
+                Status         = _salimaxAlarmState,
+                Type           = MessageType.Heartbit,
+                Timestamp      = nameOfCsvFile
+            };
+            if (s3Bucket != null)
+                RabbitMqManager.InformMiddleware(returnedStatus);
+        }
+    }
+
+    private static Byte[] CompresseBytes(String csvToSend)
+    {
+        //Compress CSV data to a byte array
+        Byte[] compressedBytes;
+        using (var memoryStream = new MemoryStream())
+        {
+            //Create a zip directory and put the compressed file inside
+            using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
+            {
+                var entry = archive.CreateEntry("data.json", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
+                using (var entryStream = entry.Open())
+                using (var writer = new StreamWriter(entryStream))
+                {
+                    writer.Write(csvToSend);
+                }
+            }
+        
+            compressedBytes = memoryStream.ToArray();
+        }
+
+        return compressedBytes;
+    }
+
+    private static async Task RestApiSavingFile(String csv)
+    {
+        // This is for the Rest API
+        // Check if the directory exists, and create it if it doesn't
+        const String directoryPath = "/var/www/html";
+
+        if (!Directory.Exists(directoryPath))
+        {
+            Directory.CreateDirectory(directoryPath);
+        }
+        
+        string filePath = Path.Combine(directoryPath, "status.csv");
+        
+        await File.WriteAllTextAsync(filePath, csv.SplitLines().Where(l => !l.Contains("Secret")).JoinLines());
+    }
+
+    private static Boolean IsPowerOfTwo(Int32 n)
+    {
+        return n > 0 && (n & (n - 1)) == 0;
+    }
+
+    private static void ApplyConfigFile(this StatusRecord status, Configuration? config)
+    {
+        if (config == null) return;
+        
+        status.Config.MinSoc = config.MinimumSoC;
+        status.Config.GridSetPoint = config.GridSetPoint * 1000; // converted from kW to W
+        status.Config.ForceCalibrationChargeState = config.CalibrationChargeState;
+
+        if (config.CalibrationChargeState == CalibrationChargeType.RepetitivelyEvery)
+        {
+            status.Config.DayAndTimeForRepetitiveCalibration = config.CalibrationChargeDate;
+        }
+        else if (config.CalibrationChargeState == CalibrationChargeType.AdditionallyOnce)
+        {
+            status.Config.DayAndTimeForAdditionalCalibration = config.CalibrationChargeDate;
+        }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/S3Config.cs b/csharp/App_backup/SaliMax/src/S3Config.cs
new file mode 100644
index 000000000..0555e3fbf
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/S3Config.cs
@@ -0,0 +1,79 @@
+using System.Security.Cryptography;
+using Flurl;
+using Flurl.Http;
+using InnovEnergy.Lib.Utils;
+using static System.Text.Encoding;
+using Convert = System.Convert;
+
+namespace InnovEnergy.App.SaliMax;
+
+public record S3Config
+{
+    public required String Bucket      { get; init; } 
+    public required String Region      { get; init; } 
+    public required String Provider    { get; init; } 
+    public required String Key         { get; init; } 
+    public required String Secret      { get; init; } 
+    public required String ContentType { get; init; } 
+
+    public String Host =>  $"{Bucket}.{Region}.{Provider}";
+    public String Url  =>  $"https://{Host}";
+
+    public IFlurlRequest CreatePutRequest(String s3Path) => CreateRequest("PUT", s3Path);
+    public IFlurlRequest CreateGetRequest(String s3Path) => CreateRequest("GET", s3Path);
+
+    private IFlurlRequest CreateRequest(String method, String s3Path)
+    {
+        var date = DateTime.UtcNow.ToString("r");
+        var auth = CreateAuthorization(method, s3Path, date);
+
+        return Url
+              .AppendPathSegment(s3Path)
+              .WithHeader("Host", Host)
+              .WithHeader("Date", date)
+              .WithHeader("Authorization", auth)
+              .AllowAnyHttpStatus();
+    }
+
+    private String CreateAuthorization(String method,
+                                       String s3Path,
+                                       String date)
+    {
+        return CreateAuthorization
+        (
+            method     : method,
+            bucket     : Bucket,
+            s3Path     : s3Path,
+            date       : date,
+            s3Key      : Key,
+            s3Secret   : Secret,
+            contentType: ContentType
+        );
+    }
+
+
+
+    private static String CreateAuthorization(String method,
+                                              String bucket,
+                                              String s3Path,
+                                              String date,
+                                              String s3Key,
+                                              String s3Secret,
+                                              String contentType = "application/base64",
+                                              String md5Hash = "")
+    {
+
+        contentType = "application/base64; charset=utf-8";
+        //contentType = "text/plain; charset=utf-8";  //this to use when sending plain csv to S3
+        
+        var payload = $"{method}\n{md5Hash}\n{contentType}\n{date}\n/{bucket.Trim('/')}/{s3Path.Trim('/')}"; 
+        using var hmacSha1 = new HMACSHA1(UTF8.GetBytes(s3Secret));
+        
+        var signature = UTF8
+                       .GetBytes(payload)
+                       .Apply(hmacSha1.ComputeHash)
+                       .Apply(Convert.ToBase64String);
+
+        return $"AWS {s3Key}:{signature}";
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/SaliMaxRelays/CombinedAdamRelaysRecord.cs b/csharp/App_backup/SaliMax/src/SaliMaxRelays/CombinedAdamRelaysRecord.cs
new file mode 100644
index 000000000..ef669c818
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SaliMaxRelays/CombinedAdamRelaysRecord.cs
@@ -0,0 +1,422 @@
+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<Boolean> 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<Boolean> K3InverterIsConnectedToIslandBus => _recordAdam6360D.K3InverterIsConnectedToIslandBus;
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/SaliMaxRelays/IRelaysRecord.cs b/csharp/App_backup/SaliMax/src/SaliMaxRelays/IRelaysRecord.cs
new file mode 100644
index 000000000..e06628c44
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SaliMaxRelays/IRelaysRecord.cs
@@ -0,0 +1,156 @@
+namespace InnovEnergy.App.SaliMax.SaliMaxRelays;
+
+
+public interface IRelaysRecord
+{
+    Boolean              K1GridBusIsConnectedToGrid        { get; }
+    Boolean              K2IslandBusIsConnectedToGridBus   { get; }
+    IEnumerable<Boolean> 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; }
+    Boolean              K2IslandBusIsConnectedToGridBus   { get; }
+    IEnumerable<Boolean> 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();*/
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysDeviceADAM6360.cs b/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysDeviceADAM6360.cs
new file mode 100644
index 000000000..50b9819d1
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysDeviceADAM6360.cs
@@ -0,0 +1,78 @@
+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
+        {
+            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
+        {
+            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();
+        }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysDeviceAdam6060.cs b/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysDeviceAdam6060.cs
new file mode 100644
index 000000000..1e509c5d4
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysDeviceAdam6060.cs
@@ -0,0 +1,76 @@
+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
+        {
+            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
+        {
+            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();
+        }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysDeviceAmax.cs b/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysDeviceAmax.cs
new file mode 100644
index 000000000..0e3f86e0a
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysDeviceAmax.cs
@@ -0,0 +1,75 @@
+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  
+{
+    private Amax5070Device  AmaxDevice { get; }
+    
+    public RelaysDeviceAmax(String hostname) => AmaxDevice = new Amax5070Device(hostname);
+    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();
+        }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6060.cs b/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6060.cs
new file mode 100644
index 000000000..77816f187
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6060.cs
@@ -0,0 +1,48 @@
+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
+{
+    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);
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6360D.cs b/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6360D.cs
new file mode 100644
index 000000000..6377bc947
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysRecordAdam6360D.cs
@@ -0,0 +1,160 @@
+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<Boolean> 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;
+
+    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<Boolean> 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);
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysRecordAmax.cs b/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysRecordAmax.cs
new file mode 100644
index 000000000..13c71d1df
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SaliMaxRelays/RelaysRecordAmax.cs
@@ -0,0 +1,268 @@
+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<Boolean> 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;
+
+    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<Boolean> K3InverterIsConnectedToIslandBus
+    {
+        get
+        {
+            yield return K3Inverter1IsConnectedToIslandBus;
+            yield return K3Inverter2IsConnectedToIslandBus;
+            yield return K3Inverter3IsConnectedToIslandBus;
+            yield return K3Inverter4IsConnectedToIslandBus;
+        }
+    }
+
+    public Boolean K3Inverter1IsConnectedToIslandBus => !_Regs.DigitalInput16;   
+    public Boolean K3Inverter2IsConnectedToIslandBus => !_Regs.DigitalInput17;  
+    public Boolean K3Inverter3IsConnectedToIslandBus => !_Regs.DigitalInput18;  
+    public 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);
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Switch.cs b/csharp/App_backup/SaliMax/src/Switch.cs
new file mode 100644
index 000000000..5011d75ee
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Switch.cs
@@ -0,0 +1,15 @@
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.SaliMax;
+
+public static class Switch
+{
+    public static TextBlock Open(String name)
+    {
+        return TextBlock.AlignCenterHorizontal
+        (
+            "  __╱ __  ",
+            name
+        );
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/System/Controller.cs b/csharp/App_backup/SaliMax/src/System/Controller.cs
new file mode 100644
index 000000000..e83f23ada
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/System/Controller.cs
@@ -0,0 +1,726 @@
+using InnovEnergy.App.SaliMax.Ess;
+using InnovEnergy.App.SaliMax.SaliMaxRelays;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
+using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.GridType;
+
+namespace InnovEnergy.App.SaliMax.System;
+
+public static class Controller
+{
+    private static Int32 GetSystemState(this StatusRecord r)
+    {
+        var relays = r.Relays;
+
+        if (relays is null)
+            return 101;  //     Message = "Panic: relay device is not available!",
+        
+        var acDcs = r.AcDc;
+
+        if (acDcs.NotAvailable())
+            return 102;
+
+        var k4 = acDcs.AllGridTied() ? 0
+               : acDcs.AllIsland()   ? 1
+               : 4;
+        
+        var k5 = acDcs.AllDisabled() ? 0
+               : acDcs.AllEnabled()  ? 1
+               : 4;
+        
+        if (k4 == 4 || k5 == 4)
+            return 103; //Message = "Panic: ACDCs have unequal grid types or power stage",
+
+        var nInverters = r.AcDc.Devices.Count;
+        
+        var k1 = relays.K1GridBusIsConnectedToGrid                                    ? 1 : 0;
+        var k2 = relays.K2IslandBusIsConnectedToGridBus                               ? 1 : 0;
+        var k3 = relays.K3InverterIsConnectedToIslandBus.Take(nInverters).Any(c => c) ? 1 : 0;
+        
+        
+        // states as defined in states excel sheet 
+        return 1 * k1 
+             + 2 * k2 
+             + 4 * k3 
+             + 8 * k4
+             + 16 * k5;
+    }
+
+    public static Boolean ControlSystemState(this StatusRecord s)
+    {
+        s.StateMachine.State = s.GetSystemState();
+        
+        return s.StateMachine.State switch
+        {
+            0   => State0(s),
+            1   => State1(s),
+            2   => State2(s),
+            3   => State3(s),
+            4   => State4(s),
+            5   => State5(s),
+            6   => State6(s),
+            7   => State7(s),
+            8   => State8(s),
+            9   => State9(s),
+            10  => State10(s),
+            11  => State11(s),
+            12  => State12(s),
+            13  => State13(s),
+            14  => State14(s),
+            15  => State15(s),
+            16  => State16(s),
+            17  => State17(s),
+            18  => State18(s),
+            19  => State19(s),
+            20  => State20(s),
+            21  => State21(s),
+            22  => State22(s),
+            23  => State23(s),
+            24  => State24(s),
+            25  => State25(s),
+            26  => State26(s),
+            27  => State27(s),
+            28  => State28(s),
+            29  => State29(s),
+            30  => State30(s),
+            31  => State31(s),
+       
+            
+            101 => State101(s),
+            102 => State102(s),
+            103 => State103(s),
+            _   => UnknownState(s)
+        };
+        
+    }
+
+    private static Boolean NotAvailable(this AcDcDevicesRecord acDcs)
+    {
+        return acDcs.SystemControl == null || acDcs.Devices.Count == 0;
+    }
+
+    private static Boolean State0(StatusRecord s)
+    {
+        s.StateMachine.Message = "Ac/Dc are off. Switching to Island Mode.";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 8
+    }
+    
+        
+    private static Boolean State1(StatusRecord s)
+    {
+        s.StateMachine.Message = "Grid Tied mode active, closing k2";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.ConnectIslandBusToGrid();
+    
+        return false;
+        
+        // => 3
+    }
+    
+    private static Boolean State2(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 0
+    }
+    
+    private static Boolean State3(StatusRecord s)
+    {
+        s.StateMachine.Message = "K2 closed, Turning on Ac/Dc";
+        
+        s.DcDc.Enable();
+        s.AcDc.Enable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.ConnectIslandBusToGrid();
+    
+        return false;
+        
+        // => 19
+    }
+
+    
+    private static Boolean State4(StatusRecord s)
+    {
+        s.StateMachine.Message = "K2 is open, waiting K3 to open";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return true;
+        
+        // => 0
+    }
+    
+    private static Boolean State5(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => ?
+    }
+        
+    private static Boolean State6(StatusRecord s)
+    {
+        s.StateMachine.Message = "Inverters are off, opening K2";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return true;
+        
+        // => 4
+    }
+
+    private static Boolean State7(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.ConnectIslandBusToGrid();
+        
+        return false;
+
+        // => ?
+    }
+    
+    private static Boolean State8(StatusRecord s)
+    {
+        s.StateMachine.Message = "Ac/Dc are off and in Island Mode.";
+        
+        s.DcDc.Enable();
+        s.AcDc.Enable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+
+        return true;
+        // => 24
+    }   
+    
+    private static Boolean State9(StatusRecord s)
+    {
+        s.StateMachine.Message = "Ac/Dc are disconnected from Island Bus. Switching to GridTie Mode.";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+
+        return false;
+        
+        // => 1
+    } 
+    
+    private static Boolean State10(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 8
+    }
+
+    private static Boolean State11(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.ConnectIslandBusToGrid();
+        
+        return false;
+
+        // => 9
+    }
+    
+    private static Boolean State12(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 8
+    }
+    
+    private static Boolean State13(StatusRecord s)
+    {
+        s.StateMachine.Message = "Ac/Dc are off. Waiting for them to disconnect from Island Bus.";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return true;
+        
+        // => 9
+    }
+
+    private static Boolean State14(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 12
+    }
+
+    
+    private static Boolean State15(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 13
+    }
+    
+    
+    private static Boolean State16(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 0
+    }
+    
+    
+    private static Boolean State17(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 1
+    }
+    
+    
+    private static Boolean State18(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 0
+    }
+    
+    private static Boolean State19(StatusRecord s)
+    {
+        s.StateMachine.Message = "Waiting for Ac/Dc to connect to Island Bus";
+         
+        s.DcDc.Enable();
+        s.AcDc.Enable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.ConnectIslandBusToGrid();
+        
+        return true;
+        
+        // => 23
+    }
+
+    
+    private static Boolean State20(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 4
+    }
+
+    private static Boolean State21(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 5
+    }
+    
+    private static Boolean State22(StatusRecord s)
+    {
+        s.StateMachine.Message = "K1 opened, switching inverters off";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.ConnectIslandBusToGrid();
+        
+        return true;
+        
+        // => 6
+    }
+
+    private static Boolean State23(StatusRecord s)
+    {
+        s.StateMachine.Message = "ESS";
+        
+        s.DcDc.Enable();
+        s.AcDc.Enable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.ConnectIslandBusToGrid();
+        
+        return true;
+        
+        // => 22
+    }
+
+        
+    private static Boolean State24(StatusRecord s)
+    {
+        s.StateMachine.Message = "Inverter are on waiting for k3 to close";
+        
+        s.DcDc.Enable();
+        s.AcDc.Enable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+
+        return false;
+        
+        // => 28
+    }
+    
+    private static Boolean State25(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 9
+    }
+    
+    private static Boolean State26(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid(); 
+        
+        return false;
+
+        // => 10
+    }
+    
+    private static Boolean State27(StatusRecord s)
+    {
+        s.StateMachine.Message = "K2 open and enable island mode";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid(); 
+        
+        return false;
+
+        // => 9
+    }
+    
+    private static Boolean State28(StatusRecord s)
+    {
+        s.StateMachine.Message = "Island Mode";
+        
+        s.DcDc.Enable();
+        s.AcDc.Enable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+
+        return false;
+        
+        // => 29
+    }
+    
+    private static Boolean State29(StatusRecord s)
+    {
+        s.StateMachine.Message = "K1 closed, Switching off Inverters and moving to grid tie";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+
+        return false;
+        
+        // => 13
+    }
+
+    private static Boolean State30(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 14
+    }
+    
+    private static Boolean State31(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 13
+    }
+    
+    private static Boolean State101(StatusRecord s)
+    {
+        s.StateMachine.Message = "Relay device is not available";
+        return s.EnableSafeDefaults();
+    }
+    
+    private static Boolean State102(StatusRecord s)
+    {
+        s.StateMachine.Message = "ACDCs not available";
+        return s.EnableSafeDefaults();
+    }
+
+    private static Boolean State103(StatusRecord s)
+    {
+        s.StateMachine.Message = "Panic: ACDCs have unequal grid types or PowerStage";
+        return s.EnableSafeDefaults();
+    }
+    
+    // private static Boolean State104(StatusRecord s)
+    // {
+    //     s.StateMachine.Message = "Panic: DCDCs not available";
+    //     return s.EnableSafeDefaults();
+    // }
+    
+    
+    private static Boolean UnknownState(StatusRecord s)
+    {
+        // "Unknown System State"
+        return s.EnableSafeDefaults();
+    }
+
+
+
+    private static Boolean AllDisabled(this AcDcDevicesRecord acDcs)
+    {
+        return acDcs.Devices.All(d => !d.Control.PowerStageEnable);
+    }
+    
+    private static Boolean AllEnabled(this AcDcDevicesRecord acDcs)
+    {
+        return acDcs.Devices.All(d => d.Control.PowerStageEnable);
+    }
+
+
+    private static Boolean AllTheSame(this AcDcDevicesRecord acDcs)
+    {
+        return acDcs.Devices
+                    .Select(d => d.Control.PowerStageEnable)
+                    .Distinct()
+                    .Count() == 1;
+    }
+    
+    private static Boolean AllGridTied(this AcDcDevicesRecord acDcs)
+    {
+        return acDcs.Devices.All(d => d.Status.ActiveGridType is GridTied380V60Hz)
+            || acDcs.Devices.All(d => d.Status.ActiveGridType is GridTied400V50Hz)
+            || acDcs.Devices.All(d => d.Status.ActiveGridType is GridTied480V60Hz);
+    }
+
+    private static Boolean AllIsland(this AcDcDevicesRecord acDcs)
+    {
+        return acDcs.Devices.All(d => d.Status.ActiveGridType is Island400V50Hz)
+            || acDcs.Devices.All(d => d.Status.ActiveGridType is Island480V60Hz);
+    }
+    
+    private static void ForAll<T>(this IEnumerable<T> ts, Action<T> action)
+    {
+        foreach (var t in ts)
+            action(t);
+    }
+    
+    private static void Disable(this AcDcDevicesRecord acDc)
+    {
+        acDc.Devices
+            .Select(d => d.Control)
+            .ForAll(c => c.PowerStageEnable = false);
+    }
+
+    //this is must be deleted
+    private static void Disable(this DcDcDevicesRecord dcDc)
+    {
+        // For Test purpose, The transition from island mode to grid tier and vis versa , may not need to disable Dc/Dc. 
+        // This will keep the Dc link powered.
+        
+        // dcDc.Devices
+        //     .Select(d => d.Control)
+        //     .ForAll(c => c.PowerStageEnable = false);
+    }
+    
+    private static void Enable(this AcDcDevicesRecord acDc)
+    {
+        acDc.Devices
+            .Select(d => d.Control)
+            .ForAll(c => c.PowerStageEnable = true);
+    }
+
+    private static void Enable(this DcDcDevicesRecord dcDc)
+    {
+        dcDc.Devices
+            .Select(d => d.Control)
+            .ForAll(c => c.PowerStageEnable = true);
+    }
+
+
+    private static void EnableGridTieMode(this AcDcDevicesRecord acDc)
+    {
+        acDc.Devices
+            .Select(d => d.Control)
+            .ForAll(c => c.Ac.GridType = GridTied400V50Hz);  // TODO: config grid type
+    }
+
+
+    private static void EnableIslandMode(this AcDcDevicesRecord acDc)
+    {
+        acDc.Devices
+            .Select(d => d.Control)
+            .ForAll(c => c.Ac.GridType = Island400V50Hz);  // TODO: config grid type
+    }
+
+    private static void DisconnectIslandBusFromGrid(this IRelaysRecord? relays)
+    {
+        if (relays is not null)
+            relays.K2ConnectIslandBusToGridBus = false;
+    }
+
+    private static void ConnectIslandBusToGrid(this IRelaysRecord? relays)
+    {
+        if (relays is not null)
+            relays.K2ConnectIslandBusToGridBus = true;
+    }
+
+
+    private static Boolean EnableSafeDefaults(this StatusRecord s)
+    {
+        // After some tests, the safe state is switch off inverter and keep the last state of K2 , Dc/Dc and Grid type to avoid conflict. 
+        
+       // s.DcDc.Disable();
+        s.AcDc.Disable();
+       // s.AcDc.EnableGridTieMode();
+       // s.Relays.DisconnectIslandBusFromGrid();
+        return false;
+    }
+    
+    public static DcDcDevicesRecord ResetAlarms(this DcDcDevicesRecord dcDcStatus)
+    {
+        var sc = dcDcStatus.SystemControl;
+
+        if (sc is not null)
+            sc.ResetAlarmsAndWarnings = sc.Alarms.Any();
+
+        foreach (var d in dcDcStatus.Devices)
+            d.Control.ResetAlarmsAndWarnings = d.Status.Alarms.Any() || d.Status.Warnings.Any();
+
+        return dcDcStatus;
+    }
+
+    public static AcDcDevicesRecord ResetAlarms(this AcDcDevicesRecord acDcStatus)
+    {
+        var sc = acDcStatus.SystemControl;
+        
+        if (sc is not null)
+            sc.ResetAlarmsAndWarnings = sc.Alarms.Any() || sc.Warnings.Any();
+
+        foreach (var d in acDcStatus.Devices)
+            d.Control.ResetAlarmsAndWarnings = d.Status.Alarms.Any() || d.Status.Warnings.Any();
+        
+        return acDcStatus;
+    }
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/System/StateMachine.cs b/csharp/App_backup/SaliMax/src/System/StateMachine.cs
new file mode 100644
index 000000000..a22afb483
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/System/StateMachine.cs
@@ -0,0 +1,9 @@
+namespace InnovEnergy.App.SaliMax.System;
+
+public record StateMachine
+{
+    public required String Message { get; set; }  // TODO: init only 
+    public required Int32  State   { get; set; }  // TODO: init only
+
+    public static StateMachine Default { get; } = new StateMachine { State = 100, Message = "Unknown State" };
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/SystemConfig/AcDcConfig.cs b/csharp/App_backup/SaliMax/src/SystemConfig/AcDcConfig.cs
new file mode 100644
index 000000000..20a755151
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SystemConfig/AcDcConfig.cs
@@ -0,0 +1,8 @@
+namespace InnovEnergy.App.SaliMax.SystemConfig;
+
+public class AcDcConfig
+{
+    public required Double MaxDcLinkVoltage       { get; set; }
+    public required Double MinDcLinkVoltage       { get; set; }
+    public required Double ReferenceDcLinkVoltage { get; init; }  
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/SystemConfig/CalibrationChargeType.cs b/csharp/App_backup/SaliMax/src/SystemConfig/CalibrationChargeType.cs
new file mode 100644
index 000000000..91cc45ff3
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SystemConfig/CalibrationChargeType.cs
@@ -0,0 +1,8 @@
+namespace InnovEnergy.App.SaliMax.SystemConfig;
+
+public enum CalibrationChargeType
+{
+    RepetitivelyEvery,
+    AdditionallyOnce,
+    ChargePermanently
+}
diff --git a/csharp/App_backup/SaliMax/src/SystemConfig/Config.cs b/csharp/App_backup/SaliMax/src/SystemConfig/Config.cs
new file mode 100644
index 000000000..0f3a55125
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SystemConfig/Config.cs
@@ -0,0 +1,279 @@
+using System.Text.Json;
+using InnovEnergy.App.SaliMax.Devices;
+using InnovEnergy.Lib.Utils;
+using static System.Text.Json.JsonSerializer;
+
+namespace InnovEnergy.App.SaliMax.SystemConfig;
+
+// shut up trim warnings
+#pragma warning disable IL2026  
+
+public class Config //TODO: let IE choose from config files (Json) and connect to GUI
+{
+    private static String DefaultConfigFilePath => Path.Combine(Environment.CurrentDirectory, "config.json");
+    private static DateTime DefaultDatetime => new(2024, 03, 11, 09, 00, 00); 
+    
+    private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
+
+    public required Double                MinSoc                             { get; set; }
+    public required UInt16                CurtailP                           { get; set; }// in Kw
+    public required UInt16                PvInstalledPower                   { get; set; }// in Kw 
+    public required CalibrationChargeType ForceCalibrationChargeState        { get; set; } 
+    public required DateTime              DayAndTimeForRepetitiveCalibration { get; set; } 
+    public required DateTime              DayAndTimeForAdditionalCalibration { get; set; }
+    public required Boolean               DisplayIndividualBatteries         { get; set; } 
+    public required Double                PConstant                          { get; set; } 
+    public required Double                GridSetPoint                       { get; set; } 
+    public required Double                BatterySelfDischargePower          { get; set; } 
+    public required Double                HoldSocZone                        { get; set; } 
+    public required DevicesConfig         IslandMode                         { get; set; }
+    public required DevicesConfig         GridTie                            { get; set; }
+    
+    public required DeviceConfig          Devices                            { get; set; }
+    public required S3Config?             S3                                 { get; set; }
+
+    private static String? LastSavedData { get; set; }
+    
+    #if DEBUG 
+    public static Config Default => new()
+    {
+        MinSoc                              = 20,
+        CurtailP                            = 0,
+        PvInstalledPower                    = 20,
+        ForceCalibrationChargeState         = CalibrationChargeType.RepetitivelyEvery,  
+        DayAndTimeForRepetitiveCalibration = DefaultDatetime,
+        DayAndTimeForAdditionalCalibration = DefaultDatetime,
+        DisplayIndividualBatteries         = false,
+        PConstant                          = .5,
+        GridSetPoint                       = 0,
+        BatterySelfDischargePower          = 200,  
+        HoldSocZone                        = 1,   // TODO: find better name,
+        IslandMode = new()
+        {
+            AcDc = new ()
+            {
+                MinDcLinkVoltage       = 690,
+                ReferenceDcLinkVoltage = 750,
+                MaxDcLinkVoltage       = 810,
+            },
+        
+            DcDc = new ()
+            {
+                UpperDcLinkVoltage     = 50,
+                LowerDcLinkVoltage     = 50,
+                ReferenceDcLinkVoltage = 750,
+                            
+                MaxBatteryChargingCurrent    = 210,
+                MaxBatteryDischargingCurrent = 210,
+                MaxDcPower                   = 10000,
+                        
+                MaxChargeBatteryVoltage    = 57,
+                MinDischargeBatteryVoltage = 0,
+            },
+        },
+        
+        GridTie = new()
+        {
+            AcDc = new ()
+            {
+                MinDcLinkVoltage       = 720,
+                ReferenceDcLinkVoltage = 750,
+                MaxDcLinkVoltage       = 810,
+            },
+        
+            DcDc = new ()
+            {
+                UpperDcLinkVoltage     = 50,
+                LowerDcLinkVoltage     = 50,
+                ReferenceDcLinkVoltage = 750,
+                            
+                MaxBatteryChargingCurrent    = 210,
+                MaxBatteryDischargingCurrent = 210,
+                MaxDcPower                   = 10000,
+                        
+                MaxChargeBatteryVoltage    = 57,
+                MinDischargeBatteryVoltage = 0,
+            },
+        },
+        
+        MaxBatteryChargingCurrent    = 210,
+        MaxBatteryDischargingCurrent = 210,
+        MaxDcPower                   = 10000,
+
+        MaxChargeBatteryVoltage    = 57,
+        MinDischargeBatteryVoltage = 0,
+        
+        Devices = new ()
+        {
+            RelaysIp             = new() { Host = "localhost", Port = 5006, DeviceState = DeviceState.Measured},
+            TsRelaysIp           = new() { Host = "localhost", Port = 5006, DeviceState = DeviceState.Measured},
+            GridMeterIp          = new() { Host = "localhost", Port = 5003, DeviceState = DeviceState.Measured},  
+            PvOnAcGrid           = new() { Host = "false"    , Port = 0   , DeviceState = DeviceState.Measured},
+            LoadOnAcGrid         = new() { Host = "false"    , Port = 0   , DeviceState = DeviceState.Measured},
+            PvOnAcIsland         = new() { Host = "true"     , Port = 0   , DeviceState = DeviceState.Measured},
+            IslandBusLoadMeterIp = new() { Host = "localhost", Port = 5004, DeviceState = DeviceState.Measured},  
+            TruConvertAcIp       = new() { Host = "localhost", Port = 5001, DeviceState = DeviceState.Measured},  
+            PvOnDc               = new() { Host = "localhost", Port = 5005, DeviceState = DeviceState.Measured},  
+            LoadOnDc             = new() { Host = "false"    , Port = 0   , DeviceState = DeviceState.Measured},
+            TruConvertDcIp       = new() { Host = "localhost", Port = 5002, DeviceState = DeviceState.Measured},           
+            BatteryIp            = new() { Host = "localhost", Port = 5007, DeviceState = DeviceState.Measured}, 
+            BatteryNodes         = new []{ 2, 3, 4, 5, 6 }
+        },
+
+        S3 = new()
+        {
+            Bucket      = "1-3e5b3069-214a-43ee-8d85-57d72000c19d",
+            Region      = "sos-ch-dk-2",
+            Provider    = "exo.io",
+            ContentType = "text/plain; charset=utf-8",
+            Key         = "EXO4ec5faf1a7650b79b5722fb5",
+            Secret      = "LUxu1PGEA-POEIckoEyq6bYyz0RnenW6tmqccMKgkHQ"
+        },
+    };
+    #else 
+    public static Config Default => new()
+    {
+        MinSoc                               = 20,
+        CurtailP                             = 0,
+        PvInstalledPower                     = 20,
+        ForceCalibrationChargeState          = CalibrationChargeType.RepetitivelyEvery,
+        DayAndTimeForRepetitiveCalibration = DefaultDatetime,
+        DayAndTimeForAdditionalCalibration = DefaultDatetime,
+        DisplayIndividualBatteries           = false,
+        PConstant                            = .5,
+        GridSetPoint                         = 0,
+        BatterySelfDischargePower            = 200,  
+        HoldSocZone                          = 1,   // TODO: find better name,
+        IslandMode = new()
+        {
+            AcDc = new ()
+            {
+                MinDcLinkVoltage       = 690,
+                ReferenceDcLinkVoltage = 750,
+                MaxDcLinkVoltage       = 810,
+            },
+        
+            DcDc = new ()
+            {
+                UpperDcLinkVoltage     = 50,
+                LowerDcLinkVoltage     = 50,
+                ReferenceDcLinkVoltage = 750,
+                                            
+                MaxBatteryChargingCurrent    = 210,
+                MaxBatteryDischargingCurrent = 210,
+                MaxDcPower                   = 10000,
+                        
+                MaxChargeBatteryVoltage    = 57,
+                MinDischargeBatteryVoltage = 0,
+            },
+        },
+        
+        GridTie = new()
+        {
+            AcDc = new ()
+            {
+                MinDcLinkVoltage       = 720,
+                ReferenceDcLinkVoltage = 750,
+                MaxDcLinkVoltage       = 780,
+            },
+        
+            DcDc = new ()
+            {
+                UpperDcLinkVoltage     = 20,
+                LowerDcLinkVoltage     = 20,
+                ReferenceDcLinkVoltage = 750,
+                            
+                MaxBatteryChargingCurrent    = 210,
+                MaxBatteryDischargingCurrent = 210,
+                MaxDcPower                   = 10000,
+                        
+                MaxChargeBatteryVoltage    = 57,
+                MinDischargeBatteryVoltage = 0,
+            },
+        },
+
+        
+        S3 = new()
+        {
+            Bucket      = "1-3e5b3069-214a-43ee-8d85-57d72000c19d",
+            Region      = "sos-ch-dk-2",
+            Provider    = "exo.io",
+            Key         = "EXObb5a49acb1061781761895e7",
+            Secret      = "sKhln0w8ii3ezZ1SJFF33yeDo8NWR1V4w2H0D4-350I",
+            ContentType = "text/plain; charset=utf-8"
+        },
+        
+        Devices                  = new ()
+        {
+            RelaysIp             = new() { Host = "10.0.1.1",  Port = 502,  DeviceState = DeviceState.Measured},
+            TsRelaysIp           = new() { Host = "10.0.1.2",  Port = 502,  DeviceState = DeviceState.Measured},
+            GridMeterIp          = new() { Host = "10.0.4.1",  Port = 502,  DeviceState = DeviceState.Measured},
+            PvOnAcGrid           = new() { Host = "false"   ,  Port = 0  ,  DeviceState = DeviceState.Measured},
+            LoadOnAcGrid         = new() { Host = "true"    ,  Port = 0  ,  DeviceState = DeviceState.Measured},
+            PvOnAcIsland         = new() { Host = "false"   ,  Port = 0  ,  DeviceState = DeviceState.Measured},
+            IslandBusLoadMeterIp = new() { Host = "10.0.4.2",  Port = 502,  DeviceState = DeviceState.Measured}, 
+            TruConvertAcIp       = new() { Host = "10.0.2.1",  Port = 502,  DeviceState = DeviceState.Measured}, 
+            PvOnDc               = new() { Host = "10.0.5.1",  Port = 502,  DeviceState = DeviceState.Measured}, 
+            LoadOnDc             = new() { Host = "false"   ,  Port = 0  ,  DeviceState = DeviceState.Measured},
+            TruConvertDcIp       = new() { Host = "10.0.3.1",  Port = 502,  DeviceState = DeviceState.Measured}, 
+            BatteryIp            = new() { Host = "localhost", Port = 6855, DeviceState = DeviceState.Measured },
+            BatteryNodes         = new []{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
+        },
+    };
+    #endif
+
+    public void Save(String? path = null)
+    {
+        var configFilePath = path ?? DefaultConfigFilePath;
+        
+        try
+        {
+            var jsonString = Serialize(this, JsonOptions);
+
+            if (LastSavedData == jsonString)
+                return;
+            
+            LastSavedData = jsonString;
+
+            File.WriteAllText(configFilePath, jsonString);
+        }
+        catch (Exception e)
+        {
+            $"Failed to write config file {configFilePath}\n{e}".WriteLine();
+            throw;
+        }
+    }
+    
+    
+    public static Config Load(String? path = null)
+    {
+        var configFilePath = path ?? DefaultConfigFilePath;
+        try
+        {
+            var jsonString = File.ReadAllText(configFilePath);
+            return Deserialize<Config>(jsonString)!;
+        }
+        catch (Exception e)
+        {
+            $"Failed to read config file {configFilePath}, using default config\n{e}".WriteLine();
+            return Default;
+        }
+    }
+    
+    
+    public static async Task<Config> LoadAsync(String? path = null)
+    {
+        var configFilePath = path ?? DefaultConfigFilePath;
+        try
+        {
+            var jsonString = await File.ReadAllTextAsync(configFilePath);
+            return Deserialize<Config>(jsonString)!;
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine($"Couldn't read config file {configFilePath}, using default config");
+            e.Message.WriteLine();
+            return Default;
+        }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/SystemConfig/DcDcConfig.cs b/csharp/App_backup/SaliMax/src/SystemConfig/DcDcConfig.cs
new file mode 100644
index 000000000..daf6a9a43
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SystemConfig/DcDcConfig.cs
@@ -0,0 +1,15 @@
+namespace InnovEnergy.App.SaliMax.SystemConfig;
+
+public class DcDcConfig
+{
+    public required Double LowerDcLinkVoltage           { get; set; }
+    public required Double ReferenceDcLinkVoltage       { get; init; } 
+    public required Double UpperDcLinkVoltage           { get; set; }
+    
+    public required Double MaxBatteryChargingCurrent    { get; set; } 
+    public required Double MaxBatteryDischargingCurrent { get; set; }
+    public required Double MaxDcPower                   { get; set; }
+    
+    public required Double MaxChargeBatteryVoltage      { get; set; }
+    public required Double MinDischargeBatteryVoltage   { get; set; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/SystemConfig/DeviceConfig.cs b/csharp/App_backup/SaliMax/src/SystemConfig/DeviceConfig.cs
new file mode 100644
index 000000000..5e23037ef
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SystemConfig/DeviceConfig.cs
@@ -0,0 +1,21 @@
+using InnovEnergy.App.SaliMax.Devices;
+using InnovEnergy.Lib.Utils.Net;
+
+namespace InnovEnergy.App.SaliMax.SystemConfig;
+
+public class DeviceConfig
+{
+    public required SalimaxDevice RelaysIp             { get; init; }
+    public required SalimaxDevice TsRelaysIp           { get; init; }
+    public required SalimaxDevice GridMeterIp          { get; init; }
+    public required SalimaxDevice PvOnAcGrid           { get; init; }
+    public required SalimaxDevice LoadOnAcGrid         { get; init; }
+    public required SalimaxDevice PvOnAcIsland         { get; init; }
+    public required SalimaxDevice IslandBusLoadMeterIp { get; init; }
+    public required SalimaxDevice TruConvertAcIp       { get; init; }
+    public required SalimaxDevice PvOnDc               { get; init; }
+    public required SalimaxDevice LoadOnDc             { get; init; }
+    public required SalimaxDevice TruConvertDcIp       { get; init; }
+    public required SalimaxDevice BatteryIp            { get; init; }
+    public required Int32[]       BatteryNodes         { get; init; }
+}
diff --git a/csharp/App_backup/SaliMax/src/SystemConfig/DevicesConfig.cs b/csharp/App_backup/SaliMax/src/SystemConfig/DevicesConfig.cs
new file mode 100644
index 000000000..4ae202e32
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/SystemConfig/DevicesConfig.cs
@@ -0,0 +1,7 @@
+namespace InnovEnergy.App.SaliMax.SystemConfig;
+
+public class DevicesConfig
+{
+    public required AcDcConfig AcDc { get; init; }
+    public required DcDcConfig DcDc { get; init; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SaliMax/src/Topology.cs b/csharp/App_backup/SaliMax/src/Topology.cs
new file mode 100644
index 000000000..73a0960d2
--- /dev/null
+++ b/csharp/App_backup/SaliMax/src/Topology.cs
@@ -0,0 +1,534 @@
+using System.Diagnostics.CodeAnalysis;
+using InnovEnergy.App.SaliMax.Devices;
+using InnovEnergy.App.SaliMax.Ess;
+using InnovEnergy.Lib.Devices.AMPT;
+using InnovEnergy.Lib.Devices.Battery48TL;
+using InnovEnergy.Lib.Devices.EmuMeter;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
+using InnovEnergy.Lib.Units;
+using InnovEnergy.Lib.Units.Power;
+using InnovEnergy.Lib.Utils;
+using Ac3Bus = InnovEnergy.Lib.Units.Composite.Ac3Bus;
+
+namespace InnovEnergy.App.SaliMax;
+
+//                         ┌────┐                 ┌────┐
+//                         │ Pv │                 │ Pv │                                     ┌────┐
+//                         └────┘                 └────┘                                     │ Pv │
+//                           V                      V                                        └────┘
+//                           V                      V                                          V
+//                     (b)  0 W               (e)  0 W                                         V
+//                           V                      V                                   (i)  13.2 kW                              ┌────────────┐
+//                           V                      V                                          V                                  │  Battery   │
+// ┌─────────┐          ┌──────────┐          ┌────────────┐          ┌─────────┐              V                                  ├────────────┤
+// │  Grid   │          │ Grid Bus │          │ Island Bus │          │  AC/DC  │          ┌────────┐          ┌───────┐          │ 52.3 V     │
+// ├─────────┤ -10.3 kW ├──────────┤ -11.7 kW ├────────────┤ -11.7 kW ├─────────┤ -11.7 kW │ Dc Bus │  1008 W  │ DC/DC │  1008 W  │ 99.1 %     │
+// │ -3205 W │<<<<<<<<<<│ 244 V    │<<<<<<<<<<│ 244 V      │<<<<<<<<<<│ -6646 W │<<<<<<<<<<├────────┤>>>>>>>>>>├───────┤>>>>>>>>>>│ 490 mA     │
+// │ -3507 W │   (a)    │ 244 V    │   (d)    │ 244 V      │    (g)   │ -5071 W │    (h)   │ 776 V  │    (k)   │ 56 V  │   (l)    │ 250 °C     │
+// │ -3605 W │   K1     │ 246 V    │   K2     │ 246 V      │    K3    └─────────┘          └────────┘          └───────┘          │ 445 A      │
+// └─────────┘          └──────────┘          └────────────┘                                   V                                  │ 0 Warnings │
+//                           V                      V                                          V                                  │ 0 Alarms   │
+//                           V                      V                                    (j)  0 W                                 └────────────┘
+//                     (c) 1400 W             (f)  0 W                                         V
+//                           V                      V                                          V
+//                           V                      V                                       ┌──────┐
+//                        ┌──────┐               ┌──────┐                                   │ Load │
+//                        │ Load │               │ Load │                                   └──────┘
+//                        └──────┘               └──────┘
+
+
+//   Calculated values: c,d & h
+//   ==========================
+//
+//   
+//    AC side
+//    a + b - c - d = 0     [eq1]
+//    d + e - f - g = 0     [eq2]
+//   
+//    c & d are not measured!
+//   
+//    d = f + g - e  [eq2]
+//    c = a + b - d  [eq1]
+//   
+//    DC side
+//    h + i - j - k = 0  [eq3]
+//   
+//    if Dc load not existing,  h = i - k [eq4]
+
+//    k = l        assuming no losses in DCDC // this is changed now l is equal total battery power
+//    j = h + i - k   [eq3]
+
+
+public static class Topology
+{
+    
+    public static TextBlock CreateTopologyTextBlock(this StatusRecord status)
+    {
+        var a = status.GridMeter?.Ac.Power.Active;
+        var b = status.PvOnAcGrid?.Dc.Power.Value;
+        var e = status.PvOnAcIsland?.Dc.Power.Value;
+        var f = status.LoadOnAcIsland?.Ac.Power.Active;
+        var g = status.AcDc.Dc.Power.Value;
+        var h = status.AcDcToDcLink?.Power.Value;
+        var i = status.PvOnDc?.Dc.Power.Value;
+        var k = status.DcDc.Dc.Link.Power.Value;
+        var l = status.Battery is not null ? status.Battery.Dc.Power.Value : 0;
+        var j = status.LoadOnDc?.Power.Value;
+        var d = status.AcGridToAcIsland?.Power.Active;
+        var c = status.LoadOnAcGrid?.Power.Active;
+
+        /////////////////////////////
+        
+        var grid      = status.CreateGridColumn(a);
+        var gridBus   = status.CreateGridBusColumn(b, c, d);
+        var islandBus = status.CreateIslandBusColumn(e, f, g);
+        var inverter  = status.CreateInverterColumn(h);
+        var dcBus     = status.CreateDcBusColumn(i, j, k);
+        var dcDc      = status.CreateDcDcColumn(l);
+        var batteries = status.CreateBatteryColumn();
+
+        return TextBlock.AlignCenterVertical
+        (
+            grid,
+            gridBus,
+            islandBus,
+            inverter,
+            dcBus,
+            dcDc,
+            batteries
+        );
+    }
+
+    private static TextBlock CreateGridColumn(this StatusRecord status, ActivePower? a)
+    {
+        //  ┌─────────┐          
+        //  │  Grid   │          
+        //  ├─────────┤ -10.3 kW 
+        //  │ -3205 W │<<<<<<<<<<
+        //  │ -3507 W │   (a)    
+        //  │ -3605 W │   K1     
+        //  └─────────┘             
+        
+        var gridMeterAc = status.GridMeter?.Ac;
+        var k1          = status.Relays?.K1GridBusIsConnectedToGrid;
+        
+        var gridBox  = PhasePowersActive(gridMeterAc).TitleBox("Grid");
+        var gridFlow = SwitchedFlow(k1, a, "K1");
+
+        return TextBlock.AlignCenterVertical(gridBox, gridFlow);
+    }
+
+
+    private static TextBlock CreateGridBusColumn(this StatusRecord status, 
+                                                      ActivePower? b, 
+                                                      ActivePower? c, 
+                                                      ActivePower? d)
+    {
+        
+        //                ┌────┐                          
+        //                │ Pv │                          
+        //                └────┘                          
+        //                  V                                
+        //                  V                                
+        //            (b)  0 W                              
+        //                  V                                
+        //                  V                                
+        //             ┌──────────┐                    
+        //             │ Grid Bus │                    
+        //             ├──────────┤ -11.7 kW  
+        //             │ 244 V    │<<<<<<<<<<
+        //             │ 244 V    │   (d)           
+        //             │ 246 V    │   K2               
+        //             └──────────┘                    
+        //                  V                                
+        //                  V                                
+        //            (c) 1400 W                          
+        //                  V                                
+        //                  V                                
+        //               ┌──────┐                        
+        //               │ Load │                        
+        //               └──────┘                        
+
+
+        ////////////// top //////////////
+        
+        var pvBox  = TextBlock.FromString("PV").Box();
+        var pvFlow = Flow.Vertical(b);
+
+        ////////////// center  //////////////
+
+        // on IslandBus show voltages measured by inverter
+        // on GridBus show voltages measured by grid meter
+        // ought to be approx the same
+        
+        var gridMeterAc = status.GridMeter?.Ac;
+        var k2 = status.Relays?.K2IslandBusIsConnectedToGridBus;
+        
+        var busBox  = PhaseVoltages(gridMeterAc).TitleBox("Grid Bus");
+        var busFlow = SwitchedFlow(k2, d, "K2");
+        
+        ////////////// bottom //////////////
+        
+        var loadFlow = Flow.Vertical(c);
+        var loadBox  = TextBlock.FromString("Load").Box();
+
+        ////////////// assemble //////////////
+        
+        return TextBlock.AlignCenterVertical
+        (
+            TextBlock.AlignCenterHorizontal(pvBox, pvFlow, busBox, loadFlow, loadBox),
+            busFlow
+        );
+    }
+
+    
+    private static TextBlock CreateIslandBusColumn(this StatusRecord status, 
+                                                        ActivePower? e, 
+                                                        ActivePower? f, 
+                                                        ActivePower? g)
+    {
+        
+        //           ┌────┐                                       
+        //           │ Pv │                                                         
+        //           └────┘                                                         
+        //             V                                                                        
+        //             V                                                                        
+        //       (e)  0 W                                                                   
+        //             V                                                                        
+        //             V                                                                        
+        //       ┌────────────┐                                        
+        //       │ Island Bus │                                        
+        //       ├────────────┤ -11.7 kW    
+        //       │ 244 V      │<<<<<<<<<<
+        //       │ 244 V      │    (g)                  
+        //       │ 246 V      │    K3                        
+        //       └────────────┘                                        
+        //             V                                                                        
+        //             V                                                                        
+        //       (f)  0 W                                                             
+        //             V                                                                        
+        //             V                                                                        
+        //          ┌──────┐                                                    
+        //          │ Load │                                                    
+        //          └──────┘                                    
+
+
+        ////////////// top //////////////
+        
+        var pvBox  = TextBlock.FromString("PV").Box();
+        var pvFlow = Flow.Vertical(e);
+
+        ////////////// center  //////////////
+
+        // on IslandBus show voltages measured by inverter
+        // on GridBus show voltages measured by grid meter
+        // ought to be approx the same
+        
+        var inverterAc = status.AcDc.Ac;
+        var busBox     = PhaseVoltages(inverterAc).TitleBox("Island Bus");  
+        var busFlow    = status.IslandBusToInverterConnection(g);
+        
+        ////////////// bottom //////////////
+        
+        var loadFlow = Flow.Vertical(f);
+        var loadBox  = TextBlock.FromString("Load").Box();
+
+        ////////////// assemble //////////////
+        
+        return TextBlock.AlignCenterVertical
+        (
+            TextBlock.AlignCenterHorizontal(pvBox, pvFlow, busBox, loadFlow, loadBox),
+            busFlow
+        );
+    }
+
+
+
+    private static TextBlock CreateInverterColumn(this StatusRecord status, ActivePower? h)
+    {
+        //    ┌─────────┐          
+        //    │  AC/DC  │          
+        //    ├─────────┤ -11.7 kW 
+        //    │ -6646 W │<<<<<<<<<<
+        //    │ -5071 W │    (h)   
+        //    └─────────┘              
+        
+        var inverterBox = status
+                         .AcDc
+                         .Devices
+                         .Select(d => d.Status.Ac.Power.Active)
+                         .Apply(TextBlock.AlignLeft)
+                         .TitleBox("AC/DC");
+
+        var dcFlow = Flow.Horizontal(h);
+
+        return TextBlock.AlignCenterVertical(inverterBox, dcFlow);
+    }
+
+
+    [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
+    private static TextBlock IslandBusToInverterConnection(this StatusRecord status, ActivePower? g)
+    {
+        if (status.Relays is null)
+            return TextBlock.FromString("????????");
+        
+        var nInverters = status.AcDc.Devices.Count;
+        
+        var k3S = status
+                 .Relays
+                 .K3InverterIsConnectedToIslandBus
+                 .Take(nInverters);
+
+        if (k3S.Prepend(true).All(s => s))   // TODO: display when no ACDC present
+            return Flow.Horizontal(g);
+
+        return Switch.Open("K3");
+    }
+
+    private static TextBlock CreateDcDcColumn(this StatusRecord status, ActivePower? p)
+    {
+        var dc48Voltage = status.DcDc.Dc.Battery.Voltage.ToDisplayString();
+
+        var busBox = TextBlock
+                    .AlignLeft(dc48Voltage)
+                    .TitleBox("DC/DC");
+
+        var busFlow = Flow.Horizontal(p);
+        
+        return TextBlock.AlignCenterVertical(busBox, busFlow);
+    }
+
+    private static TextBlock CreateDcBusColumn(this StatusRecord status, 
+                                                    ActivePower? i, 
+                                                    ActivePower? j, 
+                                                    ActivePower? k)
+    {
+        //          ┌────┐
+        //          │ Pv │
+        //          └────┘
+        //            V
+        //            V
+        //     (i)  13.2 kW           
+        //            V               
+        //            V               
+        //        ┌────────┐          
+        //        │ Dc Bus │  1008 W  
+        //        ├────────┤>>>>>>>>>>
+        //        │ 776 V  │    (k)   
+        //        └────────┘          
+        //            V               
+        //            V               
+        //      (j)  0 W              
+        //            V
+        //            V
+        //         ┌──────┐
+        //         │ Load │
+        //         └──────┘
+        //            
+        
+        
+        /////////////////// top ///////////////////
+
+        var mppt = status.PvOnDc;
+
+        var nStrings = mppt is not null
+                     ? "x" + mppt.Strings.Count
+                     : "?";
+            
+        var pvBox    = TextBlock.FromString($"PV {nStrings}").Box();
+        var pvToBus  = Flow.Vertical(i);
+        
+        /////////////////// center ///////////////////
+
+        var dcBusVoltage = status.DcDc.Dc.Link.Voltage;
+        
+        var dcBusBox = dcBusVoltage
+                      .ToDisplayString()
+                      .Apply(TextBlock.FromString)
+                      .TitleBox("DC Bus ");
+
+        var busFlow = Flow.Horizontal(k);
+        
+        /////////////////// bottom ///////////////////
+        
+        var busToLoad = Flow.Vertical(j);
+        var loadBox   = TextBlock.FromString("DC Load").Box();
+        
+        ////////////// assemble //////////////
+        
+        return TextBlock.AlignCenterVertical
+        (
+            TextBlock.AlignCenterHorizontal(pvBox, pvToBus, dcBusBox, busToLoad, loadBox),
+            busFlow
+        );
+    }
+
+    private static TextBlock CreateBatteryColumn(this StatusRecord status)
+    {
+        var bat = status.Battery;
+        if (bat is null)
+            return TextBlock.AlignLeft("no battery").Box();
+        
+        
+        var batteryAvgBox = CreateAveragedBatteryBox(bat);
+
+        var batteryBoxes = bat
+                          .Devices
+                          .Select(CreateBatteryBox)
+                          .ToReadOnlyList();
+
+
+        var individualWithAvgBox = TextBlock
+                                    .AlignCenterVertical
+                                    (
+                                        batteryAvgBox ,
+                                        batteryBoxes.Any()
+                                            ? TextBlock.AlignLeft(batteryBoxes)
+                                            : TextBlock.Empty 
+                                    );
+
+        return status.Config.DisplayIndividualBatteries ? individualWithAvgBox : batteryAvgBox;
+    }
+
+    private static TextBlock CreateAveragedBatteryBox(Battery48TlRecords bat)
+    {
+        var voltage        = bat.Dc.Voltage.ToDisplayString();
+        var soc            = bat.Devices.Any() ? bat.Devices.Average(b => b.Soc).Percent().ToDisplayString() : "0"; // TODO
+        var current        = bat.Dc.Current.ToDisplayString();
+        var busCurrent     = bat.Devices.Any() ? bat.Devices.Sum(b => b.BusCurrent).A().ToDisplayString() : "0";
+        var temp           = bat.Temperature.ToDisplayString();
+        var heatingPower   = bat.HeatingPower.ToDisplayString();
+        var alarms         = bat.Alarms.Count   + " Alarms";
+        var warnings       = bat.Warnings.Count + " Warnings";
+        var nBatteries     = bat.Devices.Count;
+        
+        return TextBlock
+              .AlignLeft
+              (
+                  voltage,
+                  soc,
+                  current,
+                  busCurrent,
+                  temp,
+                  heatingPower,
+                  warnings,
+                  alarms
+              )
+              .TitleBox($"Battery x{nBatteries}");
+    }
+
+    private static TextBlock PhaseVoltages(Ac3Bus? ac)
+    {
+        return TextBlock.AlignLeft
+        (
+            ac?.L1.Voltage.ToDisplayString() ?? "???",
+            ac?.L2.Voltage.ToDisplayString() ?? "???",
+            ac?.L3.Voltage.ToDisplayString() ?? "???"
+        );
+    }
+
+    private static TextBlock PhasePowersActive(Ac3Bus? ac)
+    {
+        return TextBlock.AlignLeft
+        (
+            ac?.L1.Power.Active.ToDisplayString() ?? "???",
+            ac?.L2.Power.Active.ToDisplayString() ?? "???",
+            ac?.L3.Power.Active.ToDisplayString() ?? "???"
+        );
+    }
+
+    private static TextBlock CreateBatteryBox(Battery48TlRecord battery, Int32 i)
+    {
+        var batteryWarnings = battery.Warnings.Any();
+        var batteryAlarms   = battery.Alarms.Any();
+
+        var content = TextBlock.AlignLeft
+        (
+            battery.Dc.Voltage.ToDisplayString(),
+            battery.Soc.ToDisplayString(),
+            battery.Dc.Current.ToDisplayString() + " C/D",
+            battery.Temperatures.Cells.Average.ToDisplayString(),
+            battery.BusCurrent.ToDisplayString() ,
+            batteryWarnings,
+            batteryAlarms,
+            battery.HeatingCurrent.ToDisplayString() + " HEAT"
+        );
+
+        var box  = content.TitleBox($"Battery {i + 1}");
+        var flow = Flow.Horizontal(battery.Dc.Power);
+
+        return TextBlock.AlignCenterVertical(flow, box);
+    }
+
+
+    private static TextBlock SwitchedFlow(Boolean? switchClosed, ActivePower? power, String kx)
+    {
+        return switchClosed is null ? TextBlock.FromString("??????????")
+             : !switchClosed.Value  ? Switch.Open(kx)
+             : power is null        ? TextBlock.FromString("??????????") 
+             :                        Flow.Horizontal(power);
+    }
+
+    //We are fake using the ampt instead of PvOnAc, We dont have a Pv on Ac at the moment and we don't have it classes :TODO 
+    public static AcPowerDevice? CalculateGridBusLoad(EmuMeterRegisters? gridMeter, AmptStatus? pvOnAcGrid, AcPowerDevice? gridBusToIslandBusPower)
+    {
+        var a = gridMeter              ?.Ac.Power;
+        var b = pvOnAcGrid  is not null? pvOnAcGrid?.Dc.Power.Value: 0;
+        var d = gridBusToIslandBusPower?.Power;
+        
+        if (a is null || b is null || d is null)
+            return null;
+        
+        var c = a + b - d;  // [eq1]
+        
+        return new AcPowerDevice { Power = c };        
+    }
+    
+    //We are fake using the ampt instead of PvOnAc, We dont have a Pv on Ac at the moment and we don't have it classes :TODO 
+    public static DcPowerDevice? CalculateAcDcToDcLink(AmptStatus? pvOnDc, DcDcDevicesRecord? dcDc, AcDcDevicesRecord acDc)
+    {
+        var i = pvOnDc?.Dc.Power;
+        var k = dcDc?.Dc.Link.Power;        // We don't check on the DcDc because this device is mandatory
+        var g = acDc?.Dc.Power;
+        
+        if (i is null || k is null )
+        {
+            return new DcPowerDevice { Power = g };  
+        }
+        
+        var h = -(i - k);  // [eq4]  
+        
+        return new DcPowerDevice { Power = h };        
+    }
+    
+    //We are fake using the ampt instead of PvOnAc, We dont have a Pv on Ac at the moment and we don't have it classes :TODO 
+    public static AcPowerDevice? CalculateGridBusToIslandBusPower(AmptStatus? pvOnAcIsland, EmuMeterRegisters? loadOnAcIsland, AcDcDevicesRecord? acDc)
+    {
+        var e = pvOnAcIsland   is not null? pvOnAcIsland?.Dc.Power.Value: 0;
+        var f = loadOnAcIsland is not null? loadOnAcIsland?.Ac.Power    : 0;
+        var g = acDc          ?.Ac.Power; // We don't check on the AcDc because this device is mandatory, if this does not exist the system will not start
+
+        if (e is null || f is null || g is null)
+            return null;
+
+        var d = f + g - e; // [eq2]
+
+        return new AcPowerDevice { Power = d };
+    }
+
+    public static DcPowerDevice? CalculateDcLoad(AcDcDevicesRecord? acDc, AmptStatus? pvOnDc, DcDcDevicesRecord? dcDc)
+    {
+        var h = acDc?.Dc.Power;             // We don't check on the AcDc because this device is mandatory
+        var i = pvOnDc is not null?  pvOnDc?.Dc.Power: 0;
+        var k = dcDc?.Dc.Link.Power;        // We don't check on the DcDc because this device is mandatory
+        
+        if (h is null || i is null || k is null)
+            return null;
+
+        var j = h + i - k; // [eq3]
+
+        return new DcPowerDevice { Power = j};
+    }
+    
+}
diff --git a/csharp/App_backup/SaliMax/tunnelstoSalimaxX.sh b/csharp/App_backup/SaliMax/tunnelstoSalimaxX.sh
new file mode 100755
index 000000000..03b256195
--- /dev/null
+++ b/csharp/App_backup/SaliMax/tunnelstoSalimaxX.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+host="ie-entwicklung@$1"
+
+tunnel() {
+  name=$1
+  ip=$2
+  rPort=$3
+  lPort=$4
+
+  echo -n "$name @ $ip    mapped to    localhost:$lPort  "
+  ssh -nNTL "$lPort:$ip:$rPort" "$host" 2> /dev/null &
+
+  until nc -vz 127.0.0.1 $lPort 2> /dev/null
+  do
+    echo -n .
+    sleep 0.3
+  done
+
+  echo "ok"
+}
+
+echo ""
+
+tunnel "Trumpf Inverter (http)  " 10.0.2.1 80 8001
+tunnel "Trumpf DCDC (http)      " 10.0.3.1 80 8002
+tunnel "Ext Emu Meter (http)    " 10.0.4.1 80 8003
+tunnel "Int Emu Meter (http)    " 10.0.4.2 80 8004
+tunnel "AMPT (http)             " 10.0.5.1 8080 8005
+
+tunnel "Trumpf Inverter (modbus)" 10.0.2.1 502 5001
+tunnel "Trumpf DCDC (modbus)    " 10.0.3.1 502 5002
+tunnel "Ext Emu Meter (modbus)  " 10.0.4.1 502 5003
+tunnel "Int Emu Meter           " 10.0.4.2 502 5004
+tunnel "AMPT (modbus)           " 10.0.5.1 502 5005
+tunnel "Adam                    " 10.0.1.1 502 5006  #for AMAX is 10.0.1.3
+tunnel "Batteries               " 127.0.0.1 6855 5007
+
+echo
+echo "press any key to close the tunnels ..."
+read -r -n 1 -s
+kill $(jobs -p)
+echo "done"
+
diff --git a/csharp/App_backup/SaliMax/uploadBatteryFw/AF0A.bin b/csharp/App_backup/SaliMax/uploadBatteryFw/AF0A.bin
new file mode 100644
index 000000000..e5b7b9aba
Binary files /dev/null and b/csharp/App_backup/SaliMax/uploadBatteryFw/AF0A.bin differ
diff --git a/csharp/App_backup/SaliMax/uploadBatteryFw/update_firmware.sh b/csharp/App_backup/SaliMax/uploadBatteryFw/update_firmware.sh
new file mode 100755
index 000000000..fab8d1174
--- /dev/null
+++ b/csharp/App_backup/SaliMax/uploadBatteryFw/update_firmware.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+dotnet_version='net6.0'
+salimax_ip="$1"
+username='ie-entwicklung'
+root_password='Salimax4x25'
+
+set -e
+
+ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.29")
+battery_ids=("2" "3" "4" "5" "6" "7" "8" "9" "10" "11")
+
+
+for ip_address in "${ip_addresses[@]}"; do
+    scp upload-bms-firmware AF0A.bin "$username"@"$ip_address":/home/"$username"
+    ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S systemctl stop battery.service"
+    ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S apt install python3-pip -y"
+    ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S pip3 install pymodbus"
+    
+    for battery in "${battery_ids[@]}"; do
+        ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S python3 upload-bms-firmware ttyUSB0 " "$battery" " AF0A.bin"
+    done
+    ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S systemctl start battery.service"
+    ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S systemctl rm upload-bms-firmware AF0A.bin"
+
+echo "Deployed and ran commands on $ip_address"
+done
+
+
diff --git a/csharp/App_backup/SaliMax/uploadBatteryFw/upload-bms-firmware b/csharp/App_backup/SaliMax/uploadBatteryFw/upload-bms-firmware
new file mode 100755
index 000000000..58d2c804d
--- /dev/null
+++ b/csharp/App_backup/SaliMax/uploadBatteryFw/upload-bms-firmware
@@ -0,0 +1,288 @@
+#!/usr/bin/python2 -u
+# coding=utf-8
+
+import os
+import struct
+from time import sleep
+
+import serial
+from os import system
+import logging
+
+from pymodbus.client import ModbusSerialClient as Modbus
+from pymodbus.exceptions import ModbusIOException
+from pymodbus.pdu import ModbusResponse
+from os.path import dirname, abspath
+from sys import path, argv, exit
+
+path.append(dirname(dirname(abspath(__file__))))
+
+PAGE_SIZE = 0x100
+HALF_PAGE =int( PAGE_SIZE / 2)
+WRITE_ENABLE = [1]
+FIRMWARE_VERSION_REGISTER = 1054
+
+ERASE_FLASH_REGISTER = 0x2084
+RESET_REGISTER = 0x2087
+logging.basicConfig(level=logging.INFO)
+
+
+# trick the pycharm type-checker into thinking Callable is in scope, not used at runtime
+# noinspection PyUnreachableCode
+if False:
+    from typing import List, NoReturn, Iterable, Optional
+
+def calc_stm32_crc_round(crc, data):
+    # type: (int, int) -> int
+    crc = crc ^ data
+    for _ in range(32):
+        xor = (crc & 0x80000000) != 0
+        crc = (crc & 0x7FFFFFFF) << 1    # clear bit 31 because python ints have "infinite" bits
+        if xor:
+            crc = crc ^ 0x04C11DB7
+
+    return crc
+
+
+def calc_stm32_crc(data):
+    # type: (Iterable[int]) -> int
+    crc = 0xFFFFFFFF
+
+    for dw in data:
+        crc = calc_stm32_crc_round(crc, dw)
+
+    return crc
+
+
+def init_modbus(tty):
+    # type: (str) -> Modbus
+
+    return Modbus(
+        port='/dev/' + tty,
+        method='rtu',
+        baudrate=115200,
+        stopbits=1,
+        bytesize=8,
+        timeout=0.5,  # seconds
+        parity=serial.PARITY_ODD)
+
+
+def failed(response):
+    # type: (ModbusResponse) -> bool
+
+    # Todo 'ModbusIOException' object has no attribute 'function_code'
+    return response.function_code > 0x80
+
+
+def clear_flash(modbus, slave_address):
+    # type: (Modbus, int) -> bool
+
+    print ('erasing flash...')
+
+    write_response = modbus.write_registers(address=0x2084, values=[1], slave=slave_address)
+
+    if failed(write_response):
+        print('erasing flash FAILED')
+        return False
+
+    flash_countdown = 17
+    while flash_countdown > 0:
+        read_response = modbus.read_holding_registers(address=0x2085, count=1, slave=slave_address)
+
+        if failed(read_response):
+            print('erasing flash FAILED')
+            return False
+
+        if read_response.registers[0] != flash_countdown:
+            flash_countdown = read_response.registers[0]
+
+            msg = str(100 * (16 - flash_countdown) / 16) + '%'
+            print('\r{0} '.format(msg), end=' ')
+
+    print('done!')
+
+    return True
+
+
+# noinspection PyShadowingBuiltins
+def bytes_to_words(bytes):
+    # type: (str) -> List[int]
+    return list(struct.unpack('>' + int(len(bytes)/2) * 'H', bytes))
+
+
+def send_half_page_1(modbus, slave_address, data, page):
+    # type: (Modbus, int, str, int) -> NoReturn
+
+    first_half = [page] + bytes_to_words(data[:HALF_PAGE])
+    write_first_half = modbus.write_registers(0x2000, first_half, slave=slave_address)
+
+    if failed(write_first_half):
+        raise Exception("Failed to write page " + str(page))
+
+
+def send_half_page_2(modbus, slave_address, data, page):
+    # type: (Modbus, int, str, int) -> NoReturn
+
+    registers = bytes_to_words(data[HALF_PAGE:]) + calc_crc(page, data) + WRITE_ENABLE
+    result = modbus.write_registers(0x2041, registers, slave=slave_address)
+
+    if failed(result):
+        raise Exception("Failed to write page " + str(page))
+
+
+def get_fw_name(fw_path):
+    # type: (str) -> str
+    return fw_path.split('/')[-1].split('.')[0]
+
+
+def upload_fw(modbus, slave_id, fw_path, fw_name):
+    # type: (Modbus, int, str, str) -> NoReturn
+
+    with open(fw_path, "rb") as f:
+
+        size = os.fstat(f.fileno()).st_size
+        n_pages = int(size / PAGE_SIZE)
+
+        print('uploading firmware ' + fw_name + ' to BMS ...')
+
+        for page in range(0, n_pages):
+            page_data = f.read(PAGE_SIZE)
+
+            msg = "page " + str(page + 1) + '/' + str(n_pages) + ' ' + str(100 * page / n_pages + 1) + '%'
+            print('\r{0} '.format(msg), end=' ')
+
+            if is_page_empty(page_data):
+                continue
+            sleep(0.01)
+            send_half_page_1(modbus, slave_id, page_data, page)
+            sleep(0.01)
+            send_half_page_2(modbus, slave_id, page_data, page)
+
+
+def is_page_empty(page):
+    # type: (str) -> bool
+    return  page.count(b'\xff') == len(page)
+
+
+def reset_bms(modbus, slave_id):
+    # type: (Modbus, int) -> bool
+
+    print ('resetting BMS...')
+
+    result = modbus.write_registers(RESET_REGISTER, [1], slave=slave_id)
+
+    # expecting a ModbusIOException (timeout)
+    # BMS can no longer reply because it is already reset
+    success = isinstance(result, ModbusIOException)
+
+    if success:
+        print('done')
+    else:
+        print('FAILED to reset battery!')
+
+    return success
+
+
+def calc_crc(page, data):
+    # type: (int, str) -> List[int]
+
+    crc = calc_stm32_crc([page] + bytes_to_words(data))
+    crc_bytes = struct.pack('>L', crc)
+
+    return bytes_to_words(crc_bytes)
+
+
+def identify_battery(modbus, slave_id):
+    # type: (Modbus, int) -> Optional[str]
+    print("slave id=",slave_id)
+    target = 'battery ' + str(slave_id) + ' at ' + '502'
+
+    try:
+
+        print(('contacting  ...'))
+
+        response = modbus.read_input_registers(address=FIRMWARE_VERSION_REGISTER, count=1, slave=slave_id)
+        fw = '{0:0>4X}'.format(response.registers[0])
+
+        print(('found battery with firmware ' + fw))
+
+        return fw
+
+    except:
+        print(('failed to communicate with  '))
+        return None
+
+
+def print_usage():
+    print(('Usage:   ' + __file__ + ' <serial device> <battery id> <firmware>'))
+    print(('Example: ' + __file__ + ' ttyUSB0 2 A08C.bin'))
+
+
+def parse_cmdline_args(argv):
+    # type: (List[str]) -> (str, str, str, str)
+
+    def fail_with(msg):
+        print(msg)
+        print_usage()
+        exit(1)
+
+    if len(argv) < 1:
+        fail_with('missing argument for tty device')
+
+    if len(argv) < 2:
+        fail_with('missing argument for battery ID')
+
+    if len(argv) < 3:
+        fail_with('missing argument for firmware')
+
+    return argv[0], int(argv[1]), argv[2], get_fw_name(argv[2])
+
+
+def verify_firmware(modbus, battery_id, fw_name):
+    # type: (Modbus, int, str) -> NoReturn
+
+    fw_verify = identify_battery(modbus, battery_id)
+
+    if fw_verify == fw_name:
+        print('SUCCESS')
+    else:
+        print('FAILED to verify uploaded firmware!')
+        if fw_verify is not None:
+            print('expected firmware version ' + fw_name + ' but got ' + fw_verify)
+
+
+def wait_for_bms_reboot():
+    # type: () -> NoReturn
+
+    # wait 20s for the battery to reboot
+
+    print('waiting for BMS to reboot...')
+
+    for t in range(20, 0, -1):
+        print('\r{0} '.format(t), end=' ')
+        sleep(1)
+
+    print('0')
+
+
+def main(argv):
+    # type: (List[str]) -> NoReturn
+
+    tty, battery_id, fw_path, fw_name = parse_cmdline_args(argv)
+    with  init_modbus(tty) as modbus:
+
+        if identify_battery(modbus, battery_id) is None:
+            return
+
+        clear_flash(modbus, battery_id)
+        upload_fw(modbus, battery_id, fw_path, fw_name)
+
+        if not reset_bms(modbus, battery_id):
+            return
+
+        wait_for_bms_reboot()
+
+        verify_firmware(modbus, battery_id, fw_name)
+
+
+main(argv[1:])
diff --git a/csharp/App_backup/SchneiderMeterDriver/Config.cs b/csharp/App_backup/SchneiderMeterDriver/Config.cs
new file mode 100644
index 000000000..27cdcfeda
--- /dev/null
+++ b/csharp/App_backup/SchneiderMeterDriver/Config.cs
@@ -0,0 +1,66 @@
+using System.Reflection;
+using InnovEnergy.Lib.Victron.VeDBus;
+
+namespace InnovEnergy.App.SchneiderDriver;
+
+public static class Config
+{
+    public const String  Version = "1.0";
+    public const String  BusName = "com.victronenergy.grid.Schneider";
+    public const Byte    ModbusNodeId = 1;
+    public const String  OwnAddress  = "192.168.3.147";
+    public const String  PeerAddress = "192.168.3.63";
+    //public const String  PeerAddress = "127.0.0.1";
+    public const UInt16  PeerPort = 502;
+
+    public static TimeSpan TcpTimeout { get; } = TimeSpan.FromSeconds(2);
+
+    private const UInt16 FactorKwToW = 1000;
+
+    public static readonly TimeSpan UpdatePeriod = TimeSpan.FromSeconds(1);
+    
+    public static readonly IReadOnlyList<Signal> Signals = new List<Signal>
+    { 
+       new Signal(s => s.CurrentL1, "/Ac/L1/Current", "0.0 A"),
+       new Signal(s => s.CurrentL2, "/Ac/L2/Current", "0.0 A"),
+       new Signal(s => s.CurrentL3, "/Ac/L3/Current", "0.0 A"),
+       new Signal(s => s.CurrentL1 + s.CurrentL2 + s.CurrentL3, "/Ac/Current", "0.0 A"),
+
+       new Signal(s => s.VoltageL1N, "/Ac/L1/Voltage", "0.0 V"),
+       new Signal(s => s.VoltageL2N, "/Ac/L2/Voltage", "0.0 V"),
+       new Signal(s => s.VoltageL3N, "/Ac/L3/Voltage", "0.0 V"),
+       new Signal(s => (s.VoltageL1N + s.VoltageL2N + s.VoltageL3N) / 3.0f, "/Ac/Voltage", "0.0 V"),
+
+       new Signal(s => s.ActivePowerL1    * FactorKwToW, "/Ac/L1/Power", "0 W"),
+       new Signal(s => s.ActivePowerL2    * FactorKwToW, "/Ac/L2/Power", "0 W"),
+       new Signal(s => s.ActivePowerL3    * FactorKwToW, "/Ac/L3/Power", "0 W"),
+       new Signal(s => s.TotalActivePower * FactorKwToW, "/Ac/Power", "0 W"), 
+       
+       
+       new Signal(s => s.TotalActiveImport, "Ac/Energy/Forward", "0.00 kWh"),
+       new Signal(s => s.TotalActiveExport, "Ac/Energy/Reverse", "0.00 kWh"),
+       new Signal(s => s.ActiveEnergyImportL1, "Ac/L1/Energy/Forward", "0.00 kWh"),
+       // new(s => s.EnergyExportL1, "Ac/L1/Energy/Reverse", "0.00 kWh"),
+       //
+       new Signal(s => s.ActiveEnergyImportL2, "Ac/L2/Energy/Forward", "0.00 kWh"),
+       // new(s => s.EnergyExportL2, "Ac/L2/Energy/Reverse", "0.00 kWh"),
+       //
+       new Signal(s => s.ActiveEnergyImportL3, "Ac/L3/Energy/Forward", "0.00 kWh"),
+       // new(s => s.EnergyExportL3, "Ac/L3/Energy/Reverse", "0.00 kWh"),
+    };
+    
+    public static VeProperties DefaultProperties => new VeProperties
+    {
+        new("/ProductName"        , "Grid meter" ),
+        new("/CustomName"         , "Schneider Professional"),
+        new("/DeviceInstance"     , 50),
+        new("/DeviceType"         , 73),
+        new("/Mgmt/Connection"    , "Modbus TCP"),
+        new("/Mgmt/ProcessName"   , Assembly.GetEntryAssembly()?.Location ?? "unknown"),
+        new("/Mgmt/ProcessVersion", Version),
+        new("/Connected"          , 1),
+        new("/ProductId"          , 45139, "b002"),
+        new("/Role"               , "grid"),
+    };
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SchneiderMeterDriver/Nic.cs b/csharp/App_backup/SchneiderMeterDriver/Nic.cs
new file mode 100644
index 000000000..a580f6fd3
--- /dev/null
+++ b/csharp/App_backup/SchneiderMeterDriver/Nic.cs
@@ -0,0 +1,149 @@
+using System.Text.Json.Nodes;
+using CliWrap;
+using CliWrap.Buffered;
+
+namespace InnovEnergy.App.SchneiderDriver;
+
+public readonly struct Nic
+{
+    private static Command IpCommand { get; } = Cli
+                                               .Wrap("/sbin/ip")
+                                               .WithValidation(CommandResultValidation.None);
+    
+    private readonly JsonNode _Node;
+
+    private Nic(JsonNode node)
+    {
+        _Node = node;
+    }
+
+    public Boolean IsEthernet
+    {
+        get
+        {
+            try
+            {
+                return _Node["link_type"]!.GetValue<String>() == "ether";
+            }
+            catch
+            {
+                return false;
+            }
+        }
+    }
+
+    public Boolean IsUp
+    {
+        get
+        {
+            // ReSharper disable once StringLiteralTypo
+            try
+            {
+                return _Node["operstate"]!.GetValue<String>() == "UP";
+            }
+            catch
+            {
+                return false;
+            }
+        }
+    }
+
+    public IReadOnlyList<String> Ip4Addresses
+    {
+        get
+        {
+            // ReSharper disable once StringLiteralTypo
+            try
+            {
+                return _Node["addr_info"]!
+                       .AsArray()
+                       .TryWhere(n => n!["family"]!.GetValue<String>() == "inet")
+                       .TrySelect(n => n!["local"]!.GetValue<String>())
+                       .ToList();
+            }
+            catch
+            {
+                return Array.Empty<String>();
+            }
+        }
+    }
+
+    public String Name
+    {
+        get
+        {
+            // ReSharper disable once StringLiteralTypo
+            try
+            {
+                return _Node["ifname"]!.GetValue<String>();
+            }
+            catch
+            {
+                return "<NO_NAME>";
+            }
+        }
+    }
+    
+  
+
+    public async Task<Boolean> AddPointToPoint(String sourceAddress, String destinationAddress)
+    {
+        var result = await IpCommand
+                          .WithArguments($"address add local {sourceAddress} peer {destinationAddress} dev {Name}")
+                          .ExecuteAsync();
+
+        return result.ExitCode == 0;
+    }
+    
+    public async Task<Boolean> RemoveAddress(String address)
+    {
+        var result = await IpCommand
+                          .WithArguments($"address del {address} dev {Name}")
+                          .ExecuteBufferedAsync();
+
+        return result.ExitCode == 0;
+    }
+
+
+    public  async Task<Boolean> AddRoute(String route)
+    {
+        var result = await IpCommand
+                          .WithArguments($"route add {route} dev {Name}")
+                          .ExecuteAsync();
+
+        return result.ExitCode == 0;
+    }
+
+    public async Task<Boolean> RemoveRoute(String route)
+    {
+        var result = await IpCommand
+                          .WithArguments($"route del {route} dev {Name}")
+                          .ExecuteAsync();
+
+        return result.ExitCode == 0;
+    }
+
+  
+    public static async Task<IReadOnlyList<Nic>> GetNetworkInterfaces()
+    {
+
+        try
+        {
+             var result = await IpCommand
+                               .WithArguments("-details -pretty -json address")
+                               .ExecuteBufferedAsync();
+
+             return JsonNode
+                   .Parse(result.StandardOutput)!
+                   .AsArray()
+                   .Where(n => n != null)
+                   .Select(n => new Nic(n!))
+                   .ToList();
+        }
+        catch 
+        {
+            return Array.Empty<Nic>();
+        }
+    }
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SchneiderMeterDriver/Program.cs b/csharp/App_backup/SchneiderMeterDriver/Program.cs
new file mode 100644
index 000000000..404068d21
--- /dev/null
+++ b/csharp/App_backup/SchneiderMeterDriver/Program.cs
@@ -0,0 +1,79 @@
+using InnovEnergy.App.SchneiderDriver;
+using InnovEnergy.Lib.Protocols.DBus;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Utils.Net;
+
+
+// dotnet publish EmuMeter.csproj -c Release -r linux-arm -p:PublishSingleFile=true --self-contained true && \
+// rsync -av bin/Release/net6.0/linux-arm/publish/ root@10.2.1.6:/home/root/emu && clear && \
+// ssh root@10.2.1.6 /home/root/emu/EmuMeter
+
+
+Console.WriteLine("Starting Schneider Driver " + Config.Version);
+
+var networkInterfaces = await Nic.GetNetworkInterfaces();
+
+Console.WriteLine("Retrieved network interfaces");
+
+// Log all network interfaces and their properties
+foreach (var nic in networkInterfaces)
+{
+    Console.WriteLine($"Interface: {nic.Name}");
+    Console.WriteLine($"  IsUp: {nic.IsUp}");
+    Console.WriteLine($"  IsEthernet: {nic.IsEthernet}");
+    Console.WriteLine($"  IP4 Addresses: {string.Join(", ", nic.Ip4Addresses)}");
+}
+
+var candidates = networkInterfaces.Where(n => n.IsUp && 
+                                              n.IsEthernet && 
+                                              (!n.Ip4Addresses.Any() || n.Ip4Addresses.Contains(Config.OwnAddress)));
+
+if (!candidates.Any())
+{
+    Console.WriteLine("No suitable network interfaces found.");
+}
+
+foreach (var nic in candidates)
+{
+    Console.WriteLine($"Found new network interface: {nic.Name}");
+    
+    if (!nic.Ip4Addresses.Contains(Config.OwnAddress))
+    {
+        Console.WriteLine($"Configuring Point-to-Point connection on {nic.Name}");
+        Console.WriteLine($"  own address:  {Config.OwnAddress}");
+        Console.WriteLine($"  peer address: {Config.PeerAddress}");
+        
+        var success = await nic.AddPointToPoint($"{Config.OwnAddress}/16", $"{Config.PeerAddress}/16");
+
+        if (!success)
+        {
+            Console.WriteLine($"Failed to configure network interface: {nic.Name}");
+            continue;
+        }    
+    }
+
+    Console.WriteLine($"Pinging peer @ {Config.PeerAddress}");
+    
+    var ping = await Config.PeerAddress.Ping();
+
+    if (ping)
+    {
+        Console.WriteLine($"Got answer from {Config.PeerAddress}");
+        //SchneiderMeterDriver.Run(Config.PeerAddress, Config.PeerPort, Bus.System);
+
+        //Console.WriteLine($"{nameof(SchneiderMeterDriver)} FAILED with\n");
+        var ex = await SchneiderMeterDriver.Run(Config.PeerAddress, Config.PeerPort, Bus.System);
+
+        Console.WriteLine($"{nameof(SchneiderMeterDriver)} FAILED with\n{ex}");
+    }
+    else
+    {
+        Console.WriteLine($"No answer from {Config.PeerAddress}");
+    }
+
+    Console.Write($"Removing Point-to-Point connection on {nic.Name} ...");
+    var removed = await nic.RemoveAddress($"{Config.OwnAddress}/16");
+    Console.WriteLine(removed ? "done" : "failed");
+}
+
+Console.WriteLine("Stopping SchneiderMeter Driver");
\ No newline at end of file
diff --git a/csharp/App_backup/SchneiderMeterDriver/SchneiderMeterDriver.cs b/csharp/App_backup/SchneiderMeterDriver/SchneiderMeterDriver.cs
new file mode 100644
index 000000000..29de8e36d
--- /dev/null
+++ b/csharp/App_backup/SchneiderMeterDriver/SchneiderMeterDriver.cs
@@ -0,0 +1,91 @@
+using System.Reactive.Linq;
+using InnovEnergy.Lib.Devices.IEM3kGridMeter;
+using InnovEnergy.Lib.Protocols.DBus;
+using InnovEnergy.Lib.Protocols.Modbus.Clients;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Victron.VeDBus;
+
+namespace InnovEnergy.App.SchneiderDriver;
+
+public static class SchneiderMeterDriver
+{
+    
+    public static Task<Exception> Run(String hostName, Bus dbusAddress)
+    {
+        return Run(hostName, ModbusTcpClient.DefaultPort, dbusAddress);
+    }
+    
+    public static async Task<Exception> Run(String hostName, UInt16 port, Bus dbusAddress)
+    {
+        // var ep = new UnixDomainSocketEndPoint("/home/eef/graber_dbus.sock");
+        // var auth = AuthenticationMethod.ExternalAsRoot();
+        // dbusAddress = new Bus(ep, auth);
+
+        var schneider = new Iem3KGridMeterDevice(hostName, port, Config.ModbusNodeId);
+
+       
+        var schneiderStatus = Observable
+                             .Interval(Config.UpdatePeriod)
+                             .Select(_ => schneider.Read())
+                             .Where(reading => reading != null)
+                             .Publish();
+
+        var poller = schneiderStatus.Connect();
+
+        var properties = Config.DefaultProperties;
+            
+        var signals = Config
+            .Signals
+            .Select(signal => schneiderStatus
+                .Select(reading =>
+                {
+                    var property = signal.ToVeProperty(reading);
+                    if (property == null)
+                    {
+                       // Console.WriteLine($"Warning: Signal {signal} produced a null property.");
+                    }
+                    else
+                    {
+                        //Console.WriteLine($"Transformed Signal to Property: {property}");
+                    }
+                    return property;
+                })
+                .Where(property => property != null))
+                .Merge()
+                .Do(p =>
+                {
+                  //  Console.WriteLine($"Setting property: {p}");
+                    properties.Set(p);
+                });
+        
+             // TODO: remove when possible
+        // Apparently some VE services need to be periodically reminded that
+        // this service is /Connected
+        Console.WriteLine("Goes to subscribe");
+        schneiderStatus.Subscribe(_ => properties.Set("/Connected", 1));
+        Console.WriteLine("Subscribed successfully");
+        // Wait until status is read once to make sure all
+        // properties are set when we go onto the bus.
+        
+        var dbus = schneiderStatus
+                   .Skip(1)
+                   .Take(1)
+                   .SelectMany(_ => PublishPropertiesOnDBus(properties, dbusAddress));
+
+         return await signals
+                      .MergeErrors(dbus)
+                      .Finally(poller.Dispose)
+                      .SelectErrors();
+    }
+
+    
+    private static Task<Exception> PublishPropertiesOnDBus(VeProperties properties, Bus bus)
+    {
+        Console.WriteLine($"Connecting to DBus {bus}");
+        return properties.PublishOnDBus(bus, Config.BusName);
+    }
+}
+
+
+
+
diff --git a/csharp/App_backup/SchneiderMeterDriver/SchneiderMeterDriver.csproj b/csharp/App_backup/SchneiderMeterDriver/SchneiderMeterDriver.csproj
new file mode 100644
index 000000000..ac5b87bcd
--- /dev/null
+++ b/csharp/App_backup/SchneiderMeterDriver/SchneiderMeterDriver.csproj
@@ -0,0 +1,26 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+    
+    <PropertyGroup>
+        <RootNamespace>InnovEnergy.App.SchneiderDriver</RootNamespace>
+        <AssemblyName>SchniederDriver</AssemblyName>
+    </PropertyGroup>
+    
+    <Import Project="../InnovEnergy.App.props" />
+
+    <ItemGroup>
+      <ProjectReference Include="../../Lib/Devices/EmuMeter/EmuMeter.csproj" />
+      <ProjectReference Include="../../Lib/Protocols/DBus/DBus.csproj" />
+      <ProjectReference Include="../../Lib/Protocols/Modbus/Modbus.csproj" />
+      <ProjectReference Include="../../Lib/Utils/Utils.csproj" />
+      <ProjectReference Include="../../Lib/Victron/VeDBus/VeDBus.csproj" />
+      <ProjectReference Include="..\..\Lib\Devices\IEM3kGridMeter\IEM3kGridMeter.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <PackageReference Include="CliWrap" Version="3.6.0" />
+    </ItemGroup>
+    
+    
+
+</Project>
diff --git a/csharp/App_backup/SchneiderMeterDriver/Signal.cs b/csharp/App_backup/SchneiderMeterDriver/Signal.cs
new file mode 100644
index 000000000..b1a0d55ef
--- /dev/null
+++ b/csharp/App_backup/SchneiderMeterDriver/Signal.cs
@@ -0,0 +1,42 @@
+
+using InnovEnergy.Lib.Devices.IEM3kGridMeter;
+using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes;
+using InnovEnergy.Lib.Victron.VeDBus;
+
+namespace InnovEnergy.App.SchneiderDriver
+{
+    public record Signal(Func<Iem3KGridMeterRegisters, Object> Source, ObjectPath Path, String Format = "")
+    {
+        // Converts the status object to a VeProperty, handling null status gracefully
+        public VeProperty ToVeProperty(Iem3KGridMeterRegisters status)
+        {
+            // Check if status is null and log a message
+           // if (status == null)
+           // {
+           //     Console.WriteLine($"Status is null for path: {Path}");
+           //     // Return a default VeProperty if status is null
+           //     return new VeProperty(Path, default(double), string.Format($"{{0:{Format}}}", default(double)));
+           // }
+//
+           // // Retrieve the value using the provided source function
+           var value = Source(status);
+           // 
+           // // Handle the case where the value itself might be null
+           // if (value == null)
+           // {
+           //     Console.WriteLine($"Value is null for path: {Path}");
+           //     // Return a default VeProperty if value is null
+           //     return new VeProperty(Path, default(double), string.Format($"{{0:{Format}}}", default(double)));
+           // }
+
+           if (value is Single floatValue)
+           {
+               value = (Double)floatValue;
+           }
+
+            // Create and return the VeProperty with the actual value and format
+            return new VeProperty(Path, value, string.Format($"{{0:{Format}}}", value));
+        }
+    }
+}
+
diff --git a/csharp/App_backup/SchneiderMeterDriver/Utils.cs b/csharp/App_backup/SchneiderMeterDriver/Utils.cs
new file mode 100644
index 000000000..4b5735a35
--- /dev/null
+++ b/csharp/App_backup/SchneiderMeterDriver/Utils.cs
@@ -0,0 +1,49 @@
+namespace InnovEnergy.App.SchneiderDriver;
+
+public static class Utils
+{
+    public static IEnumerable<T> TryWhere<T>(this IEnumerable<T> src, Func<T, Boolean> predicate)
+    {
+        foreach (var e in src)
+        {
+            var ok = false;
+            
+            try
+            {
+                ok = predicate(e);
+            }
+            catch
+            {
+                // ignored
+            }
+
+            if (ok)
+                yield return e;
+        }
+    }
+    
+    public static IEnumerable<R> TrySelect<T,R>(this IEnumerable<T> src, Func<T, R> map)
+    {
+        foreach (var e in src)
+        {
+            var ok = false;
+            var result = default(R);
+            
+            try
+            {
+                result = map(e);
+                ok = true;
+            }
+            catch
+            {
+                // ignored
+            }
+
+            if (ok)
+                yield return result!;
+        }
+    }
+    
+
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SchneiderMeterDriver/debug.sh b/csharp/App_backup/SchneiderMeterDriver/debug.sh
new file mode 100755
index 000000000..9f7657b01
--- /dev/null
+++ b/csharp/App_backup/SchneiderMeterDriver/debug.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+
+csproj="SchneiderMeterDriver.csproj"
+exe="SchneiderMeterDriver"
+#remote="10.2.1.6"
+remote="10.2.4.155"
+platform="linux-arm"
+netVersion="net6.0"
+config="Release"
+host="root@$remote"
+dir="/opt/victronenergy/$exe"
+
+set -e
+
+# Detect the current platform
+#if uname -m | grep -i 'arm' > /dev/null; then
+    
+#else
+ #   platform="linux-x64"
+#fi
+
+dotnet publish "$csproj" -c $config -r $platform -p:SuppressTrimmAnalysisWarnings=true -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false --self-contained true 
+rsync -av "bin/$config/$netVersion/$platform/publish/" "$host:$dir" 
+#clear 
+#ssh "$host" "$dir/$exe"
+  
\ No newline at end of file
diff --git a/csharp/App_backup/SchneiderMeterDriver/deploy.sh b/csharp/App_backup/SchneiderMeterDriver/deploy.sh
new file mode 100755
index 000000000..eacfec15f
--- /dev/null
+++ b/csharp/App_backup/SchneiderMeterDriver/deploy.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+
+
+csproj="SchneiderMeterDriver.csproj"
+exe="SchneiderMeterDriver"
+remote="10.2.4.114"
+platform="linux-arm"
+netVersion="net6.0"
+config="Release"
+host="root@$remote"
+dir="/opt/innovenergy/$exe"
+log_dir="/var/log/SchneiderMeterDriver"
+
+set -e
+
+# Publish the project locally
+dotnet publish "$csproj" -c $config -r $platform -p:SuppressTrimmAnalysisWarnings=true -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false --self-contained true
+
+# Sync the published files to the remote server
+rsync -av "bin/$config/$netVersion/$platform/publish/" "$host:$dir"
+
+# Execute commands on the remote server
+ssh "$host" << 'EOF'
+set -e
+
+# Remount the root filesystem with read and write permissions
+mount -o remount,rw /
+
+# Create service and log directories
+mkdir -p /opt/innovenergy/SchneiderMeterDriver/service
+mkdir -p /opt/innovenergy/SchneiderMeterDriver/service/log
+mkdir -p /var/log/SchneiderMeterDriver
+
+# Create the service run script
+cat << 'EOL' > /opt/innovenergy/SchneiderMeterDriver/service/run
+#!/bin/sh
+exec 2>&1
+exec softlimit -d 200000000 -s 2000000 -a 200000000 /opt/innovenergy/SchneiderMeterDriver/SchniederDriver
+EOL
+chmod +x /opt/innovenergy/SchneiderMeterDriver/service/run
+
+# Create the log run script
+cat << 'EOL' > /opt/innovenergy/SchneiderMeterDriver/service/log/run
+#!/bin/sh
+exec 2>&1
+exec multilog t s25000 n4 /var/log/SchneiderMeterDriver
+EOL
+chmod +x /opt/innovenergy/SchneiderMeterDriver/service/log/run
+
+# Create the symbolic link for the service
+ln -sf /opt/innovenergy/SchneiderMeterDriver/service /service/SchneiderMeterDriver
+
+# Wait a bit for the symlink to be recognized
+
+sleep 2
+
+# Start the service
+start SchneiderMeterDriver
+EOF
+
+echo "Deployment and service setup completed successfully."
diff --git a/csharp/App_backup/SodiStoreMax/Doc/AllStates.graphml b/csharp/App_backup/SodiStoreMax/Doc/AllStates.graphml
new file mode 100644
index 000000000..5877679af
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/Doc/AllStates.graphml
@@ -0,0 +1,934 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
+  <!--Created by yEd 3.22-->
+  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
+  <key for="port" id="d1" yfiles.type="portgraphics"/>
+  <key for="port" id="d2" yfiles.type="portgeometry"/>
+  <key for="port" id="d3" yfiles.type="portuserdata"/>
+  <key attr.name="url" attr.type="string" for="node" id="d4"/>
+  <key attr.name="description" attr.type="string" for="node" id="d5"/>
+  <key for="node" id="d6" yfiles.type="nodegraphics"/>
+  <key for="graphml" id="d7" yfiles.type="resources"/>
+  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
+  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
+  <graph edgedefault="directed" id="G">
+    <data key="d0" xml:space="preserve"/>
+    <node id="n0">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="21.541219630823434" y="771.0152962325975"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="3.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">23</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="123.90064835074503" y="608.1111986375686"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">19</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="286.80474594577396" y="505.75176991764715"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">3</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="659.5852254317465" y="547.754042478705"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">9</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="477.9883579995691" y="484.21055028682383"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">1</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n5">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="795.6278615079401" y="683.7966785548988"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">13</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n6">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="859.171353699821" y="865.3935459870761"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">29</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n7">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="393.7937432135768" y="243.91085524521077"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">5</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n8">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="241.37121964689652" y="243.91085524521077"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">7</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n9">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="913.7233840476495" y="390.20661887776083"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">11</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n10">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="1077.2454208201314" y="623.2576236989755"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">15</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n11">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="393.7937432135768" y="32.482490272373525"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">21</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n12">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="546.2162667802571" y="243.91085524521077"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">17</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n13">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="913.7233840476495" y="243.91085524521077"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">25</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n14">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="966.0692273802504" y="543.8131759748794"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">27</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n15">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="1077.853337442888" y="743.4321654278978"/>
+          <y:Fill color="#FFEB9C" color2="#FF0000" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">31</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n16">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="837.6301340689974" y="1056.5771580408712"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="3.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">28</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n17">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="735.2707053490758" y="1219.4812556359"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">24</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n18">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="572.3666077540471" y="1321.8406843558214"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">8</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n19">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="63.54349219188089" y="1143.79577571857"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">6</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n20">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="381.1829957002519" y="1343.3819039866448"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">0</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n21">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="199.58612826807462" y="1279.8384117947637"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">4</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n22">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="0.0" y="962.1989082863927"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">22</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n23">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="381.1829957002519" y="1666.836774125534"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">16</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n24">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="20.81460729275682" y="1564.3516575638562"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">20</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n25">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="247.63957015025017" y="1827.5924542734685"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">18</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n26">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="247.63957015025017" y="1666.836774125534"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">2</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n27">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="735.2707053490758" y="1666.836774125534"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">10</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n28">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="572.3666077540471" y="1666.836774125534"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">12</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n29">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="572.3666077540471" y="1827.5924542734685"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">14</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n30">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="878.9380146340279" y="1661.335945415411"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">26</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n31">
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="572.3666077540471" y="1988.348134421403"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">30</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.519195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <edge id="e0" source="n6" target="n5">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="side_slider" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="-7.6066190457218" xml:space="preserve" y="-64.14237361463267">turn off
+Inverters<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n3" target="n4">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-22.56775183821742" anchorY="-5.777885637190593" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="-0.33027906195516804" upY="0.9438833303083672" verticalTextPosition="bottom" visible="true" width="57.61639404296875" x="-89.06825945702936" xml:space="preserve" y="-24.807374214941643">switch to 
+grid tie<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n5" target="n3">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-10.834918063373834" anchorY="-3.763856446737236" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="-0.7071067811865482" upY="0.7071067811865468" verticalTextPosition="bottom" visible="true" width="58.68438720703125" x="-66.71650738854461" xml:space="preserve" y="-45.25998459060615">K3's open<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="5.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n4" target="n2">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-36.90577949345038" anchorY="-18.32720550204249" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.11196447610330729" upY="0.9937122098932426" verticalTextPosition="bottom" visible="true" width="50.044342041015625" x="-86.63545321568132" xml:space="preserve" y="-18.32720550204249">close K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n2" target="n1">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-34.073280050695104" anchorY="-24.282018553049284" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.5320320765153357" upY="0.8467241992282847" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="-80.73486874846864" xml:space="preserve" y="-24.282018553049284">turn on 
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e5" source="n1" target="n0">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-28.71096311323771" anchorY="3.695546315632555" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.8467241992282841" upY="0.5320320765153367" verticalTextPosition="bottom" visible="true" width="58.492401123046875" x="-59.83079674310035" xml:space="preserve" y="3.695546315632555">K3's close<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e6" source="n9" target="n3">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="21.046829314938122" ty="-45.01680098318991">
+            <y:Point x="874.0493776621389" y="435.20661887776083"/>
+            <y:Point x="715.6320547466846" y="519.7002048682145"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-95.81164490935862" anchorY="32.19070766070081" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" upX="0.47060708552926384" upY="0.8823428874590944" verticalTextPosition="bottom" visible="true" width="50.236328125" x="-140.13731172251363" xml:space="preserve" y="32.19070766070081">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5000000000000019" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e7" source="n10" target="n5">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-83.35912704662064" anchorY="-4.934957639868799" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.21016777322347138" upY="0.977665334916958" verticalTextPosition="bottom" visible="true" width="50.236328125" x="-132.47344360794696" xml:space="preserve" y="-4.934957639868799">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e8" source="n12" target="n4">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="15.152106842485068" ty="-45.029446684529034">
+            <y:Point x="581.2162667802571" y="365.51788910504735"/>
+            <y:Point x="528.1404648420541" y="447.09481487826343"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-11.667059533747079" anchorY="53.20739194515636" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.8382050245156525" upY="0.5453552391576654" verticalTextPosition="bottom" visible="true" width="48.38832092285156" x="-38.055883863066654" xml:space="preserve" y="53.20739194515636">turn off
+inverter<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5000000000000003" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e9" source="n15" target="n5">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="7.654500781281627" ty="24.1090580512913"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="side_slider" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="128.14088439941406" x="-170.15796772429303" xml:space="preserve" y="-60.704752015977874">turn off
+inverters and open K2<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e10" source="n11" target="n7">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-38.68821708305404" anchorY="33.66069253418277" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="1.0" upY="1.8369701987210297E-16" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="-38.68821708305405" xml:space="preserve" y="33.66069253418277">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e11" source="n14" target="n3">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="3.644099504220094" ty="8.35227299621522"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-54.95981067303774" anchorY="4.232641597982592" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.0405594629413826" upY="0.999177126422491" verticalTextPosition="bottom" visible="true" width="128.14088439941406" x="-182.99525132448088" xml:space="preserve" y="4.232641597982592">turn off
+inverters and open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5000000000000013" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e12" source="n13" target="n3">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
+            <y:Point x="875.759476311287" y="288.91085524521077"/>
+            <y:Point x="694.5852254317465" y="500.95688054473624"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-140.36047493803812" anchorY="60.311953252495755" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.7602822957199457" upY="0.6495928192451093" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="-175.52707071953466" xml:space="preserve" y="60.311953252495755">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5000000000000003" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e13" source="n17" target="n16">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" anchorX="28.71097587067584" anchorY="-3.6954992687265076" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.8467241992282839" upY="-0.532032076515337" verticalTextPosition="bottom" visible="true" width="58.492401123046875" x="11.48512197796088" xml:space="preserve" y="-64.0461521483817">K3's close<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e14" source="n23" target="n20">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="38.68824090899216" anchorY="-89.20073479644952" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-1.0" upY="-6.123233995736766E-17" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="2.0000123018144222" xml:space="preserve" y="-144.3091027163714">turn off 
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.4999999999999987" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e15" source="n20" target="n18">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="26.307533067371537" anchorY="35.968859753049855" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.11196447610330768" upY="-0.9937122098932426" verticalTextPosition="bottom" visible="true" width="73.21649169921875" x="22.199754772210497" xml:space="preserve" y="-8.686327108482413">switch to 
+island mode<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5000000000000004" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e16" source="n18" target="n17">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="34.27900103801858" anchorY="24.15265971881331" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.5320320765153352" upY="-0.846724199228285" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="14.759686588472483" xml:space="preserve" y="-35.714435444192404">turn on 
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e17" source="n22" target="n19">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" anchorX="-31.943410160379326" anchorY="25.84898726732399" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.9438833303083679" upY="-0.33027906195516604" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="-31.943410160379326" xml:space="preserve" y="13.73163353814864">turn off
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e18" source="n21" target="n20">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="26.036995974329216" anchorY="6.991824229345639" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.33027906195516815" upY="-0.9438833303083672" verticalTextPosition="bottom" visible="true" width="55.984375" x="26.036995974329216" xml:space="preserve" y="-12.21064613169991">K3 opens<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e19" source="n19" target="n21">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="4.486562103588653" anchorY="36.08589104047951" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.7071067811865477" upY="-0.7071067811865474" verticalTextPosition="bottom" visible="true" width="50.236328125" x="4.486562103588653" xml:space="preserve" y="21.70042985917759">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e20" source="n25" target="n20">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="-12.24576280270594" ty="10.36548331324957">
+            <y:Point x="118.40975029349039" y="1695.1176856990405"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-68.85844058790809" anchorY="-205.23460946299792" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.7201576722416737" upY="-0.6938104403303932" verticalTextPosition="bottom" visible="true" width="130.25289916992188" x="-95.2797499003236" xml:space="preserve" y="-324.4919101768264">turn off
+inverters and Open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5000000000000001" segment="-1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e21" source="n26" target="n20">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="-13.677119243026084" ty="44.9895375252479">
+            <y:Point x="282.63957015025017" y="1624.6814853696424"/>
+            <y:Point x="402.50587645722584" y="1481.6545842023272"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="42.26630925296365" anchorY="-95.71994547025815" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.7664335365409145" upY="-0.6423236209772972" verticalTextPosition="bottom" visible="true" width="50.236328125" x="26.67389777947143" xml:space="preserve" y="-147.29025726298894">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.4999999999999997" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e22" source="n27" target="n18">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="19.39352371895791" ty="45.01703178967159">
+            <y:Point x="770.2707053490758" y="1624.8477957198368"/>
+            <y:Point x="626.760131473005" y="1451.0534797665293"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-38.53255297975011" anchorY="-123.76381877365907" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.7710894922153189" upY="0.6367267820620722" verticalTextPosition="bottom" visible="true" width="50.236328125" x="-86.20650129732047" xml:space="preserve" y="-162.50052351832747">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.500000000000001" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e23" source="n28" target="n18">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-2.000030429546655" anchorY="-99.48622193119718" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-1.0" upY="-6.123233995736766E-17" verticalTextPosition="bottom" visible="true" width="55.984375" x="-22.344144733135522" xml:space="preserve" y="-155.47059693119718">K3 opens<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e24" source="n29" target="n28">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="22.344083874042212" anchorY="-10.243393612998716" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-1.0" upY="-6.123233995736766E-17" verticalTextPosition="bottom" visible="true" width="50.236328125" x="1.9999695704533451" xml:space="preserve" y="-60.479721737998716">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e25" source="n30" target="n18">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="-4.644418613171297" ty="3.8960954734218376">
+            <y:Point x="913.5495752668096" y="1598.4359115261484"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="20.402772986958098" anchorY="-95.7266552045121" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.5909569293856483" upY="0.8067031099550106" verticalTextPosition="bottom" visible="true" width="128.14088439941406" x="-104.65003989273117" xml:space="preserve" y="-171.45239877795115">turn off
+inverters and open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.24999999999999953" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e26" source="n31" target="n29">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="38.68819817763108" anchorY="-8.293308863013408" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-1.0" upY="-6.123233995736766E-17" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="1.9999695704533451" xml:space="preserve" y="-62.42967666086497">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e27" source="n16" target="n6">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="24.92939609351697" anchorY="-21.690106143654248" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.9937122098932425" upY="-0.1119644761033088" verticalTextPosition="bottom" visible="true" width="55.792388916015625" x="4.713201310576952" xml:space="preserve" y="-79.40950232839855">K1 closes<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.4999999999999994" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="anywhere" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e28" source="n0" target="n22">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-24.918635525422495" anchorY="21.594702697511707" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.9937122098932426" upY="0.11196447610330804" verticalTextPosition="bottom" visible="true" width="55.984375" x="-31.18689674226863" xml:space="preserve" y="21.594702697511707">K1 opens<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.4999999999999979" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e29" source="n24" target="n21">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
+            <y:Point x="55.81460729275682" y="1523.820754474699"/>
+            <y:Point x="234.58612826807462" y="1404.8536338521308"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="88.28491738649134" anchorY="-52.831225794193415" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" upX="-0.5540104212935304" upY="-0.832509731533611" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="67.95925639931545" xml:space="preserve" y="-113.36664507338794">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5000000000000027" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e30" source="n7" target="n4">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="-12.103675434707498" ty="-0.8973455153978875">
+            <y:Point x="428.7937432135768" y="368.2394927091691"/>
+            <y:Point x="500.8846825648616" y="448.27634874029746"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="-0.19439353507226542" anchorY="67.52807422348087" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.7430274178867984" upY="-0.669260977697398" verticalTextPosition="bottom" visible="true" width="58.68438720703125" x="-0.19439353507226542" xml:space="preserve" y="53.91255239427337">K3's open<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e31" source="n8" target="n2">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="0.0" sy="0.0" tx="0.0" ty="0.0">
+            <y:Point x="276.3712196468965" y="371.2720580009981"/>
+            <y:Point x="321.80474594577396" y="464.4058541099475"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" anchorX="-10.230074924814858" anchorY="67.35206739565979" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.8987592955167517" upY="-0.43844238928533374" verticalTextPosition="bottom" visible="true" width="58.68438720703125" x="-10.230074924814858" xml:space="preserve" y="58.43234531250035">K3's open<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d7">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/csharp/App_backup/SodiStoreMax/Doc/SalimaxConfigReadme.txt b/csharp/App_backup/SodiStoreMax/Doc/SalimaxConfigReadme.txt
new file mode 100644
index 000000000..559eb6ca8
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/Doc/SalimaxConfigReadme.txt
@@ -0,0 +1,110 @@
+"MinSoc": Number, 0 - 100 this is the minimum State of Charge that the batteries must not go below,
+  "ForceCalibrationCharge": Boolean (true or false), A flag to force a calibration charge,
+  "DisplayIndividualBatteries": Boolean (true or false), To display the indvidual batteries
+  "PConstant": Number 0 - 1,  P value of our controller.
+  "GridSetPoint": Number in Watts, The set point of our controller.
+  "BatterySelfDischargePower": Number, 200,  this a physical measurement of the self discharging power.
+  "HoldSocZone": Number, 1, This is magic number for the soft landing factor.
+  "IslandMode": { // Dc Link Voltage in Island mode
+    "AcDc": {
+      "MaxDcLinkVoltage": Number, 810, Max Dc Link Voltage,
+      "MinDcLinkVoltage": Number, 690, Min Dc Link Voltage,
+      "ReferenceDcLinkVoltage": Number, 750, Reference Dc Link
+    },
+    "DcDc": {
+      "LowerDcLinkVoltage": Number, 50, Lower Dc Link Window ,
+      "ReferenceDcLinkVoltage": 750, reference Dc Link
+      "UpperDcLinkVoltage": Number, 50, Upper Dc Link Window ,
+    }
+  },
+  "GridTie": {// Dc Link Voltage in GrieTie mode
+    "AcDc": {
+      "MaxDcLinkVoltage":Number, 780, Max Dc Link Voltage,
+      "MinDcLinkVoltage": Number, 690, Min Dc Link Voltage,
+      "ReferenceDcLinkVoltage": Number, 750, Reference Dc Link
+    },
+    "DcDc": {
+     "LowerDcLinkVoltage": Number, 20, Lower Dc Link Window ,
+      "ReferenceDcLinkVoltage": 750, reference Dc Link
+      "UpperDcLinkVoltage": Number, 20, Upper Dc Link Window ,
+    }
+  },
+  "MaxBatteryChargingCurrent":Number, 0 - 210, Max Charging current by DcDc
+  "MaxBatteryDischargingCurrent":Number, 0 - 210, Max Discharging current by DcDc
+  "MaxDcPower": Number, 0 - 10000, Max Power exported/imported by DcDc (10000 is the maximum)
+  "MaxChargeBatteryVoltage": Number, 57, Max Charging battery Voltage
+  "MinDischargeBatteryVoltage": Number, 0, Min Charging Battery Voltage
+  "Devices": { This is All Salimax devices (including offline ones)
+    "RelaysIp": {
+      "DeviceState": 1, // 0: is not present, 1: Present and Can be mesured, 2: Present but must be computed/calculted
+      "Host": "10.0.1.1", // Ip @ of the device in the local network
+      "Port": 502   // port
+    },
+    "GridMeterIp": {
+      "DeviceState": 1,
+      "Host": "10.0.4.1",
+      "Port": 502
+    },
+    "PvOnAcGrid": {
+      "DeviceState": 0, // If a device is not present
+      "Host": "false", // this is not important
+      "Port": 0 // this is not important
+    },
+    "LoadOnAcGrid": {
+      "DeviceState": 2, // this is a computed device
+      "Host": "true",
+      "Port": 0
+    },
+    "PvOnAcIsland": {
+      "DeviceState": 0,
+      "Host": "false",
+      "Port": 0
+    },
+    "IslandBusLoadMeterIp": {
+      "DeviceState": 1,
+      "Host": "10.0.4.2",
+      "Port": 502
+    },
+    "TruConvertAcIp": {
+      "DeviceState": 1,
+      "Host": "10.0.2.1",
+      "Port": 502
+    },
+    "PvOnDc": {
+      "DeviceState": 1,
+      "Host": "10.0.5.1",
+      "Port": 502
+    },
+    "LoadOnDc": {
+      "DeviceState": 0,
+      "Host": "false",
+      "Port": 0
+    },
+    "TruConvertDcIp": {
+      "DeviceState": 1,
+      "Host": "10.0.3.1",
+      "Port": 502
+    },
+    "BatteryIp": {
+      "DeviceState": 1,
+      "Host": "localhost",
+      "Port": 6855
+    },
+    "BatteryNodes": [ // this is a list of nodes
+      2,
+      3,
+      4,
+      5,
+      6
+    ]
+  },
+  "S3": { // this is parameters of S3 Buckets and co
+    "Bucket": "8-3e5b3069-214a-43ee-8d85-57d72000c19d",
+    "Region": "sos-ch-dk-2",
+    "Provider": "exo.io",
+    "Key": "EXO502627299197f83e8b090f63",
+    "Secret": "jUNYJL6B23WjndJnJlgJj4rc1i7uh981u5Aba5xdA5s",
+    "ContentType": "text/plain; charset=utf-8",
+    "Host": "8-3e5b3069-214a-43ee-8d85-57d72000c19d.sos-ch-dk-2.exo.io",
+    "Url": "https://8-3e5b3069-214a-43ee-8d85-57d72000c19d.sos-ch-dk-2.exo.io"
+  }
diff --git a/csharp/App_backup/SodiStoreMax/Doc/States_Table.xlsx b/csharp/App_backup/SodiStoreMax/Doc/States_Table.xlsx
new file mode 100644
index 000000000..776df481c
Binary files /dev/null and b/csharp/App_backup/SodiStoreMax/Doc/States_Table.xlsx differ
diff --git a/csharp/App_backup/SodiStoreMax/Doc/TransitionToGridTied.graphml b/csharp/App_backup/SodiStoreMax/Doc/TransitionToGridTied.graphml
new file mode 100644
index 000000000..b73645cf6
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/Doc/TransitionToGridTied.graphml
@@ -0,0 +1,501 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
+  <!--Created by yEd 3.20.1-->
+  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
+  <key for="port" id="d1" yfiles.type="portgraphics"/>
+  <key for="port" id="d2" yfiles.type="portgeometry"/>
+  <key for="port" id="d3" yfiles.type="portuserdata"/>
+  <key attr.name="url" attr.type="string" for="node" id="d4"/>
+  <key attr.name="description" attr.type="string" for="node" id="d5"/>
+  <key for="node" id="d6" yfiles.type="nodegraphics"/>
+  <key for="graphml" id="d7" yfiles.type="resources"/>
+  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
+  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
+  <graph edgedefault="directed" id="G">
+    <data key="d0"/>
+    <node id="n0">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="1093.125" y="-372.5"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">19</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="923.125" y="-372.5"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">3</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="561.625" y="-350.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">9</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="753.125" y="-372.5"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">1</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="361.5" y="-207.5"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">13</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n5">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-230.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">29</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n6">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="1284.625" y="-395.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">23</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n7">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="923.125" y="-492.5"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">5</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n8">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="1093.125" y="-492.5"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">7</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n9">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="361.5" y="-350.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">11</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n10">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-110.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">15</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n11">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="753.125" y="-492.5"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">21</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n12">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="561.625" y="-470.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">17</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n13">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="361.5" y="-470.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">25</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n14">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-350.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">27</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n15">
+      <data key="d4" xml:space="preserve"/>
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="0.0" y="-110.0"/>
+          <y:Fill color="#FFEB9C" color2="#FF0000" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">31</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="30.628189086914062" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✓
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <edge id="e0" source="n5" target="n4">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-22.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="side_slider" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="33.19581604003906" xml:space="preserve" y="2.0">turn off
+Inverters<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n2" target="n3">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="22.5"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="57.61639404296875" x="31.941802978515625" xml:space="preserve" y="2.0">switch to 
+grid tie<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n4" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="30.0">
+            <y:Point x="482.0" y="-162.5"/>
+            <y:Point x="511.125" y="-275.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="52.86815850220398" anchorY="-29.097424414959733" configuration="AutoFlippingLabel" distance="5.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="-0.9680839425312168" upY="-0.25062617623308175" verticalTextPosition="bottom" visible="true" width="58.68438720703125" x="33.173348119879954" xml:space="preserve" y="-91.00760492412827">K3's open<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="5.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n3" target="n1">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.044342041015625" x="24.977828979492188" xml:space="preserve" y="2.0">close K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n1" target="n0">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="22.445816040039062" xml:space="preserve" y="2.0">turn on 
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e5" source="n0" target="n6">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="22.5"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="58.492401123046875" x="31.503799438476562" xml:space="preserve" y="2.0">K3's close<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e6" source="n9" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="anywhere" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.236328125" x="39.9443359375" xml:space="preserve" y="-22.344114303588867">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" frozen="true" placement="anywhere" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e7" source="n10" target="n4">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="22.5">
+            <y:Point x="290.5" y="-65.0"/>
+            <y:Point x="311.0" y="-140.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="75.68078078809617" anchorY="-7.379352680589193" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="-0.9646152654368906" upY="-0.26366150588608345" verticalTextPosition="bottom" visible="true" width="50.236328125" x="56.056537569061355" xml:space="preserve" y="-61.202041482663645">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e8" source="n8" target="n6">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-22.5">
+            <y:Point x="1213.625" y="-447.5"/>
+            <y:Point x="1234.125" y="-372.5"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="16.16576645645864" anchorY="21.121410140198805" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.9646152654368907" upY="-0.263661505886083" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="16.16576645645864" xml:space="preserve" y="11.448136537337454">turn on 
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e9" source="n7" target="n8">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.044342041015625" x="24.977828979492188" xml:space="preserve" y="2.0">close K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e10" source="n12" target="n3">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-22.5">
+            <y:Point x="682.125" y="-425.0"/>
+            <y:Point x="702.625" y="-350.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="17.05167531189511" anchorY="24.362540099112607" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.9646152654368907" upY="-0.263661505886083" verticalTextPosition="bottom" visible="true" width="48.38832092285156" x="17.05167531189511" xml:space="preserve" y="14.689266496251257">turn off
+inverter<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e11" source="n15" target="n10">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="side_slider" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="22.93181610107422" xml:space="preserve" y="2.0">turn off
+inverters<y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e12" source="n11" target="n7">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="22.93181610107422" xml:space="preserve" y="2.0">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e13" source="n14" target="n9">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="33.68181610107422" xml:space="preserve" y="2.0">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e14" source="n13" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-30.0">
+            <y:Point x="482.0" y="-425.0"/>
+            <y:Point x="511.125" y="-335.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="19.919637075116952" anchorY="31.15848493507559" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" preferredPlacement="center" ratio="0.5" textColor="#000000" upX="0.9514217507056354" upY="-0.3078906498811288" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="19.919637075116952" xml:space="preserve" y="19.86252238622422">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="anywhere" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d7">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/csharp/App_backup/SodiStoreMax/Doc/TransitionToIsland.graphml b/csharp/App_backup/SodiStoreMax/Doc/TransitionToIsland.graphml
new file mode 100644
index 000000000..800dffa96
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/Doc/TransitionToIsland.graphml
@@ -0,0 +1,487 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<graphml xmlns="http://graphml.graphdrawing.org/xmlns" xmlns:java="http://www.yworks.com/xml/yfiles-common/1.0/java" xmlns:sys="http://www.yworks.com/xml/yfiles-common/markup/primitives/2.0" xmlns:x="http://www.yworks.com/xml/yfiles-common/markup/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:y="http://www.yworks.com/xml/graphml" xmlns:yed="http://www.yworks.com/xml/yed/3" xsi:schemaLocation="http://graphml.graphdrawing.org/xmlns http://www.yworks.com/xml/schema/graphml/1.1/ygraphml.xsd">
+  <!--Created by yEd 3.20.1-->
+  <key attr.name="Description" attr.type="string" for="graph" id="d0"/>
+  <key for="port" id="d1" yfiles.type="portgraphics"/>
+  <key for="port" id="d2" yfiles.type="portgeometry"/>
+  <key for="port" id="d3" yfiles.type="portuserdata"/>
+  <key attr.name="url" attr.type="string" for="node" id="d4"/>
+  <key attr.name="description" attr.type="string" for="node" id="d5"/>
+  <key for="node" id="d6" yfiles.type="nodegraphics"/>
+  <key for="graphml" id="d7" yfiles.type="resources"/>
+  <key attr.name="url" attr.type="string" for="edge" id="d8"/>
+  <key attr.name="description" attr.type="string" for="edge" id="d9"/>
+  <key for="edge" id="d10" yfiles.type="edgegraphics"/>
+  <graph edgedefault="directed" id="G">
+    <data key="d0"/>
+    <node id="n0">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="1099.0" y="-470.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">28</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n1">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="929.0" y="-470.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">24</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n2">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="759.0" y="-470.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">8</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n3">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-110.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">6</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n4">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="534.5" y="-230.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">0</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n5">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="340.0" y="-110.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">4</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n6">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="0.0" y="-110.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">22</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n7">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="340.0" y="-230.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">16</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n8">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-230.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">20</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n9">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-350.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">18</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n10">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="340.0" y="-350.0"/>
+          <y:Fill color="#B4B4FF" transparent="false"/>
+          <y:BorderStyle color="#000068" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#B4B4FF" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#000068" verticalTextPosition="bottom" visible="true" width="10.864044189453125" x="29.567977905273438" xml:space="preserve" y="4.0">2</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#000068" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n11">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="534.5" y="-470.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">10</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n12">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="534.5" y="-590.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">12</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✘
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n13">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="340.0" y="-590.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="dashed" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">14</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n14">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="340.0" y="-470.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">26</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✘<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <node id="n15">
+      <data key="d5"/>
+      <data key="d6">
+        <y:GenericNode configuration="com.yworks.entityRelationship.big_entity">
+          <y:Geometry height="90.0" width="70.0" x="170.0" y="-590.0"/>
+          <y:Fill color="#FFEB9C" transparent="false"/>
+          <y:BorderStyle color="#683A00" type="line" width="1.0"/>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" backgroundColor="#FFEB9C" configuration="com.yworks.entityRelationship.label.name" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="internal" modelPosition="t" textColor="#683A00" verticalTextPosition="bottom" visible="true" width="17.72808837890625" x="26.135955810546875" xml:space="preserve" y="4.0">30</y:NodeLabel>
+          <y:NodeLabel alignment="center" autoSizePolicy="content" fontFamily="Dialog" fontSize="12" fontStyle="bold" hasBackgroundColor="false" hasLineColor="false" height="53.0323429107666" horizontalTextPosition="center" iconTextGap="4" modelName="custom" textColor="#683A00" verticalTextPosition="top" visible="true" width="31.144195556640625" x="2.0" xml:space="preserve" y="32.34411430358887">K1 ✘
+K2 ✓
+K3 ✓<y:LabelModel><y:ErdAttributesNodeLabelModel/></y:LabelModel><y:ModelParameter><y:ErdAttributesNodeLabelModelParameter/></y:ModelParameter></y:NodeLabel>
+          <y:StyleProperties>
+            <y:Property class="java.lang.Boolean" name="y.view.ShadowNodePainter.SHADOW_PAINTING" value="false"/>
+          </y:StyleProperties>
+        </y:GenericNode>
+      </data>
+    </node>
+    <edge id="e0" source="n8" target="n7">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="58.68438720703125" x="20.657806396484375" xml:space="preserve" y="-22.344114303588896">K3's open<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e1" source="n1" target="n0">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="58.492401123046875" x="20.753799438476562" xml:space="preserve" y="2.0">K3's close<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e2" source="n7" target="n4">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="5.0" xml:space="preserve" y="-38.688228607177734">turn off 
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.0" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e3" source="n4" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="30.0">
+            <y:Point x="655.0" y="-185.0"/>
+            <y:Point x="708.5" y="-395.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="105.70300540936205" anchorY="-59.97368347153517" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.9690470117615817" upY="-0.24687626252021255" verticalTextPosition="bottom" visible="true" width="73.21649169921875" x="70.1503871107507" xml:space="preserve" y="-139.9813587213569">switch to 
+island mode<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5000000000000004" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e4" source="n2" target="n1">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="22.93181610107422" xml:space="preserve" y="2.0">turn on 
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e5" source="n6" target="n3">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="delta"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="55.108367919921875" x="22.445816040039062" xml:space="preserve" y="1.999999999999993">turn off
+Inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e6" source="n5" target="n4">
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="30.0">
+            <y:Point x="460.5" y="-65.0"/>
+            <y:Point x="484.0" y="-155.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="53.242913557579016" anchorY="-18.421155878144106" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="-0.9675601644592253" upY="-0.25264070960879775" verticalTextPosition="bottom" visible="true" width="55.984375" x="33.55875897622129" xml:space="preserve" y="-77.72915843431224">K3 opens<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e7" source="n3" target="n5">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.236328125" x="24.8818359375" xml:space="preserve" y="2.0">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e8" source="n9" target="n10">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="22.93181610107422" xml:space="preserve" y="-38.688228607177734">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e9" source="n10" target="n4">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-30.0">
+            <y:Point x="460.5" y="-305.0"/>
+            <y:Point x="484.0" y="-215.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="57.83924953609818" anchorY="20.191383629556128" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.9675601644592253" upY="-0.25264070960879753" verticalTextPosition="bottom" visible="true" width="50.236328125" x="57.83924953609818" xml:space="preserve" y="15.05163215553495">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e10" source="n11" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.236328125" x="52.1318359375" xml:space="preserve" y="2.0">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e11" source="n12" target="n2">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-30.0">
+            <y:Point x="655.0" y="-545.0"/>
+            <y:Point x="708.5" y="-455.0"/>
+          </y:Path>
+          <y:LineStyle color="#000000" type="dashed" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" anchorX="64.66573623876036" anchorY="19.916163239593743" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" upX="0.8595925806889443" upY="-0.5109800340762061" verticalTextPosition="bottom" visible="true" width="55.984375" x="64.66573623876036" xml:space="preserve" y="9.520727019495672">K3 opens<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="true" ratio="0.5" segment="1"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e12" source="n13" target="n12">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="20.344114303588867" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="50.236328125" x="37.1318359375" xml:space="preserve" y="2.0">open K2<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e13" source="n14" target="n11">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="35.18181610107422" xml:space="preserve" y="2.0">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+    <edge id="e14" source="n15" target="n13">
+      <data key="d9"/>
+      <data key="d10">
+        <y:PolyLineEdge>
+          <y:Path sx="35.0" sy="-0.0" tx="-35.0" ty="-0.0"/>
+          <y:LineStyle color="#000000" type="line" width="1.0"/>
+          <y:Arrows source="none" target="standard"/>
+          <y:EdgeLabel alignment="center" configuration="AutoFlippingLabel" distance="2.0" fontFamily="Dialog" fontSize="12" fontStyle="plain" hasBackgroundColor="false" hasLineColor="false" height="36.688228607177734" horizontalTextPosition="center" iconTextGap="4" modelName="custom" ratio="0.5" textColor="#000000" verticalTextPosition="bottom" visible="true" width="54.13636779785156" x="22.93181610107422" xml:space="preserve" y="2.0">turn off
+inverters<y:LabelModel><y:RotatedSliderEdgeLabelModel angle="0.0" autoRotationEnabled="true" distance="2.0" distanceRelativeToEdge="true" mode="side_slider"/></y:LabelModel><y:ModelParameter><y:RotatedSliderEdgeLabelModelParameter invertingSign="false" ratio="0.5" segment="0"/></y:ModelParameter><y:PreferredPlacementDescriptor angle="0.0" angleOffsetOnRightSide="0" angleReference="absolute" angleRotationOnRightSide="co" distance="-1.0" placement="center" side="left|right" sideReference="relative_to_edge_flow"/></y:EdgeLabel>
+          <y:BendStyle smoothed="false"/>
+        </y:PolyLineEdge>
+      </data>
+    </edge>
+  </graph>
+  <data key="d7">
+    <y:Resources/>
+  </data>
+</graphml>
diff --git a/csharp/App_backup/SodiStoreMax/HostList.txt b/csharp/App_backup/SodiStoreMax/HostList.txt
new file mode 100755
index 000000000..9a03162c9
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/HostList.txt
@@ -0,0 +1,14 @@
+
+Prototype          ie-entwicklung@10.2.3.115         Prototype
+Salimax0001        ie-entwicklung@10.2.3.104         Marti Technik (Bern)
+Salimax0002        ie-entwicklung@10.2.4.29          Weidmann d (ZG)
+Salimax0003        ie-entwicklung@10.2.4.33          Elektrotechnik Stefan GmbH
+Salimax0004        ie-entwicklung@10.2.4.32          Biohof Gubelmann (Walde)
+Salimax0004A       ie-entwicklung@10.2.4.153
+Salimax0005        ie-entwicklung@10.2.4.36          Schreinerei Schönthal (Thun)
+Salimax0006        ie-entwicklung@10.2.4.35          Steakhouse Mettmenstetten
+Salimax0007        ie-entwicklung@10.2.4.154         LerchenhofHerr Twannberg 
+Salimax0008        ie-entwicklung@10.2.4.113         Wittmann Kottingbrunn
+Salimax0010        ie-entwicklung@10.2.4.211         Mohatech 1 (Beat Moser)
+Salimax0011        ie-entwicklung@10.2.4.239         Thomas Tschirren (Enggistein)
+SalidomoServer     ig@134.209.238.170
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/SodiStoreMax.csproj b/csharp/App_backup/SodiStoreMax/SodiStoreMax.csproj
new file mode 100644
index 000000000..c40ce5aa8
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/SodiStoreMax.csproj
@@ -0,0 +1,32 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <Import Project="../InnovEnergy.App.props" />
+
+    <PropertyGroup>
+        <RootNamespace>InnovEnergy.App.SodiStoreMax</RootNamespace>
+    </PropertyGroup>
+    
+    <ItemGroup>
+        <PackageReference Version="3.2.4" Include="Flurl.Http" />
+        <PackageReference Version="7.0.0" Include="Microsoft.Extensions.Logging" />
+        <PackageReference Include="RabbitMQ.Client" Version="6.6.0" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <ProjectReference Include="../../Lib/Devices/Adam6360D/Adam6360D.csproj" />
+      <ProjectReference Include="../../Lib/Devices/AMPT/Ampt.csproj" />
+      <ProjectReference Include="../../Lib/Devices/BatteryDeligreen/BatteryDeligreen.csproj" />
+      <ProjectReference Include="../../Lib/Devices/EmuMeter/EmuMeter.csproj" />
+      <ProjectReference Include="../../Lib/Devices/Trumpf/SystemControl/SystemControl.csproj" />
+      <ProjectReference Include="../../Lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj" />
+      <ProjectReference Include="../../Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj" />
+      <ProjectReference Include="../../Lib/Units/Units.csproj" />
+      <ProjectReference Include="../../Lib/Utils/Utils.csproj" />
+      <ProjectReference Include="../../Lib/Devices/Amax5070/Amax5070.csproj" />
+      <ProjectReference Include="../../Lib/Devices/Adam6060/Adam6060.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+      <Folder Include="resources\" />
+    </ItemGroup>
+
+</Project>
diff --git a/csharp/App_backup/SodiStoreMax/deploy.sh b/csharp/App_backup/SodiStoreMax/deploy.sh
new file mode 100755
index 000000000..941647e33
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/deploy.sh
@@ -0,0 +1,21 @@
+#!/bin/bash
+dotnet_version='net6.0'
+salimax_ip="$1"
+username='ie-entwicklung'
+root_password='Salimax4x25'
+set -e
+
+echo -e "\n============================ Build ============================\n" 
+
+dotnet publish  \
+       ./SodiStoreMax.csproj \
+       -p:PublishTrimmed=false \
+       -c Release \
+       -r linux-x64 
+
+echo -e "\n============================ Deploy ============================\n" 
+
+rsync -v \
+      --exclude '*.pdb' \
+      ./bin/Release/$dotnet_version/linux-x64/publish/* \
+       $username@"$salimax_ip":~/salimax
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/deploy_all_installations.sh b/csharp/App_backup/SodiStoreMax/deploy_all_installations.sh
new file mode 100755
index 000000000..533946856
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/deploy_all_installations.sh
@@ -0,0 +1,37 @@
+#!/bin/bash
+
+dotnet_version='net6.0'
+salimax_ip="$1"
+username='ie-entwicklung'
+root_password='Salimax4x25'
+
+set -e
+
+echo -e "\n============================ Build ============================\n" 
+
+dotnet publish  \
+       ./SaliMax.csproj \
+       -p:PublishTrimmed=false \
+       -c Release \
+       -r linux-x64 
+
+echo -e "\n============================ Deploy ============================\n" 
+#ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.29" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.211")
+#ip_addresses=("10.2.4.154" "10.2.4.29")
+ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.29" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" )
+
+
+
+for ip_address in "${ip_addresses[@]}"; do
+    rsync -v \
+          --exclude '*.pdb' \
+          ./bin/Release/$dotnet_version/linux-x64/publish/* \
+           $username@"$ip_address":~/salimax
+
+    ssh "$username"@"$ip_address" "cd salimax && echo '$root_password' | sudo -S ./restart"
+
+    echo "Deployed and ran commands on $ip_address"
+done
+
+
+
diff --git a/csharp/App_backup/SodiStoreMax/downloadBatteryLogs/download-bms-log b/csharp/App_backup/SodiStoreMax/downloadBatteryLogs/download-bms-log
new file mode 100644
index 000000000..b9c2e8f23
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/downloadBatteryLogs/download-bms-log
@@ -0,0 +1,284 @@
+#!/usr/bin/python2 -u
+# coding=utf-8
+import os
+import re
+import struct
+import serial
+import logging
+from sys import argv, exit
+from datetime import datetime
+from pymodbus.pdu import ModbusRequest, ModbusResponse, ExceptionResponse
+from pymodbus.other_message import ReportSlaveIdRequest
+from pymodbus.exceptions import ModbusException
+from pymodbus.pdu import ExceptionResponse
+from pymodbus.factory import ClientDecoder
+from pymodbus.client import ModbusSerialClient as Modbus
+logging.basicConfig(level=logging.INFO)
+
+
+
+# trick the pycharm type-checker into thinking Callable is in scope, not used at runtime
+# noinspection PyUnreachableCode
+if False:
+    from typing import List, Optional, NoReturn
+
+RESET_REGISTER = 0x2087
+FIRMWARE_VERSION_REGISTER = 1054
+SERIAL_STARTER_DIR = '/opt/victronenergy/serial-starter/'
+INSTALLATION_NAME_FILE = '/data/innovenergy/openvpn/installation-name'
+OUTPUT_DIR = '/data/innovenergy'
+
+
+class ReadLogRequest(ModbusRequest):
+
+    function_code = 0x42
+    _rtu_frame_size = 5  # not used
+
+    def __init__(self, address = None, **kwargs):
+
+        ModbusRequest.__init__(self, **kwargs)
+        self.sub_function = 0 if address is None else 1
+        self.address = address
+
+        # FUGLY as hell, but necessary bcs PyModbus cannot deal
+        # with responses that have lengths depending on the sub_function.
+        # it goes without saying that this isn't thread-safe
+        ReadLogResponse._rtu_frame_size = 9 if self.sub_function == 0 else 9+128
+
+    def encode(self):
+
+        if self.sub_function == 0:
+            return struct.pack('>B', self.sub_function)
+        else:
+            return struct.pack('>BI', self.sub_function, self.address)
+
+    def decode(self, data):
+        self.sub_function = struct.unpack('>B', data)
+
+    def execute(self, context):
+        print("EXECUTE1")
+
+    def get_response_pdu_size(self):
+        return ReadLogResponse._rtu_frame_size - 3
+
+    def __str__(self):
+        return "ReadLogAddressRequest"
+
+
+class ReadLogResponse(ModbusResponse):
+
+    function_code = 0x42
+    _rtu_frame_size = 9  # the WHOLE frame incl crc
+
+    def __init__(self, sub_function=0, address=b'\x00', data=None, **kwargs):
+        ModbusResponse.__init__(self, **kwargs)
+        self.sub_function = sub_function
+        self.address = address
+        self.data = data
+
+    def encode(self):
+        pass
+
+    def decode(self, data):
+        self.address, self.address = struct.unpack_from(">BI", data)
+        self.data = data[5:]
+
+    def __str__(self):
+        arguments = (self.function_code, self.address)
+        return "ReadLogAddressResponse(%s, %s)" % arguments
+
+# unfortunately we have to monkey-patch this global table because
+# the current (victron) version of PyModbus does not have a
+# way to "register" new function-codes yet
+ClientDecoder.function_table.append(ReadLogResponse)
+
+
+class LockTTY(object):
+
+    def __init__(self, tty):
+        # type: (str) -> None
+        self.tty = tty
+
+    def __enter__(self):
+        os.system(SERIAL_STARTER_DIR + 'stop-tty.sh ' + self.tty)
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        os.system(SERIAL_STARTER_DIR + 'start-tty.sh ' + self.tty)
+
+
+def wrap_try_except(error_msg):
+    def decorate(f):
+        def applicator(*args, **kwargs):
+            try:
+                return f(*args, **kwargs)
+            except:
+                print(error_msg)
+                exit(1)
+        return applicator
+    return decorate
+
+
+def init_modbus(tty):
+    # type: (str) -> Modbus
+
+    return Modbus(
+        port='/dev/' + tty,
+        method='rtu',
+        baudrate=115200,
+        stopbits=1,
+        bytesize=8,
+        timeout=0.5,  # seconds
+        parity=serial.PARITY_ODD)
+
+
+@wrap_try_except("Failed to download BMS log!")
+def download_log(modbus, node_id, battery_id):
+    # type: (Modbus, int, str) -> NoReturn
+
+    # Get address of latest log entry
+    # request = ReadLogRequest(unit=slave_id)
+
+    print ('downloading BMS log from node ' + str(node_id) + ' ...')
+
+    progress = -1
+    log_file = battery_id + "-node" + str(node_id) + "-"  + datetime.now().strftime('%d-%m-%Y') + ".bin"
+    print(log_file)
+
+    with open(log_file, 'w') as f:
+
+        eof = 0x200000
+        record = 0x40
+        for address in range(0, eof, 2*record):
+
+            percent = int(100*address/eof)
+
+            if percent != progress:
+                progress = percent
+                print('\r{}% '.format(progress),end='')
+
+            request = ReadLogRequest(address, slave=node_id)
+            result = modbus.execute(request)  # type: ReadLogResponse
+
+            address1 = "{:06X}".format(address)
+            address2 = "{:06X}".format(address+record)
+
+            data1 = result.data[:record]
+            data2 = result.data[record:]
+
+            line1 = address1 + ":" + ''.join('{:02X}'.format(byte) for byte in data1)
+            line2 = address2 + ":" + ''.join('{:02X}'.format(byte) for byte in data2)
+
+            lines = line1 + "\n" + line2 + "\n"
+            f.write(lines)
+
+    print("\r100%")
+    print("done")
+    print("wrote log to " + log_file)
+
+    return True
+
+
+@wrap_try_except("Failed to contact battery!")
+def identify_battery(modbus, node_id):
+    # type: (Modbus, int) -> str
+
+    target = 'battery #' + str(node_id)
+    print('contacting ' + target + ' ...')
+
+    request = ReportSlaveIdRequest(slave=node_id)
+    response = modbus.execute(request)
+
+    index_of_ff = response.identifier.find(b'\xff')
+    sid_response = response.identifier[index_of_ff + 1:].decode('utf-8').split(' ')
+
+    response = modbus.read_input_registers(address=FIRMWARE_VERSION_REGISTER, count=1, slave=node_id)
+
+    fw = '{0:0>4X}'.format(response.registers[0])
+    print("log string is",sid_response[0]+"-"+sid_response[1]+"-"+fw)
+        
+    #return re.sub(" +", "-", sid + " " + fw)
+    return sid_response[0]+"-"+sid_response[1]+"-"+fw
+
+
+def is_int(value):
+    # type: (str) -> bool
+    try:
+        _ = int(value)
+        return True
+    except ValueError:
+        return False
+
+
+def print_usage():
+    print ('Usage:   ' + __file__ + ' <slave id> <serial device>')
+    print ('Example: ' + __file__ + ' 2 ttyUSB0')
+    print ('')
+    print ('You can omit the "ttyUSB" prefix of the serial device:')
+    print ('  ' + __file__ + ' 2 0')
+    print ('')
+    print ('You can omit the serial device entirely when the "com.victronenergy.battery.<serial device>" service is running:')
+    print ('  ' + __file__ + ' 2')
+    print ('')
+
+
+def get_tty_from_battery_service_name():
+    # type: () -> Optional[str]
+
+    import dbus
+    bus = dbus.SystemBus()
+
+    tty = (
+        name.split('.')[-1]
+        for name in bus.list_names()
+        if name.startswith('com.victronenergy.battery.')
+    )
+
+    return next(tty, None)
+
+
+def parse_tty(tty):
+    # type: (Optional[str]) -> str
+
+    if tty is None:
+        return get_tty_from_battery_service_name()
+
+    if is_int(tty):
+        return 'ttyUSB' + argv[1]
+    else:
+        return tty
+
+
+def parse_cmdline_args(argv):
+    # type: (List[str]) -> (str, int)
+
+    slave_id = element_at_or_none(argv, 0)
+    tty = parse_tty(element_at_or_none(argv, 1))
+
+    if slave_id is None or tty is None:
+        print_usage()
+        exit(2)
+
+    print("tty=",tty)
+    print("slave id= ",slave_id)
+
+    return tty, int(slave_id)
+
+
+def element_at_or_none(lst, index):
+    return next(iter(lst[index:]), None)
+
+
+def main(argv):
+    # type: (List[str]) -> ()
+
+    tty, node_id = parse_cmdline_args(argv)
+
+    with init_modbus(tty) as modbus:
+        battery_id = identify_battery(modbus, node_id)
+        download_log(modbus, node_id, battery_id)
+
+    exit(0)
+
+
+main(argv[1:])
diff --git a/csharp/App_backup/SodiStoreMax/downloadBatteryLogs/download_battery_logs.sh b/csharp/App_backup/SodiStoreMax/downloadBatteryLogs/download_battery_logs.sh
new file mode 100755
index 000000000..20c3f05b4
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/downloadBatteryLogs/download_battery_logs.sh
@@ -0,0 +1,70 @@
+#!/bin/bash
+
+#Prototype          10.2.3.115         Prototype
+#Salimax0001        10.2.3.104         Marti Technik (Bern)
+#Salimax0002        10.2.4.29          Weidmann d (ZG)
+#Salimax0003        10.2.4.33          Elektrotechnik Stefan GmbH
+#Salimax0004        10.2.4.32          Biohof Gubelmann (Walde)
+#Salimax0005        10.2.4.36          Schreinerei Schönthal (Thun)
+#Salimax0006        10.2.4.35          Steakhouse Mettmenstetten
+#Salimax0007        10.2.4.154         LerchenhofHerr Twannberg 
+#Salimax0008        10.2.4.113         Wittmann Kottingbrunn
+
+dotnet_version='net6.0'
+ip_address="$1"
+battery_ids="$2"
+username='ie-entwicklung'
+root_password='Salimax4x25'
+
+if [ "$#" -lt 2 ]; then
+  echo "Error: Insufficient arguments. Usage: $0 <ip_address> <battery_ids>"
+  exit 1
+fi
+
+# Function to expand battery ids from a range
+expand_battery_ids() {
+  local range="$1"
+  local expanded_ids=()
+
+  IFS='-' read -r start end <<< "$range"
+  for ((i = start; i <= end; i++)); do
+    expanded_ids+=("$i")
+  done
+
+  echo "${expanded_ids[@]}"
+}
+
+# Check if battery_ids_arg contains a hyphen indicating a range
+if [[ "$battery_ids" == *-* ]]; then
+  # Expand battery ids from the range
+  battery_ids=$(expand_battery_ids "$battery_ids")
+else
+  # Use the provided battery ids
+  battery_ids=("$battery_ids")
+fi
+
+echo "ip_address: $ip_address"
+echo "Battery_ids: ${battery_ids[@]}"
+
+#ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.29")
+#battery_ids=("2" "3" "4" "5" "6" "7" "8" "9" "10" "11")
+
+set -e
+
+scp download-bms-log "$username"@"$ip_address":/home/"$username"
+ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S systemctl stop battery.service"
+ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S apt install python3-pip -y"
+ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S pip3 install pymodbus"
+
+for battery in "${battery_ids[@]}"; do
+    ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S python3 download-bms-log " "$battery" " ttyUSB0" 
+done
+ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S systemctl start battery.service"
+ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S rm download-bms-log"
+scp "$username"@"$ip_address":/home/"$username/*.bin" .
+ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S rm *.bin"
+
+echo "Deployed and ran commands on $ip_address"
+done
+
+
diff --git a/csharp/App_backup/SodiStoreMax/resources/Battery.Service b/csharp/App_backup/SodiStoreMax/resources/Battery.Service
new file mode 100644
index 000000000..ebead9a53
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/resources/Battery.Service
@@ -0,0 +1,12 @@
+[Unit]
+Description=Battery48TL_TCP_Bridge
+
+[Service]
+Type=simple
+ExecStart=socat -x -d -d TCP-LISTEN:6855 GOPEN:/dev/ttyUSB0,rawer,b115200,cs8,parenb=1,parodd=1
+Restart=always
+RestartSec=500ms
+StartLimitInterval=0
+
+[Install]
+WantedBy=multi-user.target
diff --git a/csharp/App_backup/SodiStoreMax/resources/PublicKey b/csharp/App_backup/SodiStoreMax/resources/PublicKey
new file mode 100644
index 000000000..ae41b2935
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/resources/PublicKey
@@ -0,0 +1 @@
+ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCed5ANekhbdV/8nEwFyaqxbPGON+NZKAkZXKx2aMAbX6jYQpusXSf4lKxEp4vHX9q2ScWycluUEhlzwe9vaWIK6mxEG9gjtU0/tKIavqZ6qpcuiglal750e8tlDh+lAgg5K3v4tvV4uVEfFc42UzSC9cIBBKPBC41dc0xQKyFIDsSH6Qha1nyncKRC3OXUkOiiRvmbd4PVc9A5ah2vt+661pghZE19Qeh5ROn/Sma9C+9QIyUDCylezqptnT+Jdvs+JMCHk8nKK2A0bz1w0a8zzO7M1RLHfBLQ6o1SQAdV/Pmon8uQ9vLHc86l5r7WSTMEcjAqY3lGE9mdxsSZWNmp InnovEnergy
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/resources/Salimax.Service b/csharp/App_backup/SodiStoreMax/resources/Salimax.Service
new file mode 100644
index 000000000..d823b591f
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/resources/Salimax.Service
@@ -0,0 +1,13 @@
+[Unit]
+Description=Salimax Controller
+Wants=battery.service
+
+[Service]
+WorkingDirectory=/home/ie-entwicklung/salimax
+ExecStart=/home/ie-entwicklung/salimax/SaliMax
+WatchdogSec=30s
+Restart=always
+RestartSec=500ms
+
+[Install]
+WantedBy=multi-user.target
diff --git a/csharp/App_backup/SodiStoreMax/src/AggregationService/Aggregator.cs b/csharp/App_backup/SodiStoreMax/src/AggregationService/Aggregator.cs
new file mode 100644
index 000000000..3fe1d40ed
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/AggregationService/Aggregator.cs
@@ -0,0 +1,356 @@
+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;
+
+
+public static class Aggregator
+{
+    
+    public static async Task HourlyDataAggregationManager()
+    {
+        var currentDateTime = DateTime.Now;
+        var nextRoundedHour = currentDateTime.AddHours(1).AddMinutes(-currentDateTime.Minute).AddSeconds(-currentDateTime.Second);
+
+        // 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);
+        Console.WriteLine("Next Rounded Hour: " + nextRoundedHour);
+        // 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);
+        
+        while (true)
+        {
+            try
+            {
+                AggregatedData hourlyAggregatedData = CreateHourlyData("JsonLogDirectory",DateTime.Now.AddHours(-1).ToUnixTime(),DateTime.Now.ToUnixTime());
+                hourlyAggregatedData.Save("HourlyData");
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine("An error has occured when calculating hourly aggregated data, exception is:\n" + e);
+            }
+            await Task.Delay(TimeSpan.FromHours(1));
+        }
+    }
+    
+    public static async Task DailyDataAggregationManager()
+    {
+        var currentDateTime = DateTime.Now;
+        var nextRoundedHour = currentDateTime.AddDays(1).AddHours(-currentDateTime.Hour).AddMinutes(-currentDateTime.Minute).AddSeconds(-currentDateTime.Second);
+
+        // Calculate the time until the next rounded hour
+        var timeUntilNextDay = nextRoundedHour - currentDateTime;
+        Console.WriteLine("------------------------------------------DailyDataAggregationManager-------------------------------------------");
+        // Output the current and next rounded hour times
+        Console.WriteLine("Current Date and Time: " + currentDateTime);
+        Console.WriteLine("Next Rounded Hour: " + nextRoundedHour);
+        // Output the time until the next rounded hour
+        Console.WriteLine("Waiting for " + timeUntilNextDay.TotalHours + " hours...");
+        Console.WriteLine("-----------------------------------------------------------------------------------------------------------------");
+        
+        // Wait until the next rounded hour
+        await Task.Delay(timeUntilNextDay);
+        
+        while (true)
+        {
+            try
+            {
+                var currentTime = DateTime.Now;
+                AggregatedData dailyAggregatedData = CreateDailyData("HourlyData",currentTime.AddDays(-1).ToUnixTime(),currentTime.ToUnixTime());
+                dailyAggregatedData.Save("DailyData");
+                if (await dailyAggregatedData.PushToS3())
+                {
+                    //DeleteHourlyData("HourlyData",currentTime.ToUnixTime());
+                    //AggregatedData.DeleteDailyData("DailyData");
+                }
+                
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine("An error has occured when calculating daily aggregated data, exception is:\n" + e);
+            }
+            await Task.Delay(TimeSpan.FromDays(1));
+        }
+    }
+
+    private static void DeleteHourlyData(String myDirectory, Int64 beforeTimestamp)
+    {
+        var jsonFiles            = Directory.GetFiles(myDirectory, "*.json");
+        Console.WriteLine("Delete data before"+beforeTimestamp);
+        foreach (var jsonFile in jsonFiles)
+        {
+            if (IsFileWithinTimeRange(jsonFile, 0, beforeTimestamp))
+            {
+                File.Delete(jsonFile);
+                Console.WriteLine($"Deleted hourly data file: {jsonFile}");
+            }
+        }
+    }
+
+    // this for test
+    private static AggregatedData CreateHourlyData(String myDirectory, Int64 afterTimestamp, Int64 beforeTimestamp)
+    {
+        // Get all CSV files in the specified directory
+        var jsonFiles              = Directory.GetFiles(myDirectory, "*.json");
+        var batterySoc            = new List<Double>();
+        var pvPowerSum            = new List<Double>(); 
+        var heatingPower          = new List<Double>(); 
+        var gridPowerImport       = new List<Double>();
+        var gridPowerExport       = new List<Double>();
+        var batteryDischargePower = new List<Double>();
+        var batteryChargePower    = new List<Double>();
+
+        
+        Console.WriteLine("File timestamp should start after "+ afterTimestamp);
+        
+        foreach (var jsonFile in jsonFiles)
+        {
+            if (jsonFile == "LogDirectory/log.json")
+            {
+                continue;
+            }
+            
+            if (IsFileWithinTimeRange(jsonFile, afterTimestamp, beforeTimestamp))
+            {
+                try
+                {
+                    // Read and parse JSON
+
+                    var jsonData = File.ReadAllText(jsonFile);
+                    
+                    // Step 2: Find the first '{' character and trim everything before it
+                    int startIndex = jsonData.IndexOf('{');
+                    if (startIndex != -1)
+                    {
+                        jsonData = jsonData.Substring(startIndex); // Trim everything before '{'
+                    }
+
+                    var jsonObject = JObject.Parse(jsonData);
+
+
+                    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
+                            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}");
+                }
+            }
+        }
+
+        //Average Power (Watts)= Sum of Power Readings/Number of Readings
+                                    
+        //Then, you can use the average power in the energy formula:
+        //
+        //Energy (kWh)= (Average Power / 3600) × Time (1 seconds) 
+        //  
+        // Dividing the Average power readings by 3600 converts the result from watt-seconds to kilowatt-hours.
+
+        var dischargingEnergy           = (batteryDischargePower.Any() ? batteryDischargePower.Average() : 0.0)  / 3600;
+        var chargingEnergy              = (batteryChargePower.Any()    ? batteryChargePower.Average()    : 0.0)  / 3600;
+        var heatingPowerAvg             = (heatingPower.Any()          ? heatingPower.Average()          : 0.0)  / 3600;
+        
+        var dMaxSoc                     = batterySoc.Any()            ? batterySoc.Max()                 : 0.0;   
+        var dMinSoc                     = batterySoc.Any()            ? batterySoc.Min()                 : 0.0;            
+        var dSumGridExportPower         = gridPowerExport.Any()       ? gridPowerExport.Max() - gridPowerExport.Min(): 0.0;               
+        var dSumGridImportPower         = gridPowerImport.Any()       ? gridPowerImport.Max() - gridPowerImport.Min(): 0.0;
+        var dSumPvPower                 = pvPowerSum.Any()            ? pvPowerSum.Max()                 : 0.0; 
+        
+
+        AggregatedData aggregatedData = new AggregatedData
+        {
+            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
+        Console.WriteLine($"Max SOC:                   {aggregatedData.MaxSoc}");
+        Console.WriteLine($"Min SOC:                   {aggregatedData.MinSoc}");
+
+        Console.WriteLine($"DischargingBatteryBattery:      {aggregatedData.DischargingBatteryPower}");
+        Console.WriteLine($"ChargingBatteryPower:           {aggregatedData.ChargingBatteryPower}");
+        
+        Console.WriteLine($"SumGridExportPower:        {aggregatedData.GridExportPower}");
+        Console.WriteLine($"SumGridImportPower:        {aggregatedData.GridImportPower}");
+        
+        Console.WriteLine($"Min SOC:                   {aggregatedData.MinSoc}");
+
+        
+        Console.WriteLine("CSV data reading and storage completed.");
+        Console.WriteLine("-----------------------------------------------------------------------------------------------------------------");
+
+        return aggregatedData;
+    }
+
+    private static AggregatedData CreateDailyData(String myDirectory, Int64 afterTimestamp, Int64 beforeTimestamp)
+    {
+        // Get all CSV files in the specified directory
+        var jsonFiles              = Directory.GetFiles(myDirectory, "*.json");
+        var batterySoc            = new List<Double>();
+        var pvPower               = new List<Double>();
+        var gridPowerImport       = new List<Double>();
+        var gridPowerExport       = new List<Double>();
+        var batteryDischargePower = new List<Double>();
+        var batteryChargePower    = new List<Double>();
+        var heatingPowerAvg       = new List<Double>();
+
+        
+        
+        Console.WriteLine("File timestamp should start after "+ afterTimestamp);
+        
+        foreach (var jsonFile in jsonFiles)
+        {
+            if (jsonFile == "LogDirectory/log.json")
+            {
+                continue;
+            }
+
+            if (IsFileWithinTimeRange(jsonFile, afterTimestamp, beforeTimestamp))
+            {
+
+                try
+                {
+                    var jsonData = File.ReadAllText(jsonFile);
+                    //Console.WriteLine("Parse file "+jsonFile);
+                    
+                    // Parse JSON into a Dictionary
+                    var jsonDict = JsonConvert.DeserializeObject<Dictionary<string, Double>>(jsonData);
+                    
+                    // Process values
+                    foreach (var (variableName, value) in jsonDict)
+                    {
+                        switch (variableName)
+                        {
+                            case "MinSoc":
+                            case "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;
+
+                            default:
+                                // Ignore unknown variables
+                                break;
+                        }
+                    }
+                
+                }
+                catch (Exception e)
+                {
+                    Console.WriteLine($"Failed to parse JSON file {jsonFile}: {e.Message}");
+                }
+
+             
+            }
+        }
+
+        AggregatedData aggregatedData = new AggregatedData
+        {
+            MaxSoc                  = batterySoc.Any()            ? batterySoc.Max()               : 0.0,
+            MinSoc                  = batterySoc.Any()            ? batterySoc.Min()               : 0.0,
+            DischargingBatteryPower = batteryDischargePower.Any() ? batteryDischargePower.Average(): 0.0,
+            ChargingBatteryPower    = batteryChargePower.Any()    ? batteryChargePower.Average()   : 0.0,
+            GridExportPower         = gridPowerExport.Any()       ? gridPowerExport.Sum()          : 0.0,  
+            GridImportPower         = gridPowerImport.Any()       ? gridPowerImport.Sum()          : 0.0,  
+            PvPower                 = pvPower.Any()               ? pvPower.Last()                 : 0.0,
+            HeatingPower            = heatingPowerAvg.Any()       ? heatingPowerAvg.Average()      : 0.0, 
+        };
+        
+        // Print the stored CSV data for verification
+        Console.WriteLine($"Pv Power:                  {aggregatedData.PvPower}");
+        Console.WriteLine($"Heating Power:             {aggregatedData.HeatingPower}");
+        Console.WriteLine($"Max SOC:                   {aggregatedData.MaxSoc}");
+        Console.WriteLine($"Min SOC:                   {aggregatedData.MinSoc}");
+
+        Console.WriteLine($"ChargingBatteryPower:      {aggregatedData.DischargingBatteryPower}");
+        Console.WriteLine($"DischargingBatteryBattery: {aggregatedData.ChargingBatteryPower}");
+        
+        Console.WriteLine($"SumGridExportPower:        {aggregatedData.GridExportPower}");
+        Console.WriteLine($"SumGridImportPower:        {aggregatedData.GridImportPower}");
+        
+
+        
+        Console.WriteLine("CSV data reading and storage completed.");
+        Console.WriteLine("-----------------------------------------------------------------------------------------------------------------");
+
+        return aggregatedData;
+    }
+
+    // Custom method to check if a string is numeric
+    private static Boolean GetVariable(String value, String path)
+    {
+        return value == path;
+    }
+  
+    private static Boolean IsFileWithinTimeRange(string filePath, long startTime, long endTime)
+    {
+        var fileTimestamp  = long.TryParse(Path.GetFileNameWithoutExtension(filePath).Replace("log_", ""), out var fileTimestamp1) ? fileTimestamp1 : -1;
+        
+        return fileTimestamp >= startTime && fileTimestamp < endTime;
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/AggregationService/HourlyData.cs b/csharp/App_backup/SodiStoreMax/src/AggregationService/HourlyData.cs
new file mode 100644
index 000000000..79ca6349e
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/AggregationService/HourlyData.cs
@@ -0,0 +1,118 @@
+using System.IO.Compression;
+using System.Text;
+using System.Text.Json;
+using Flurl.Http;
+using InnovEnergy.App.SodiStoreMax.Devices;
+using InnovEnergy.App.SodiStoreMax.SystemConfig;
+using InnovEnergy.Lib.Units;
+using InnovEnergy.Lib.Utils;
+using Newtonsoft.Json;
+using static System.Text.Json.JsonSerializer;
+
+namespace InnovEnergy.App.SodiStoreMax.AggregationService;
+// shut up trim warnings
+#pragma warning disable IL2026  
+
+public class AggregatedData
+{
+    public required Double  MinSoc                      { get; set; } 
+    public required Double  MaxSoc                      { get; set; } 
+    public required Double  PvPower                     { get; set; }
+    public required Double  DischargingBatteryPower     { get; set; }
+    public required Double  ChargingBatteryPower        { get; set; }
+    public required Double  GridExportPower             { get; set; }
+    public required Double  GridImportPower             { get; set; }
+    public required Double  HeatingPower                { get; set; }
+
+    
+    private readonly S3Config? _S3Config = Config.Load().S3;
+
+    public void Save(String directory)
+    {
+        var date             = DateTime.Now.ToUnixTime();
+        var defaultHDataPath = Environment.CurrentDirectory + "/" + directory + "/";
+        var dataFilePath     = defaultHDataPath + date + ".json";
+        
+        if (!Directory.Exists(defaultHDataPath))
+        {
+            Directory.CreateDirectory(defaultHDataPath);
+            Console.WriteLine("Directory created successfully.");
+        }
+        Console.WriteLine("data file path is " + dataFilePath);
+        
+        try
+        {
+            // 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)
+        {
+            $"Failed to write config file {dataFilePath}\n{e}".WriteLine();
+            throw;
+        }
+    }
+    
+    public static void DeleteDailyData(String directory)
+    {
+        
+        var jsonFiles            = Directory.GetFiles(directory, "*.json");
+        foreach (var jsonFile in jsonFiles)
+        {
+            File.Delete(jsonFile);
+            Console.WriteLine($"Deleted daily data file: {jsonFile}");
+        }
+    }
+    
+    public async Task<Boolean> PushToS3()
+    {
+        var jsonString = JsonConvert.SerializeObject(this, Formatting.None);
+        if (_S3Config is null)
+            return false;
+
+        var s3Path   = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd") + ".json";
+        var request  = _S3Config.CreatePutRequest(s3Path);
+        
+        // Compress CSV data to a byte array
+        byte[] compressedBytes;
+        using (var memoryStream = new MemoryStream())
+        {
+            //Create a zip directory and put the compressed file inside
+            using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
+            {
+                var entry = archive.CreateEntry("data.json", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
+                using (var entryStream = entry.Open())
+                using (var writer = new StreamWriter(entryStream))
+                {
+                    writer.Write(jsonString);
+                }
+            }
+
+            compressedBytes = memoryStream.ToArray();
+        }
+
+        // Encode the compressed byte array as a Base64 string
+        string base64String = Convert.ToBase64String(compressedBytes);
+
+        // Create StringContent from Base64 string
+        var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64");
+
+        // Upload the compressed data (ZIP archive) to S3
+        var response = await request.PutAsync(stringContent);
+        
+
+        if (response.StatusCode != 200)
+        {
+            Console.WriteLine("ERROR: PUT");
+            var error = await response.GetStringAsync();
+            Console.WriteLine(error);
+            return false;
+        }
+
+        return true;
+    }
+    
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/DataTypes/AlarmOrWarning.cs b/csharp/App_backup/SodiStoreMax/src/DataTypes/AlarmOrWarning.cs
new file mode 100644
index 000000000..762caad37
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/DataTypes/AlarmOrWarning.cs
@@ -0,0 +1,9 @@
+namespace InnovEnergy.App.SodiStoreMax.DataTypes;
+
+public class AlarmOrWarning
+{
+    public String? Date                   { get; set; }
+    public String? Time                   { get; set; }
+    public String? Description            { get; set; }
+    public String? CreatedBy              { get; set; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/DataTypes/Configuration.cs b/csharp/App_backup/SodiStoreMax/src/DataTypes/Configuration.cs
new file mode 100644
index 000000000..8610e2601
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/DataTypes/Configuration.cs
@@ -0,0 +1,12 @@
+using InnovEnergy.App.SodiStoreMax.SystemConfig;
+
+namespace InnovEnergy.App.SodiStoreMax.DataTypes;
+
+public class Configuration
+{
+    public Double                MinimumSoC                   { get; set; }
+    public Double                GridSetPoint                 { get; set; } 
+    public CalibrationChargeType CalibrationChargeState       { get; set; } 
+    public DateTime              CalibrationChargeDate        { get; set; } 
+}
+
diff --git a/csharp/App_backup/SodiStoreMax/src/DataTypes/StatusMessage.cs b/csharp/App_backup/SodiStoreMax/src/DataTypes/StatusMessage.cs
new file mode 100644
index 000000000..d54d1f586
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/DataTypes/StatusMessage.cs
@@ -0,0 +1,20 @@
+using InnovEnergy.App.SodiStoreMax.Ess;
+
+namespace InnovEnergy.App.SodiStoreMax.DataTypes;
+
+public class StatusMessage
+{
+    public required Int32                    InstallationId  { get; set; }
+    public required Int32                    Product         { get; set; }
+    public required SalimaxAlarmState        Status          { get; set; }
+    public required MessageType              Type            { get; set; }
+    public List<AlarmOrWarning>?             Warnings        { get; set; }
+    public List<AlarmOrWarning>?             Alarms          { get; set; }
+    public Int32                             Timestamp       { get; set; }
+}
+
+public enum MessageType
+{
+    AlarmOrWarning,
+    Heartbit
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Devices/AcPowerDevice.cs b/csharp/App_backup/SodiStoreMax/src/Devices/AcPowerDevice.cs
new file mode 100644
index 000000000..0b1033a05
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Devices/AcPowerDevice.cs
@@ -0,0 +1,8 @@
+using InnovEnergy.Lib.Units.Composite;
+
+namespace InnovEnergy.App.SodiStoreMax.Devices;
+
+public class AcPowerDevice
+{
+    public required AcPower Power { get; init; } 
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Devices/DcPowerDevice.cs b/csharp/App_backup/SodiStoreMax/src/Devices/DcPowerDevice.cs
new file mode 100644
index 000000000..5579c5f12
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Devices/DcPowerDevice.cs
@@ -0,0 +1,8 @@
+using InnovEnergy.Lib.Units.Power;
+
+namespace InnovEnergy.App.SodiStoreMax.Devices;
+
+public class DcPowerDevice
+{
+    public required DcPower Power { get; init; } 
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Devices/DeviceState.cs b/csharp/App_backup/SodiStoreMax/src/Devices/DeviceState.cs
new file mode 100644
index 000000000..6814e10ec
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Devices/DeviceState.cs
@@ -0,0 +1,8 @@
+namespace InnovEnergy.App.SodiStoreMax.Devices;
+
+public enum DeviceState
+{
+    Disabled,
+    Measured,
+    Computed
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Devices/SalimaxDevice.cs b/csharp/App_backup/SodiStoreMax/src/Devices/SalimaxDevice.cs
new file mode 100644
index 000000000..61281bb24
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Devices/SalimaxDevice.cs
@@ -0,0 +1,8 @@
+using InnovEnergy.Lib.Utils.Net;
+
+namespace InnovEnergy.App.SodiStoreMax.Devices;
+
+public class SalimaxDevice : Ip4Address
+{
+    public required DeviceState DeviceState { get; init; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Ess/Controller.cs b/csharp/App_backup/SodiStoreMax/src/Ess/Controller.cs
new file mode 100644
index 000000000..8e62b431a
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Ess/Controller.cs
@@ -0,0 +1,269 @@
+using InnovEnergy.App.SodiStoreMax.SystemConfig;
+using InnovEnergy.Lib.Devices.BatteryDeligreen;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes;
+using InnovEnergy.Lib.Units;
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.SodiStoreMax.Ess;
+
+public static class Controller
+{
+    private static readonly Double MaxDischargePower   = -4000; //  By battery  TODO: move to config
+    private static readonly Double MaxChargePower      = 3500; //  By battery  TODO: move to config
+                                                                       
+    public static EssMode SelectControlMode(this StatusRecord s)
+    {
+        //return EssMode.OptimizeSelfConsumption;
+        
+        return s.StateMachine.State != 23  ? EssMode.Off
+             : s.MustReachMinSoc()         ? EssMode.ReachMinSoc
+             : s.GridMeter is null         ? EssMode.NoGridMeter
+             :                               EssMode.OptimizeSelfConsumption;
+    }
+
+
+    public static EssControl ControlEss(this StatusRecord s)
+    {
+        var mode = s.SelectControlMode().WriteLine();
+
+        if (mode is EssMode.Off) // to test on prototype
+        {
+            if (s.StateMachine.State == 28 )
+            {
+                return new EssControl
+                {
+                    LimitedBy       = EssLimit.NoLimit,
+                    Mode            = EssMode.OffGrid,
+                    PowerCorrection = 0,
+                    PowerSetpoint   = 0 
+                };
+            }
+            return EssControl.Default;
+        }
+
+        // if we have no reading from the Grid meter, but we have a grid power (K1 is close),
+        // then we do only heat the battery to avoid discharging the battery and the oscillation between reach min soc and off mode
+        if (mode is EssMode.NoGridMeter)
+            return new EssControl
+            {
+                LimitedBy       = EssLimit.NoLimit,
+                Mode            = EssMode.NoGridMeter,
+                PowerCorrection = 0,
+                PowerSetpoint   = 0, //s.Battery == null ? 1000 : s.Battery.Devices.Count * s.Config.BatterySelfDischargePower // 1000 default value for heating the battery
+            };
+
+        var essDelta = s.ComputePowerDelta(mode);
+
+        var unlimitedControl = new EssControl
+        {
+            Mode            = mode,
+            LimitedBy       = EssLimit.NoLimit,
+            PowerCorrection = essDelta,
+            PowerSetpoint   = 0
+        };
+        
+        var limitedControl = unlimitedControl
+                            .LimitChargePower(s)
+                            .LimitDischargePower(s)
+                            .LimitInverterPower(s);
+
+        var currentPowerSetPoint = s.CurrentPowerSetPoint();
+
+        return limitedControl with { PowerSetpoint = currentPowerSetPoint + limitedControl.PowerCorrection };
+    }
+
+    private static EssControl LimitInverterPower(this EssControl control, StatusRecord s)
+    {
+        var powerDelta = control.PowerCorrection.Value;
+        
+        var acDcs = s.AcDc.Devices;
+
+        var nInverters = acDcs.Count;
+        
+        if (nInverters < 2)
+            return control;   // current loop cannot happen 
+
+        var nominalPower = acDcs.Average(d => d.Status.Nominal.Power);
+        var maxStep = nominalPower / 25; //TODO magic number to config
+        
+        var clampedPowerDelta = powerDelta.Clamp(-maxStep, maxStep);
+        
+        var dcLimited = acDcs.Any(d => d.Status.PowerLimitedBy == PowerLimit.DcLink);
+
+        if (!dcLimited)
+            return control with { PowerCorrection = clampedPowerDelta };
+
+        var maxPower = acDcs.Max(d => d.Status.Ac.Power.Active.Value);
+        var minPower = acDcs.Min(d => d.Status.Ac.Power.Active.Value);
+        
+        var powerDifference = maxPower - minPower;
+
+        if (powerDifference < maxStep)
+            return control with { PowerCorrection = clampedPowerDelta };
+
+        var correction = powerDifference / 4; //TODO magic number to config
+        
+        
+        // find out if we reach the lower or upper Dc limit by comparing the current Dc voltage to the reference voltage
+        return s.AcDc.Dc.Voltage > s.Config.GridTie.AcDc.ReferenceDcLinkVoltage
+             ? control with { PowerCorrection = clampedPowerDelta.ClampMax(-correction), LimitedBy = EssLimit.ChargeLimitedByMaxDcBusVoltage }
+             : control with { PowerCorrection = clampedPowerDelta.ClampMin(correction), LimitedBy = EssLimit.DischargeLimitedByMinDcBusVoltage };
+    }
+
+   
+    private static EssControl LimitChargePower(this EssControl control, StatusRecord s)
+    {
+                
+        //var maxInverterChargePower = s.ControlInverterPower(s.Config.MaxInverterPower);
+        var maxBatteryChargePower  = s.MaxBatteryChargePower();
+        maxBatteryChargePower.WriteLine(" Max Battery Charge Power");
+        
+        return control
+              //.LimitChargePower(, EssLimit.ChargeLimitedByInverterPower)
+              .LimitChargePower(maxBatteryChargePower, EssLimit.ChargeLimitedByBatteryPower);
+        
+    }
+    
+    private static EssControl LimitDischargePower(this EssControl control, StatusRecord s)
+    {
+        var maxBatteryDischargeDelta = s.Battery?.Devices.Count * MaxDischargePower ?? 0; 
+        var keepMinSocLimitDelta     = s.ControlBatteryPower(s.HoldMinSocPower());
+        maxBatteryDischargeDelta.WriteLine(" Max Battery Discharge Power");
+
+
+        return control
+              .LimitDischargePower(maxBatteryDischargeDelta , EssLimit.DischargeLimitedByBatteryPower)
+              .LimitDischargePower(keepMinSocLimitDelta     , EssLimit.DischargeLimitedByMinSoc);
+    }
+
+    private static Double ComputePowerDelta(this StatusRecord s, EssMode mode)
+    {
+        var chargePower = s.AcDc.Devices.Sum(d => d.Status.Nominal.Power.Value);
+        
+        return mode switch
+        {
+            EssMode.ReachMinSoc              => s.ControlInverterPower(chargePower),
+            EssMode.OptimizeSelfConsumption  => s.ControlGridPower(s.Config.GridSetPoint),
+            EssMode.Off                      => 0,
+            EssMode.OffGrid                  => 0,
+            EssMode.NoGridMeter              => 0,
+            _                                => throw new ArgumentException(null, nameof(mode))
+        };
+    }
+
+    // private static Boolean MustHeatBatteries(this StatusRecord s)
+   // {
+   //     var batteries = s.GetBatteries();
+//
+   //     if (batteries.Count <= 0)
+   //         return true; // batteries might be there but BMS is without power 
+//
+   //     return batteries
+   //           .Select(b => b.Temperatures.State)
+   //           .Contains(TemperatureState.Cold);
+   // }
+
+    private static Double MaxBatteryChargePower(this StatusRecord s)
+    {
+        // This introduces a limit when we don't have communication with batteries
+        // Otherwise the limit will be 0 and the batteries will be not heated
+                                
+        var batteries = s.GetBatteries();
+        
+        var maxChargePower = batteries.Count == 0 
+                           ? 0
+                           : batteries.Count * MaxChargePower;
+                
+        return maxChargePower;
+    }
+
+    private static Double CurrentPowerSetPoint(this StatusRecord s)
+    {
+        return s
+              .AcDc
+              .Devices
+              .Select(d =>
+              {
+                  var acPowerControl = d.Control.Ac.Power;
+                  
+                  return acPowerControl.L1.Active 
+                       + acPowerControl.L2.Active 
+                       + acPowerControl.L3.Active;
+              })
+              .Sum(p => p);
+    }
+
+    private static Boolean MustReachMinSoc(this StatusRecord s)
+    {
+        var batteries = s.GetBatteries();
+        
+        return batteries.Count > 0
+            && batteries.Any(b => b.BatteryDeligreenDataRecord.Soc < s.Config.MinSoc);
+    }
+
+    private static IReadOnlyList<BatteryDeligreenRecord> GetBatteries(this StatusRecord s)
+    {
+        return s.Battery?.Devices ?? Array.Empty<BatteryDeligreenRecord>();
+    }
+    
+    private static Double ControlGridPower(this StatusRecord status, Double targetPower)
+    {
+        return ControlPower
+        (
+            measurement : status.GridMeter!.Ac.Power.Active,
+            target      : targetPower,
+            pConstant   : status.Config.PConstant
+        );
+    }
+
+    private static Double ControlInverterPower(this StatusRecord status, Double targetInverterPower)
+    {
+        return ControlPower
+        (
+            measurement : status.AcDc.Ac.Power.Active,
+            target      : targetInverterPower,
+            pConstant   : status.Config.PConstant
+        );
+    }
+
+    private static Double ControlBatteryPower(this StatusRecord status, Double targetBatteryPower)
+    {
+        return ControlPower
+        (
+            measurement: status.GetBatteries().Sum(b => b.BatteryDeligreenDataRecord.Power),
+            target: targetBatteryPower,
+            pConstant: status.Config.PConstant
+        );
+    }
+
+    private static Double HoldMinSocPower(this StatusRecord s)
+    {
+        // TODO: explain LowSOC curve
+
+        var batteries = s.GetBatteries();
+
+        if (batteries.Count == 0)
+            return Double.NegativeInfinity;
+
+        var a = -2 * s.Config.BatterySelfDischargePower * batteries.Count / s.Config.HoldSocZone;
+        var b = -a * (s.Config.MinSoc + s.Config.HoldSocZone);
+
+        return batteries.Min(d => d.BatteryDeligreenDataRecord.Soc.Value) * a + b;
+    }
+
+    private static Double ControlPower(Double measurement, Double target, Double pConstant)
+    {
+        var error = target - measurement;
+        return error * pConstant;
+    }
+
+    // ReSharper disable once UnusedMember.Local, TODO
+    private static Double ControlPowerWithIntegral(Double measurement, Double target, Double p, Double i)
+    {
+        var errorSum = 0; // this is must be sum of error
+        var error = target - measurement;
+        var kp = p * error;
+        var ki = i * errorSum;
+        return ki + kp;
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Ess/EssControl.cs b/csharp/App_backup/SodiStoreMax/src/Ess/EssControl.cs
new file mode 100644
index 000000000..66c3b3681
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Ess/EssControl.cs
@@ -0,0 +1,53 @@
+using InnovEnergy.Lib.Units.Power;
+
+namespace InnovEnergy.App.SodiStoreMax.Ess;
+
+public record EssControl
+{
+    public required EssMode     Mode            { get; init; } 
+    public required EssLimit    LimitedBy       { get; init; } 
+    public required ActivePower PowerCorrection { get; init; }
+    public required ActivePower PowerSetpoint   { get; init; }
+
+    public static EssControl Default { get; } = new()
+    {
+        Mode            = EssMode.Off,
+        LimitedBy       = EssLimit.NoLimit,
+        PowerCorrection = 0,
+        PowerSetpoint   = 0
+    };
+    
+    
+    public EssControl LimitChargePower(Double controlDelta, EssLimit reason)
+    {
+        var overload = PowerCorrection - controlDelta;
+
+        if (overload <= 0)
+            return this;
+        
+        return this with
+        {
+            LimitedBy       = reason,
+            PowerCorrection = controlDelta,
+            PowerSetpoint   = PowerSetpoint - overload
+        };
+    }
+
+    public EssControl LimitDischargePower(Double controlDelta, EssLimit reason)
+    {
+        var overload = PowerCorrection - controlDelta;
+
+        if (overload >= 0)
+            return this;
+        
+        return this with
+        {
+            LimitedBy = reason, 
+            PowerCorrection = controlDelta,
+            PowerSetpoint = PowerSetpoint - overload
+        };
+    }
+}
+
+
+    
diff --git a/csharp/App_backup/SodiStoreMax/src/Ess/EssLimit.cs b/csharp/App_backup/SodiStoreMax/src/Ess/EssLimit.cs
new file mode 100644
index 000000000..4a814a790
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Ess/EssLimit.cs
@@ -0,0 +1,20 @@
+namespace InnovEnergy.App.SodiStoreMax.Ess;
+
+public enum EssLimit
+{
+    NoLimit,
+    DischargeLimitedByMinSoc,
+    DischargeLimitedByBatteryPower,
+    DischargeLimitedByInverterPower,
+    ChargeLimitedByInverterPower,
+    ChargeLimitedByBatteryPower,
+    ChargeLimitedByMaxDcBusVoltage,
+    DischargeLimitedByMinDcBusVoltage,
+}
+
+
+// limitedBy = $"limiting discharging power in order to stay above min SOC: {s.Config.MinSoc}%";
+// limitedBy = $"limited by max battery discharging power: {maxDischargePower}";
+// limitedBy = $"limited by max inverter Dc to Ac power: {-s.Config.MaxInverterPower}W";
+// limitedBy = $"limited by max battery charging power: {maxChargePower}";
+// limitedBy = "limited by max inverter Ac to Dc power";
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Ess/EssMode.cs b/csharp/App_backup/SodiStoreMax/src/Ess/EssMode.cs
new file mode 100644
index 000000000..c81c5953c
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Ess/EssMode.cs
@@ -0,0 +1,12 @@
+namespace InnovEnergy.App.SodiStoreMax.Ess;
+
+public enum EssMode
+{
+    Off,
+    OffGrid,
+    HeatBatteries,
+    CalibrationCharge,
+    ReachMinSoc,
+    NoGridMeter,
+    OptimizeSelfConsumption
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Ess/SalimaxAlarmState.cs b/csharp/App_backup/SodiStoreMax/src/Ess/SalimaxAlarmState.cs
new file mode 100644
index 000000000..1cd7b3bd6
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Ess/SalimaxAlarmState.cs
@@ -0,0 +1,8 @@
+namespace InnovEnergy.App.SodiStoreMax.Ess;
+
+public enum SalimaxAlarmState
+{
+    Green,
+    Orange,
+    Red
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Ess/StatusRecord.cs b/csharp/App_backup/SodiStoreMax/src/Ess/StatusRecord.cs
new file mode 100644
index 000000000..151ee0ff4
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Ess/StatusRecord.cs
@@ -0,0 +1,33 @@
+using InnovEnergy.App.SodiStoreMax.Devices;
+using InnovEnergy.App.SodiStoreMax.SaliMaxRelays;
+using InnovEnergy.App.SodiStoreMax.System;
+using InnovEnergy.App.SodiStoreMax.SystemConfig;
+using InnovEnergy.Lib.Devices.AMPT;
+using InnovEnergy.Lib.Devices.BatteryDeligreen;
+using InnovEnergy.Lib.Devices.EmuMeter;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
+
+namespace InnovEnergy.App.SodiStoreMax.Ess;
+
+public record StatusRecord
+{   
+    public required AcDcDevicesRecord        AcDc                { get; init; }  
+    public required DcDcDevicesRecord        DcDc                { get; init; }  
+    public required BatteryDeligreenRecords? Battery             { get; init; } 
+    public required EmuMeterRegisters?       GridMeter           { get; init; } 
+    public required EmuMeterRegisters?       LoadOnAcIsland      { get; init; } 
+    public required AcPowerDevice?           LoadOnAcGrid        { get; init; } 
+    public required AmptStatus?              PvOnAcGrid          { get; init; } 
+    public required AmptStatus?              PvOnAcIsland        { get; init; } 
+    public required AcPowerDevice?           AcGridToAcIsland    { get; init; } 
+    public required DcPowerDevice?           AcDcToDcLink        { get; init; } 
+    public required DcPowerDevice?           LoadOnDc            { get; init; } 
+    public required IRelaysRecord?           Relays              { get; init; } 
+    public required AmptStatus?              PvOnDc              { get; init; } 
+    public required Config                   Config              { get; set; } 
+    public required SystemLog                Log                 { get; init; }    // TODO: init only
+    
+    public required EssControl               EssControl          { get; set; }    // TODO: init only
+    public required StateMachine             StateMachine        { get; init; } 
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Ess/SystemLog.cs b/csharp/App_backup/SodiStoreMax/src/Ess/SystemLog.cs
new file mode 100644
index 000000000..71b58a9af
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Ess/SystemLog.cs
@@ -0,0 +1,11 @@
+using InnovEnergy.App.SodiStoreMax.DataTypes;
+
+namespace InnovEnergy.App.SodiStoreMax.Ess;
+
+public record SystemLog
+{
+    public required String?               Message                { get; init; }
+    public required SalimaxAlarmState     SalimaxAlarmState      { get; init; }
+    public required List<AlarmOrWarning>? SalimaxAlarms          { get; set; }
+    public required List<AlarmOrWarning>? SalimaxWarnings        { get; set; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Flow.cs b/csharp/App_backup/SodiStoreMax/src/Flow.cs
new file mode 100644
index 000000000..99e0ff452
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Flow.cs
@@ -0,0 +1,56 @@
+using System.Diagnostics.CodeAnalysis;
+using InnovEnergy.Lib.Units;
+using InnovEnergy.Lib.Utils;
+
+
+namespace InnovEnergy.App.SodiStoreMax;
+
+public static class Flow
+{
+    private static readonly String RightArrowChar   = ">";
+    private static readonly String LeftArrowChar    = "<";
+    private static readonly String DownArrowChar    = "V";
+    private static readonly String UpArrowChar      = "^";
+    private static readonly String UnknownArrowChar = "?";
+
+    public static TextBlock Horizontal(Unit? amount) => Horizontal(amount, 10);
+
+    public static TextBlock Horizontal(Unit? amount, Int32 width)
+    {
+        var label = amount?.ToDisplayString() ?? "";
+        
+        var arrowChar = amount switch
+        {
+            { Value: < 0  } => LeftArrowChar,
+            { Value: >= 0 } => RightArrowChar,
+            _               => UnknownArrowChar,
+        };
+        
+        //var arrowChar = amount.Value < 0 ? LeftArrowChar : RightArrowChar;
+        var arrow     = Enumerable.Repeat(arrowChar, width).Join();
+
+        // note : appending "fake label" below to make it vertically symmetric
+        return TextBlock.AlignCenterHorizontal(label, arrow, ""); 
+    }
+
+    public static TextBlock Vertical(Unit? amount) => Vertical(amount, 4);
+
+    [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
+    [SuppressMessage("ReSharper", "CoVariantArrayConversion")]
+    public static TextBlock Vertical(Unit? amount, Int32 height)
+    {
+        var label     = amount?.ToDisplayString() ?? UnknownArrowChar;
+        var arrowChar = amount switch
+        {
+            { Value: < 0  } => UpArrowChar,
+            { Value: >= 0 } => DownArrowChar,
+            _               => UnknownArrowChar,
+        };
+        
+        // var arrowChar =  amount is null ? UnknownArrowChar
+        //                : amount.Value < 0 ? UpArrowChar 
+        //                : DownArrowChar;
+        
+        return TextBlock.AlignCenterHorizontal(arrowChar, arrowChar, label, arrowChar, arrowChar);
+    }
+}
diff --git a/csharp/App_backup/SodiStoreMax/src/LogFileConcatenator.cs b/csharp/App_backup/SodiStoreMax/src/LogFileConcatenator.cs
new file mode 100644
index 000000000..c5e63f6f4
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/LogFileConcatenator.cs
@@ -0,0 +1,34 @@
+using System.Text;
+
+namespace InnovEnergy.App.SodiStoreMax;
+
+public class LogFileConcatenator
+{
+    private readonly string _logDirectory;
+
+    public LogFileConcatenator(String logDirectory = "JsonLogDirectory/")
+    {
+        _logDirectory = logDirectory;
+    }
+
+    public String ConcatenateFiles(int numberOfFiles)
+    {
+        var logFiles = Directory
+            .GetFiles(_logDirectory, "log_*.json")
+            .OrderByDescending(file => file)
+            .Take(numberOfFiles)
+            .OrderBy(file => file)
+            .ToList();
+
+        var concatenatedContent = new StringBuilder();
+
+        foreach (var fileContent in logFiles.Select(File.ReadAllText))
+        {
+            concatenatedContent.AppendLine(fileContent);
+            //concatenatedContent.AppendLine(); // Append an empty line to separate the files // maybe we don't need this 
+        }
+
+        return concatenatedContent.ToString();
+    }
+}
+
diff --git a/csharp/App_backup/SodiStoreMax/src/Logfile.cs b/csharp/App_backup/SodiStoreMax/src/Logfile.cs
new file mode 100644
index 000000000..75739b56d
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Logfile.cs
@@ -0,0 +1,49 @@
+using InnovEnergy.Lib.Utils;
+using Microsoft.Extensions.Logging;
+
+namespace InnovEnergy.App.SodiStoreMax;
+
+public class CustomLogger : ILogger
+{
+    private readonly String _logFilePath;
+    //private readonly Int64  _maxFileSizeBytes;
+    private readonly Int32  _maxLogFileCount;
+    private          Int64  _currentFileSizeBytes;
+
+    public CustomLogger(String logFilePath, Int32 maxLogFileCount)
+    {
+        _logFilePath          = logFilePath;
+        _maxLogFileCount      = maxLogFileCount;
+        _currentFileSizeBytes = File.Exists(logFilePath) ? new FileInfo(logFilePath).Length : 0;
+    }
+
+    public IDisposable? BeginScope<TState>(TState state) where TState : notnull => throw new NotImplementedException();
+
+    public Boolean IsEnabled(LogLevel logLevel) => true; // Enable logging for all levels
+
+    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception, String> formatter)
+    {
+        var logMessage = formatter(state, exception!);
+        
+        // Check the log file count and delete the oldest file if necessary
+        var logFileDir      = Path.GetDirectoryName(_logFilePath)!;
+        var logFileExt      = Path.GetExtension(_logFilePath);
+        var logFileBaseName = Path.GetFileNameWithoutExtension(_logFilePath);
+        
+        var logFiles = Directory
+            .GetFiles(logFileDir, $"{logFileBaseName}_*{logFileExt}")
+            .OrderBy(file => file)
+            .ToList();
+
+        if (logFiles.Count >= _maxLogFileCount)
+        {
+            File.Delete(logFiles.First());
+        }
+
+        var roundedUnixTimestamp = DateTime.Now.ToUnixTime() % 2 == 0 ? DateTime.Now.ToUnixTime() : DateTime.Now.ToUnixTime() + 1;
+        var timestamp = "Timestamp;" + roundedUnixTimestamp + Environment.NewLine;
+        
+        var logFileBackupPath = Path.Combine(logFileDir, $"{logFileBaseName}_{DateTime.Now.ToUnixTime()}{logFileExt}");
+        File.AppendAllText(logFileBackupPath, timestamp + logMessage + Environment.NewLine);
+    }
+}
diff --git a/csharp/App_backup/SodiStoreMax/src/Logger.cs b/csharp/App_backup/SodiStoreMax/src/Logger.cs
new file mode 100644
index 000000000..8b6a74252
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Logger.cs
@@ -0,0 +1,40 @@
+using InnovEnergy.App.SodiStoreMax;
+using Microsoft.Extensions.Logging;
+
+namespace InnovEnergy.App.SodiStoreMax;
+
+public static class Logger
+{
+    // Specify the maximum log file size in bytes (e.g., 1 MB)
+    
+    //private const Int32  MaxFileSizeBytes = 2024 * 30;       // TODO: move to settings
+    private const Int32  MaxLogFileCount = 5000;               // TODO: move to settings
+    private const String LogFilePath = "JsonLogDirectory/log.json"; // TODO: move to settings
+    
+    // ReSharper disable once InconsistentNaming
+    private static readonly ILogger _logger = new CustomLogger(LogFilePath,  MaxLogFileCount);
+
+    public static T LogInfo<T>(this T t) where T : notnull
+    {
+        _logger.LogInformation(t.ToString());  // TODO: check warning
+        return t;
+    }
+    
+    public static T LogDebug<T>(this T t) where T : notnull
+    {
+        _logger.LogDebug(t.ToString());  // TODO: check warning
+        return t;
+    }
+    
+    public static T LogError<T>(this T t) where T : notnull
+    {
+        _logger.LogError(t.ToString());  // TODO: check warning
+        return t;
+    }
+    
+    public static T LogWarning<T>(this T t) where T : notnull
+    {
+        _logger.LogWarning(t.ToString());  // TODO: check warning
+        return t;
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/MiddlewareClasses/MiddlewareAgent.cs b/csharp/App_backup/SodiStoreMax/src/MiddlewareClasses/MiddlewareAgent.cs
new file mode 100644
index 000000000..6e1bd8c36
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/MiddlewareClasses/MiddlewareAgent.cs
@@ -0,0 +1,93 @@
+using System.Net;
+using System.Net.NetworkInformation;
+using System.Net.Sockets;
+using System.Text;
+using System.Text.Json;
+using InnovEnergy.App.SodiStoreMax.DataTypes;
+
+namespace InnovEnergy.App.SodiStoreMax.MiddlewareClasses;
+
+public static class MiddlewareAgent
+{
+    private static UdpClient   _udpListener = null!;
+    private static IPAddress? _controllerIpAddress;
+    private static EndPoint? _endPoint;
+    
+    public static void InitializeCommunicationToMiddleware()
+    {
+        _controllerIpAddress = FindVpnIp();
+        if (Equals(IPAddress.None, _controllerIpAddress))
+        {
+            Console.WriteLine("There is no VPN interface, exiting...");
+        }
+
+        const Int32 udpPort = 9000;
+        _endPoint = new IPEndPoint(_controllerIpAddress, udpPort);
+
+        _udpListener = new UdpClient();
+        _udpListener.Client.Blocking = false;
+        _udpListener.Client.Bind(_endPoint);
+    }
+    
+    private static IPAddress FindVpnIp()
+    {
+        const String interfaceName = "innovenergy";
+        
+        var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
+        
+        foreach (var networkInterface in networkInterfaces)
+        {
+            if (networkInterface.Name == interfaceName)
+            {
+                var ipProps         = networkInterface.GetIPProperties();
+                var uniCastIPs      = ipProps.UnicastAddresses;
+                var controllerIpAddress = uniCastIPs[0].Address;
+
+                Console.WriteLine("VPN IP is: "+ uniCastIPs[0].Address);
+                return controllerIpAddress;
+            }
+        }
+
+        return IPAddress.None;
+    }
+
+    public static Configuration? SetConfigurationFile()
+    {
+        if (_udpListener.Available > 0)
+        {
+
+            IPEndPoint? serverEndpoint = null;
+
+            var replyMessage = "ACK";
+            var replyData = Encoding.UTF8.GetBytes(replyMessage);
+
+            var udpMessage = _udpListener.Receive(ref serverEndpoint);
+            var message = Encoding.UTF8.GetString(udpMessage);
+
+            var config = JsonSerializer.Deserialize<Configuration>(message);
+
+            if (config != null)
+            {
+                Console.WriteLine($"Received a configuration message: GridSetPoint is " + config.GridSetPoint +
+                                  ", MinimumSoC is " + config.MinimumSoC + " and ForceCalibrationCharge is " +
+                                  config.CalibrationChargeState + " and CalibrationChargeDate is " +
+                                  config.CalibrationChargeDate);
+
+                // Send the reply to the sender's endpoint
+                _udpListener.Send(replyData, replyData.Length, serverEndpoint);
+                Console.WriteLine($"Replied to {serverEndpoint}: {replyMessage}");
+                return config;
+            }
+        }
+
+        if (_endPoint != null && !_endPoint.Equals(_udpListener.Client.LocalEndPoint as IPEndPoint))
+        {
+            Console.WriteLine("UDP address has changed, rebinding...");
+            InitializeCommunicationToMiddleware();
+        }
+        
+
+        return null;
+    }
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/MiddlewareClasses/RabbitMQManager.cs b/csharp/App_backup/SodiStoreMax/src/MiddlewareClasses/RabbitMQManager.cs
new file mode 100644
index 000000000..2de9f5665
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/MiddlewareClasses/RabbitMQManager.cs
@@ -0,0 +1,61 @@
+using System.Text;
+using System.Text.Json;
+using InnovEnergy.App.SodiStoreMax.DataTypes;
+using RabbitMQ.Client;
+
+namespace InnovEnergy.App.SodiStoreMax.MiddlewareClasses;
+
+public static class RabbitMqManager
+{
+    public static ConnectionFactory?  Factory ;
+    public static IConnection ?       Connection;
+    public static IModel?             Channel;
+    
+    public static Boolean SubscribeToQueue(StatusMessage currentSalimaxState, String? s3Bucket,String VpnServerIp)
+    {
+        try
+        {
+            //_factory    = new ConnectionFactory { HostName = VpnServerIp };
+            
+            Factory = new ConnectionFactory
+            {
+                HostName = VpnServerIp,
+                Port = 5672,
+                VirtualHost = "/",
+                UserName = "producer",
+                Password = "b187ceaddb54d5485063ddc1d41af66f",
+               
+            };
+            
+            Connection = Factory.CreateConnection();
+            Channel    = Connection.CreateModel();
+            Channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
+            
+            Console.WriteLine("The controller sends its status to the middleware for the first time");
+            if (s3Bucket != null) InformMiddleware(currentSalimaxState);
+    
+            
+        }
+        catch (Exception ex)
+        {
+            Console.WriteLine("An error occurred while connecting to the RabbitMQ queue: " + ex.Message);
+            return false;
+        }
+        return true;
+    }
+    
+    public static void  InformMiddleware(StatusMessage status)
+    {        
+        var message = JsonSerializer.Serialize(status);
+        var body    = Encoding.UTF8.GetBytes(message);
+
+        Channel.BasicPublish(exchange: string.Empty,
+            routingKey: "statusQueue",
+            basicProperties: null,
+            body: body);
+        
+        Console.WriteLine($"Producer sent message: {message}");
+    }
+
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Program.cs b/csharp/App_backup/SodiStoreMax/src/Program.cs
new file mode 100644
index 000000000..ec9f2e542
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Program.cs
@@ -0,0 +1,945 @@
+#define Amax
+#undef GridLimit
+
+using System.Diagnostics;
+using System.IO.Compression;
+using System.Reactive.Linq;
+using System.Reactive.Threading.Tasks;
+using System.Reflection.Metadata;
+using System.Security;
+using System.Text;
+using Flurl.Http;
+using InnovEnergy.App.SodiStoreMax;
+using InnovEnergy.App.SodiStoreMax.Devices;
+using InnovEnergy.App.SodiStoreMax.Ess;
+using InnovEnergy.App.SodiStoreMax.MiddlewareClasses;
+using InnovEnergy.App.SodiStoreMax.SaliMaxRelays;
+using InnovEnergy.App.SodiStoreMax.System;
+using InnovEnergy.App.SodiStoreMax.SystemConfig;
+using InnovEnergy.Lib.Devices.AMPT;
+using InnovEnergy.Lib.Devices.BatteryDeligreen;
+using InnovEnergy.Lib.Devices.EmuMeter;
+using InnovEnergy.Lib.Devices.Trumpf.SystemControl;
+using InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.DataTypes;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc.Control;
+using InnovEnergy.Lib.Protocols.Modbus.Channels;
+using InnovEnergy.Lib.Units;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.App.SodiStoreMax.DataTypes;
+using InnovEnergy.Lib.Utils.Net;
+using static System.Int32;
+using static InnovEnergy.App.SodiStoreMax.AggregationService.Aggregator;
+using static InnovEnergy.App.SodiStoreMax.MiddlewareClasses.MiddlewareAgent;
+using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.SystemConfig;
+using DeviceState = InnovEnergy.App.SodiStoreMax.Devices.DeviceState;
+
+#pragma warning disable IL2026
+
+namespace InnovEnergy.App.SodiStoreMax;
+
+internal static class Program
+{
+    private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(2);
+
+    private static readonly IReadOnlyList<Byte> BatteryNodes;
+
+    private static readonly Channel TruConvertAcChannel;
+    private static readonly Channel TruConvertDcChannel;
+    private static readonly Channel GridMeterChannel;
+    private static readonly Channel IslandBusLoadChannel;
+    private static readonly Channel PvOnDc;
+    private static readonly Channel PvOnAcGrid;
+    private static readonly Channel PvOnAcIsland;
+    private static readonly Channel RelaysChannel;
+    private static readonly Channel BatteriesChannel;
+
+    private static Boolean              _curtailFlag                    = false;
+    private const  String               VpnServerIp                     = "10.2.0.11";
+    private static Boolean             _subscribedToQueue               = false;
+    private static Boolean             _subscribeToQueueForTheFirstTime = false;
+    private static SalimaxAlarmState   _prevSalimaxState                = SalimaxAlarmState.Green;
+    private const  UInt16              NbrOfFileToConcatenate           = 30;
+    private static UInt16              _counterOfFile                   = 0;
+    private static SalimaxAlarmState   _salimaxAlarmState               = SalimaxAlarmState.Green;
+    private const  String              Port                             = "/dev/ttyUSB0";
+
+    
+    static Program()
+    {
+        var config = Config.Load();
+        var d = config.Devices;
+
+        Channel CreateChannel(SalimaxDevice device) => device.DeviceState == DeviceState.Disabled 
+                                                     ? new NullChannel()
+                                                     : new TcpChannel(device);
+
+
+        TruConvertAcChannel  = CreateChannel(d.TruConvertAcIp);
+        TruConvertDcChannel  = CreateChannel(d.TruConvertDcIp);
+        GridMeterChannel     = CreateChannel(d.GridMeterIp);
+        IslandBusLoadChannel = CreateChannel(d.IslandBusLoadMeterIp);
+        PvOnDc               = CreateChannel(d.PvOnDc);
+        PvOnAcGrid           = CreateChannel(d.PvOnAcGrid);
+        PvOnAcIsland         = CreateChannel(d.PvOnAcIsland);
+        RelaysChannel        = CreateChannel(d.RelaysIp);
+        BatteriesChannel     = CreateChannel(d.BatteryIp);
+        
+        BatteryNodes         = config
+                                .Devices
+                                .BatteryNodes
+                                .Select(n => n.ConvertTo<Byte>())
+                                .ToArray(config.Devices.BatteryNodes.Length);
+    }
+
+    public static async Task Main(String[] args)
+    {
+        //Do not await
+        HourlyDataAggregationManager()
+                        .ContinueWith(t=>t.Exception.WriteLine(), TaskContinuationOptions.OnlyOnFaulted)
+                        .SupressAwaitWarning();
+        
+        DailyDataAggregationManager()
+                        .ContinueWith(t=>t.Exception.WriteLine(), TaskContinuationOptions.OnlyOnFaulted)
+                        .SupressAwaitWarning();
+        
+        InitializeCommunicationToMiddleware();
+        
+        while (true)
+        {
+            try
+            {
+                await Run();
+            }
+            catch (Exception e)
+            {
+                e.LogError();
+            }    
+        }
+    }
+
+
+    private static async Task Run()
+    {
+        "Starting SodiStore Max".WriteLine();
+
+        Watchdog.NotifyReady();
+
+        var batteryDeligreenDevice = BatteryNodes.Select(n => new BatteryDeligreenDevice(Port, n))
+                                                 .ToList();
+        
+        var batteryDevices        = new BatteryDeligreenDevices(batteryDeligreenDevice);
+        var acDcDevices           = new TruConvertAcDcDevices(TruConvertAcChannel);
+        var dcDcDevices           = new TruConvertDcDcDevices(TruConvertDcChannel);
+        var gridMeterDevice       = new EmuMeterDevice(GridMeterChannel);
+        var acIslandLoadMeter     = new EmuMeterDevice(IslandBusLoadChannel); 
+        var pvOnDcDevice          = new AmptDevices(PvOnDc);
+        var pvOnAcGridDevice      = new AmptDevices(PvOnAcGrid);
+        var pvOnAcIslandDevice    = new AmptDevices(PvOnAcIsland);
+        
+#if Amax
+        var saliMaxRelaysDevice = new RelaysDeviceAmax(RelaysChannel);
+#else
+        var saliMaxRelaysDevice = new RelaysDevice(RelaysChannel);
+#endif       
+        
+
+        StatusRecord ReadStatus()
+        {            
+            var config             = Config.Load();
+            var devices            = config.Devices;
+            var acDc               = acDcDevices.Read();
+            var dcDc               = dcDcDevices.Read();
+            var relays             = saliMaxRelaysDevice.Read();
+            var loadOnAcIsland     = acIslandLoadMeter.Read();
+            var gridMeter          = gridMeterDevice.Read();
+            var pvOnDc             = pvOnDcDevice.Read();
+            var battery            = batteryDevices.Read();
+            var pvOnAcGrid         = pvOnAcGridDevice.Read();   
+            var pvOnAcIsland       = pvOnAcIslandDevice.Read(); 
+
+            var gridBusToIslandBus = Topology.CalculateGridBusToIslandBusPower(pvOnAcIsland, loadOnAcIsland, acDc);
+
+            var gridBusLoad = devices.LoadOnAcGrid.DeviceState == DeviceState.Disabled
+                                                                 ? new AcPowerDevice { Power = 0 }
+                                                                 : Topology.CalculateGridBusLoad(gridMeter, pvOnAcGrid, gridBusToIslandBus);
+
+            var dcLoad = devices.LoadOnDc.DeviceState == DeviceState.Disabled
+                                                        ? new DcPowerDevice { Power = 0 }
+                                                        : Topology.CalculateDcLoad(acDc, pvOnDc, dcDc);
+
+            var acDcToDcLink = devices.LoadOnDc.DeviceState == DeviceState.Disabled ?
+                                                              Topology.CalculateAcDcToDcLink(pvOnDc, dcDc, acDc) 
+                                                              : new DcPowerDevice{ Power = acDc.Dc.Power};
+
+
+            return new StatusRecord
+            {
+                AcDc             = acDc,
+                DcDc             = dcDc,
+                Battery          = battery,
+                Relays           = relays,
+                GridMeter        = gridMeter,
+                PvOnAcGrid       = pvOnAcGrid,
+                PvOnAcIsland     = pvOnAcIsland,
+                PvOnDc           = pvOnDc,
+                AcGridToAcIsland = gridBusToIslandBus,
+                AcDcToDcLink     = acDcToDcLink,
+                LoadOnAcGrid     = gridBusLoad,
+                LoadOnAcIsland   = loadOnAcIsland,
+                LoadOnDc         = dcLoad,
+                StateMachine     = StateMachine.Default,
+                EssControl       = EssControl.Default,
+                Log              = new SystemLog { SalimaxAlarmState = SalimaxAlarmState.Green, Message = null, SalimaxAlarms = null, SalimaxWarnings = null}, //TODO: Put real stuff
+                Config           = config // load from disk every iteration, so config can be changed while running
+            };
+        }
+
+        void WriteControl(StatusRecord r)
+        {
+            if (r.Relays is not null)
+            {
+#if Amax
+                saliMaxRelaysDevice.Write((RelaysRecordAmax)r.Relays);
+#else
+                ((RelaysDevice)saliMaxRelaysDevice).Write((RelaysRecord)r.Relays);
+#endif
+
+            }
+            
+            acDcDevices.Write(r.AcDc);
+            dcDcDevices.Write(r.DcDc);
+        }
+
+        Console.WriteLine("press ctrl-c to stop");
+
+        while (true)
+        {
+            await Observable
+                 .Interval(UpdateInterval)
+                 .Select(_ => RunIteration())
+                 .SelectMany(r => UploadCsv(r, DateTime.Now.Round(UpdateInterval)))
+                 .SelectError()
+                 .ToTask();
+        }
+
+
+        StatusRecord RunIteration()
+        {
+            Watchdog.NotifyAlive();
+
+            var record = ReadStatus();
+            
+            SendSalimaxStateAlarm(GetSalimaxStateAlarm(record), record);  // to improve
+              
+            record.ControlConstants();
+            record.ControlSystemState();
+
+            //record.ControlPvPower(record.Config.CurtailP, record.Config.PvInstalledPower);
+
+            var essControl = record.ControlEss().WriteLine();
+
+            record.EssControl = essControl;
+
+            record.AcDc.SystemControl.ApplyAcDcDefaultSettings();
+            record.DcDc.SystemControl.ApplyDcDcDefaultSettings();
+
+            DistributePower(record, essControl);
+            
+            //record.PerformLed();
+                
+            WriteControl(record);
+            
+            $"{DateTime.Now.Round(UpdateInterval).ToUnixTime()} : {record.StateMachine.State}: {record.StateMachine.Message}".WriteLine();
+
+            record.CreateTopologyTextBlock().WriteLine();
+
+            (record.Relays is null ? "No relay Data available" : record.Relays.FiWarning ? "Alert: Fi Warning Detected" : "No Fi Warning Detected").WriteLine();
+            (record.Relays is null ? "No relay Data available" : record.Relays.FiError   ? "Alert: Fi Error Detected"   : "No Fi Error Detected")  .WriteLine();
+
+            record.Config.Save();
+
+            "===========================================".WriteLine();
+
+            return record;
+        }
+
+        // ReSharper disable once FunctionNeverReturns
+    }
+
+    private static void SendSalimaxStateAlarm(StatusMessage currentSalimaxState, StatusRecord record)
+    {
+        var s3Bucket = Config.Load().S3?.Bucket;
+        var subscribedNow = false;
+
+        //Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue
+        //_heartBitInterval++;
+
+        //When the controller boots, it tries to subscribe to the queue
+        if (_subscribeToQueueForTheFirstTime == false)
+        {
+            subscribedNow = true;
+            _subscribeToQueueForTheFirstTime = true;
+            _prevSalimaxState = currentSalimaxState.Status;
+            _subscribedToQueue = RabbitMqManager.SubscribeToQueue(currentSalimaxState, s3Bucket, VpnServerIp);
+        }
+
+        //If already subscribed to the queue and the status has been changed, update the queue
+        if (!subscribedNow && _subscribedToQueue && currentSalimaxState.Status != _prevSalimaxState)
+        {
+            _prevSalimaxState = currentSalimaxState.Status;
+            if (s3Bucket != null)
+                RabbitMqManager.InformMiddleware(currentSalimaxState);
+        }
+        // else if (_subscribedToQueue && _heartBitInterval >= 30)
+        // {
+        //     //Send a heartbit to the backend
+        //     Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
+        //     _heartBitInterval = 0;
+        //     currentSalimaxState.Type = MessageType.Heartbit;
+        //
+        //     if (s3Bucket != null)
+        //         RabbitMqManager.InformMiddleware(currentSalimaxState);
+        // }
+
+        //If there is an available message from the RabbitMQ Broker, apply the configuration file
+        Configuration? config = SetConfigurationFile();
+        if (config != null)
+        {
+            record.ApplyConfigFile(config);
+        }
+    }
+
+    // This preparing a message to send to salimax monitor
+    private static StatusMessage GetSalimaxStateAlarm(StatusRecord record)
+    {
+        var alarmCondition = record.DetectAlarmStates(); // this need to be emailed to support or customer
+        var s3Bucket       = Config.Load().S3?.Bucket;
+
+       var alarmList   = new List<AlarmOrWarning>();
+       var warningList = new List<AlarmOrWarning>();
+       var bAlarmList   = new List<String>();
+       var bWarningList = new List<String>();
+
+       /*
+        if (record.Battery != null)
+        {
+            var i = 0;
+           
+            foreach (var battery in record.Battery.Devices)
+            {
+                var devicesBatteryNode = record.Config.Devices.BatteryNodes[i];
+                
+                if (battery.LimpBitMap == 0)
+                {
+                   // "All String are Active".WriteLine();
+                }
+                else if (IsPowerOfTwo(battery.LimpBitMap))
+                {
+                    "1 String is disabled".WriteLine();
+                    Console.WriteLine(" ****************** ");
+
+                    warningList.Add(new AlarmOrWarning
+                    {
+                        Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                        Time = DateTime.Now.ToString("HH:mm:ss"),
+                        CreatedBy = "Battery node" + devicesBatteryNode,
+                        Description = "1 String is disabled"
+                    });
+                    
+                    bWarningList.Add("/"+i+1 + "/1 String is disabled"); // battery id instead ( i +1 ) of node id: requested from the frontend 
+                }
+                else
+                {
+                    "2 or more string are disabled".WriteLine();
+                    Console.WriteLine(" ****************** ");
+
+                    alarmList.Add(new AlarmOrWarning
+                    {
+                        Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                        Time = DateTime.Now.ToString("HH:mm:ss"),
+                        CreatedBy = "Battery node" + devicesBatteryNode,
+                        Description = "2 or more string are disabled"
+                    });
+                    bAlarmList.Add(i +";2 or more string are disabled");
+                }
+
+                foreach (var warning in record.Battery.Warnings)
+                {
+                    warningList.Add(new AlarmOrWarning
+                    {
+                        Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                        Time = DateTime.Now.ToString("HH:mm:ss"),
+                        CreatedBy = "Battery node" + devicesBatteryNode,
+                        Description = warning
+                    });
+                    bWarningList.Add(i +";" + warning);
+                }
+                
+                foreach (var alarm in battery.Alarms)
+                {
+                    alarmList.Add(new AlarmOrWarning
+                    {
+                        Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                        Time = DateTime.Now.ToString("HH:mm:ss"),
+                        CreatedBy = "Battery node" + devicesBatteryNode,
+                        Description = alarm
+                    });
+                    bWarningList.Add(i +";" + alarm);
+                }
+                i++;
+            }
+        }*/
+        
+        if (alarmCondition is not null)
+        {
+            alarmCondition.WriteLine();
+
+            alarmList.Add(new AlarmOrWarning
+            {
+                Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                Time = DateTime.Now.ToString("HH:mm:ss"),
+                CreatedBy = "Salimax",
+                Description = alarmCondition
+            });
+        }
+          
+        foreach (var alarm in record.AcDc.Alarms)
+        {
+            alarmList.Add(new AlarmOrWarning
+            {
+                Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                Time = DateTime.Now.ToString("HH:mm:ss"),
+                CreatedBy = "AcDc",
+                Description = alarm.ToString()
+            });
+        }
+       
+        foreach (var alarm in record.DcDc.Alarms)
+        {
+            alarmList.Add(new AlarmOrWarning
+            {
+                Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                Time = DateTime.Now.ToString("HH:mm:ss"),
+                CreatedBy = "DcDc",
+                Description = alarm.ToString()
+            });
+        }
+       
+        foreach (var warning in record.AcDc.Warnings)
+        {
+            warningList.Add(new AlarmOrWarning
+            {
+                Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                Time = DateTime.Now.ToString("HH:mm:ss"),
+                CreatedBy = "AcDc",
+                Description = warning.ToString()
+            });
+        }
+       
+        foreach (var warning in record.DcDc.Warnings)
+        {
+            warningList.Add(new AlarmOrWarning
+            {
+                Date = DateTime.Now.ToString("yyyy-MM-dd"),
+                Time = DateTime.Now.ToString("HH:mm:ss"),
+                CreatedBy = "DcDc",
+                Description = warning.ToString()
+            });
+        }
+
+        _salimaxAlarmState = warningList.Any()
+            ? SalimaxAlarmState.Orange
+            : SalimaxAlarmState.Green; // this will be replaced by LedState
+
+        _salimaxAlarmState = alarmList.Any()
+            ? SalimaxAlarmState.Red
+            : _salimaxAlarmState; // this will be replaced by LedState
+
+        TryParse(s3Bucket?.Split("-")[0], out var installationId);
+
+        var returnedStatus = new StatusMessage
+        {
+            InstallationId = installationId,
+            Product        = 0,
+            Status         = _salimaxAlarmState,
+            Type           = MessageType.AlarmOrWarning,
+            Alarms         = alarmList,
+            Warnings       = warningList
+        };
+
+        return returnedStatus;     
+    }
+
+    private static String? DetectAlarmStates(this StatusRecord r) => r.Relays switch
+    {
+        { K2ConnectIslandBusToGridBus: false, K2IslandBusIsConnectedToGridBus: true } => " Contradiction: R0 is opening the K2 but the K2 is still close ",
+        { K1GridBusIsConnectedToGrid : false, K2IslandBusIsConnectedToGridBus: true } => " Contradiction: K1 is open but the K2 is still close ",
+        { FiError: true, K2IslandBusIsConnectedToGridBus: true                      } => " Contradiction: Fi error occured but the K2 is still close ",
+        _                                                                             => null
+    };
+
+    private static void ControlConstants(this StatusRecord r)
+    {
+        var inverters     = r.AcDc.Devices;
+        var dcDevices     = r.DcDc.Devices;
+        var configFile    = r.Config;
+        var devicesConfig = r.AcDc.Devices.All(d => d.Control.Ac.GridType == GridType.GridTied400V50Hz) ? configFile.GridTie : configFile.IslandMode; // TODO if any of the grid tie mode
+
+        Double maxBatteryChargingCurrentLive ;  //used with deligreenBattery for limiting charging
+        Double maxBatteryDischargingCurrentLive; //used with deligreenBattery for limiting discharging
+        
+        
+        //var maxBatteryDischargingCurrentLive = 0.0; //never used with deligreenBattery 
+        /*
+        // This adapting the max discharging current to the current Active Strings
+        if (r.Battery != null)
+        {
+            const Int32 stringsByBattery         = 5;
+            var numberOfBatteriesConfigured      = r.Config.Devices.BatteryNodes.Length;
+            var numberOfTotalStrings             = stringsByBattery * numberOfBatteriesConfigured;
+            var dischargingCurrentByString       = devicesConfig.DcDc.MaxBatteryDischargingCurrent / numberOfTotalStrings;
+            
+            var boolList = new List<Boolean>();
+
+            foreach (var stringActive  in r.Battery.Devices.Select(b => b.BatteryStrings).ToList())
+            {
+                boolList.Add(stringActive.String1Active);
+                boolList.Add(stringActive.String2Active);
+                boolList.Add(stringActive.String3Active);
+                boolList.Add(stringActive.String4Active);
+                boolList.Add(stringActive.String5Active);
+            }
+            
+            var numberOfBatteriesStringActive = boolList.Count(b => b);
+            
+            if (numberOfTotalStrings != 0)
+            {
+                maxBatteryDischargingCurrentLive = dischargingCurrentByString * numberOfBatteriesStringActive;
+            }
+        }
+        */
+        // TODO The discharging current is well calculated but not communicated to live. But Written in S3
+        
+        // Deligreen upper current limitation dynCCL
+        if (r.Battery?.Voltage != null && (r.Battery?.Voltage ?? 0) > 61.0 )
+        {
+            maxBatteryChargingCurrentLive = r.Battery.Devices.Count * 10;  // Max charging current is 10 A * Number of batteries
+            maxBatteryChargingCurrentLive.WriteLine("dynCCL Active: Max Battery Charging is "+ maxBatteryChargingCurrentLive);
+        }
+        else
+        {
+            maxBatteryChargingCurrentLive = devicesConfig.DcDc.MaxBatteryChargingCurrent;
+        }
+        
+        // Deligreen lower current limitation dynDCL
+        if (r.Battery?.Soc != null && (r.Battery?.Soc ?? 100) < r.Config.MinSoc )
+        {
+            maxBatteryDischargingCurrentLive = 0;  // Max charging current is 10 A * Number of batteries
+            maxBatteryDischargingCurrentLive.WriteLine("dynDCL Active:  Max Battery disCharging is "+ maxBatteryDischargingCurrentLive);
+        }
+        else
+        {
+            maxBatteryDischargingCurrentLive = devicesConfig.DcDc.MaxBatteryDischargingCurrent;
+        }
+        
+        inverters.ForEach(d => d.Control.Dc.MaxVoltage                               = devicesConfig.AcDc.MaxDcLinkVoltage);
+        inverters.ForEach(d => d.Control.Dc.MinVoltage                               = devicesConfig.AcDc.MinDcLinkVoltage);
+        inverters.ForEach(d => d.Control.Dc.ReferenceVoltage                         = devicesConfig.AcDc.ReferenceDcLinkVoltage);
+        
+        inverters.ForEach(d => d.Control.Dc.PrechargeConfig                          = DcPrechargeConfig.PrechargeDcWithInternal);
+       
+        dcDevices.ForEach(d => d.Control.DroopControl.UpperVoltage                   = devicesConfig.DcDc.UpperDcLinkVoltage);
+        dcDevices.ForEach(d => d.Control.DroopControl.LowerVoltage                   = devicesConfig.DcDc.LowerDcLinkVoltage);
+        dcDevices.ForEach(d => d.Control.DroopControl.ReferenceVoltage               = devicesConfig.DcDc.ReferenceDcLinkVoltage);
+        
+        dcDevices.ForEach(d => d.Control.CurrentControl.MaxBatteryChargingCurrent    = maxBatteryChargingCurrentLive);
+        dcDevices.ForEach(d => d.Control.CurrentControl.MaxBatteryDischargingCurrent = maxBatteryDischargingCurrentLive);
+        dcDevices.ForEach(d => d.Control.MaxDcPower                                  = devicesConfig.DcDc.MaxDcPower);
+
+        dcDevices.ForEach(d => d.Control.VoltageLimits.MaxBatteryVoltage             = devicesConfig.DcDc.MaxChargeBatteryVoltage);
+        dcDevices.ForEach(d => d.Control.VoltageLimits.MinBatteryVoltage             = devicesConfig.DcDc.MinDischargeBatteryVoltage);
+        dcDevices.ForEach(d => d.Control.ControlMode                                 = DcControlMode.VoltageDroop);
+
+        r.DcDc.ResetAlarms();
+        r.AcDc.ResetAlarms();
+    }
+
+    // This will be used for provider throttling, this example is only for either 100% or 0 %
+    private static void ControlPvPower(this StatusRecord r, UInt16 exportLimit = 0, UInt16 pvInstalledPower = 20)
+    {   
+        // Maybe add a condition to do this only if we are in optimised Self consumption, this is not true
+        
+        if (r.GridMeter?.Ac.Power.Active == null)
+        {
+            Console.WriteLine(" No reading from Grid meter");
+            return;
+        }
+
+        if (pvInstalledPower == 0)
+        {
+            Console.WriteLine(" No curtailing, because Pv installed is equal to 0");
+            return;
+        }
+        
+        const Int32 constantDeadBand = 5000; // magic number
+        const Double voltageRange    = 100;  // 100 Voltage configured rang for PV slope, if the configured slope change this must change also 
+        var configFile               = r.Config;
+        var inverters                = r.AcDc.Devices;
+        var systemExportLimit        = - exportLimit * 1000 ; // Conversion from Kw in W // the config file value is positive and limit should be negative from 0 to ...
+        var stepSize                 = ClampStepSize((UInt16)Math.Floor(voltageRange/ pvInstalledPower));  // in Voltage per 1 Kw
+        var deadBand                 = constantDeadBand/stepSize;
+
+        // LINQ query to select distinct ActiveUpperVoltage
+        var result = r.AcDc.Devices
+                    .Select(device => device?.Status?.DcVoltages?.Active?.ActiveUpperVoltage)
+                    .Select(voltage => voltage.Value)    // Extract the value since we've confirmed it's non-null
+                    .Distinct()
+                    .ToList();
+
+        Double upperVoltage;
+        
+        if (result.Count == 1)
+        {
+            upperVoltage = result[0];
+        }
+        else
+        {
+            Console.WriteLine(" Different ActiveUpperVoltage between inverters "); // this should be reported to salimax Alarm
+            return;
+        }
+        
+        /************* For debugging purposes ********************/ 
+        
+        systemExportLimit.WriteLine(" Export Limit in W");
+        upperVoltage.WriteLine(" Upper Voltage");
+        r.GridMeter.Ac.Power.Active.WriteLine(" Active Export");
+        Console.WriteLine(" ****************** ");
+        
+        /*********************************************************/
+        
+        if (r.GridMeter.Ac.Power.Active < systemExportLimit)
+        {
+            _curtailFlag = true;
+            upperVoltage = IncreaseInverterUpperLimit(upperVoltage, stepSize);
+            upperVoltage.WriteLine("Upper Voltage Increased: New Upper limit");
+        }
+        else
+        {
+            if (_curtailFlag)
+            {
+                if (r.GridMeter.Ac.Power.Active > (systemExportLimit + deadBand))
+                {
+                    upperVoltage = DecreaseInverterUpperLimit(upperVoltage, stepSize);
+
+                    if (upperVoltage <= configFile.GridTie.AcDc.MaxDcLinkVoltage)
+                    {
+                        _curtailFlag     = false;
+                        upperVoltage     = configFile.GridTie.AcDc.MaxDcLinkVoltage;
+                        upperVoltage.WriteLine(" New Upper limit");
+                        Console.WriteLine("Upper Voltage decreased: Smaller than the default value, value clamped");
+                    }
+                    else
+                    {
+                        Console.WriteLine("Upper Voltage decreased: New Upper limit");
+                        upperVoltage.WriteLine(" New Upper limit");                    
+                    }
+                }
+                else
+                { 
+                    deadBand.WriteLine("W  :We are in Dead band area");
+                    upperVoltage.WriteLine(" same Upper limit from last cycle");
+                }
+            }
+            else
+            {
+                Console.WriteLine("Curtail Flag is false , no need to curtail");
+                upperVoltage.WriteLine(" same Upper limit from last cycle");
+            }
+        }
+        inverters.ForEach(d => d.Control.Dc.MaxVoltage = upperVoltage);
+        Console.WriteLine(" ****************** ");
+    }
+
+    // why this is not in Controller?
+    private static void DistributePower(StatusRecord record, EssControl essControl)
+    {
+        var nInverters = record.AcDc.Devices.Count;
+        
+        var powerPerInverterPhase = nInverters > 0
+                                  ? essControl.PowerSetpoint / nInverters / 3
+                                  : 0;
+                
+        record.AcDc.Devices.ForEach(d =>
+        {
+            d.Control.Ac.PhaseControl = PhaseControl.Asymmetric;
+            d.Control.Ac.Power.L1 = powerPerInverterPhase;
+            d.Control.Ac.Power.L2 = powerPerInverterPhase;
+            d.Control.Ac.Power.L3 = powerPerInverterPhase;
+        });
+    }
+
+    // To test, most probably the curtailing flag will not work
+    /*private static void PerformLed(this StatusRecord record)
+    {
+        if (record.StateMachine.State == 23)
+        {
+            switch (record.EssControl.Mode)
+            {
+                case EssMode.CalibrationCharge:
+                    record.Relays?.PerformSlowFlashingGreenLed();
+                    break;
+                case EssMode.OptimizeSelfConsumption when !_curtailFlag:
+                    record.Relays?.PerformSolidGreenLed();
+                    break;
+                case EssMode.Off:
+                    break;
+                case EssMode.OffGrid:
+                    break;
+                case EssMode.HeatBatteries:
+                    break;
+                case EssMode.ReachMinSoc:
+                    break;
+                case EssMode.NoGridMeter:
+                    break;
+                default:
+                {
+                    if (_curtailFlag)
+                    {
+                        record.Relays?.PerformFastFlashingGreenLed();
+                    }
+
+                    break;
+                }
+            }
+        }
+        else if (record.Battery?.Soc != null && record.StateMachine.State != 23 && record.Battery.Soc > 50)
+        {
+            record.Relays?.PerformSolidOrangeLed();
+        }
+        else if (record.Battery?.Soc != null && record.StateMachine.State != 23 && record.Battery.Soc < 50 && record.Battery.Soc > 20)
+        {
+            record.Relays?.PerformSlowFlashingOrangeLed();
+        }
+        else if (record.Battery?.Soc != null && record.StateMachine.State != 23 && record.Battery.Soc < 20)
+        {
+            record.Relays?.PerformFastFlashingOrangeLed();
+        }
+
+        var criticalAlarm = record.DetectAlarmStates();
+        
+        if (criticalAlarm is not null)
+        {
+            record.Relays?.PerformFastFlashingRedLed();
+        }
+    }*/
+    
+    private static Double IncreaseInverterUpperLimit(Double upperLimit, Double stepSize)
+    {
+        return upperLimit + stepSize;
+    }
+
+    private static Double DecreaseInverterUpperLimit(Double upperLimit, Double stepSize)
+    {
+        return upperLimit - stepSize;
+    }
+    
+    private static UInt16 ClampStepSize(UInt16 stepSize)
+    {
+        return stepSize switch
+        {
+            > 5 => 5,
+            <= 1 => 1,
+            _ => stepSize
+        };
+    }
+    
+    private static void ApplyAcDcDefaultSettings(this SystemControlRegisters? sc)
+    {
+        if (sc is null)
+            return;
+        
+        sc.ReferenceFrame          = ReferenceFrame.Consumer;
+        sc.SystemConfig            = AcDcAndDcDc;
+
+        #if DEBUG
+        sc.CommunicationTimeout    = TimeSpan.FromMinutes(2);    
+        #else
+        sc.CommunicationTimeout    = TimeSpan.FromSeconds(20);
+        #endif
+        
+        sc.PowerSetPointActivation = PowerSetPointActivation.Immediate;
+        sc.UseSlaveIdForAddressing = true;
+        sc.SlaveErrorHandling      = SlaveErrorHandling.Relaxed;
+        sc.SubSlaveErrorHandling   = SubSlaveErrorHandling.Off;
+        
+        sc.ResetAlarmsAndWarnings  = true;
+    }
+    
+    private static void ApplyDcDcDefaultSettings(this SystemControlRegisters? sc)
+    {
+
+        if (sc is null)
+            return;
+        
+        sc.SystemConfig            = DcDcOnly;
+        #if DEBUG
+        sc.CommunicationTimeout    = TimeSpan.FromMinutes(2);    
+        #else
+        sc.CommunicationTimeout    = TimeSpan.FromSeconds(20);
+        #endif
+        
+        sc.PowerSetPointActivation = PowerSetPointActivation.Immediate;
+        sc.UseSlaveIdForAddressing = true;
+        sc.SlaveErrorHandling      = SlaveErrorHandling.Relaxed;
+        sc.SubSlaveErrorHandling   = SubSlaveErrorHandling.Off;
+                
+        sc.ResetAlarmsAndWarnings  = true; 
+    }
+
+    private static async Task<Boolean> UploadCsv(StatusRecord status, DateTime timeStamp)
+    {
+        
+        var csv  = status.ToCsv().LogInfo();
+
+        await RestApiSavingFile(csv);
+
+        var s3Config = status.Config.S3;
+        
+        if (s3Config is null)
+            return false;
+
+        //Concatenating 15 files in one file 
+        return await ConcatinatingAndCompressingFiles(timeStamp, s3Config);
+    }
+
+    private static async Task<Boolean> ConcatinatingAndCompressingFiles(DateTime timeStamp, S3Config s3Config)
+    {
+        if (_counterOfFile >= NbrOfFileToConcatenate)
+        {
+            _counterOfFile = 0;
+            
+            var logFileConcatenator = new LogFileConcatenator();
+                        
+            var s3Path              = timeStamp.ToUnixTime() + ".csv";
+            s3Path.WriteLine("");
+            var csvToSend           = logFileConcatenator.ConcatenateFiles(NbrOfFileToConcatenate);
+
+            var request             = s3Config.CreatePutRequest(s3Path);
+      
+            //Use this for no compression
+            //var response = await request.PutAsync(new StringContent(csv));
+            var compressedBytes = CompresseBytes(csvToSend);
+
+            // Encode the compressed byte array as a Base64 string
+            string base64String = Convert.ToBase64String(compressedBytes);
+        
+            // Create StringContent from Base64 string
+            var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64");
+        
+            // Upload the compressed data (ZIP archive) to S3
+            var response = await request.PutAsync(stringContent);
+        
+            if (response.StatusCode != 200)
+            {
+                Console.WriteLine("ERROR: PUT");
+                var error = await response.GetStringAsync();
+                Console.WriteLine(error);
+                Heartbit(new DateTime(0));
+                return false;
+            }   
+            
+            Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
+            
+            Heartbit(timeStamp);
+        }
+        _counterOfFile++;
+
+        return true;
+    }
+
+    private static void Heartbit(DateTime timeStamp)
+    {
+        var s3Bucket = Config.Load().S3?.Bucket;
+        var tryParse = TryParse(s3Bucket?.Split("-")[0], out var installationId);
+        var parse    = TryParse(timeStamp.ToUnixTime().ToString(), out var nameOfCsvFile);
+
+        if (tryParse)
+        {
+            var returnedStatus = new StatusMessage
+            {
+                InstallationId = installationId,
+                Product        = 0, // Salimax is always 0
+                Status         = _salimaxAlarmState,
+                Type           = MessageType.Heartbit,
+                Timestamp      = nameOfCsvFile
+            };
+            if (s3Bucket != null)
+                RabbitMqManager.InformMiddleware(returnedStatus);
+        }
+    }
+
+    private static Byte[] CompresseBytes(String csvToSend)
+    {
+        //Compress CSV data to a byte array
+        Byte[] compressedBytes;
+        using (var memoryStream = new MemoryStream())
+        {
+            //Create a zip directory and put the compressed file inside
+            using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
+            {
+                var entry = archive.CreateEntry("data.csv", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
+                using (var entryStream = entry.Open())
+                using (var writer = new StreamWriter(entryStream))
+                {
+                    writer.Write(csvToSend);
+                }
+            }
+        
+            compressedBytes = memoryStream.ToArray();
+        }
+
+        return compressedBytes;
+    }
+
+    private static async Task RestApiSavingFile(String csv)
+    {
+        // This is for the Rest API
+        // Check if the directory exists, and create it if it doesn't
+        const String directoryPath = "/var/www/html";
+
+        if (!Directory.Exists(directoryPath))
+        {
+            Directory.CreateDirectory(directoryPath);
+        }
+        
+        string filePath = Path.Combine(directoryPath, "status.csv");
+        
+        await File.WriteAllTextAsync(filePath, csv.SplitLines().Where(l => !l.Contains("Secret")).JoinLines());
+    }
+
+    private static Boolean IsPowerOfTwo(Int32 n)
+    {
+        return n > 0 && (n & (n - 1)) == 0;
+    }
+
+    private static void ApplyConfigFile(this StatusRecord status, Configuration? config)
+    {
+        if (config == null) return;
+        
+        status.Config.MinSoc = config.MinimumSoC;
+        status.Config.GridSetPoint = config.GridSetPoint * 1000; // converted from kW to W
+        // status.Config.ForceCalibrationChargeState = config.CalibrationChargeState;
+        //
+        // if (config.CalibrationChargeState == CalibrationChargeType.RepetitivelyEvery)
+        // {
+        //     status.Config.DayAndTimeForRepetitiveCalibration = config.CalibrationChargeDate;
+        // }
+        // else if (config.CalibrationChargeState == CalibrationChargeType.AdditionallyOnce)
+        // {
+        //     status.Config.DayAndTimeForAdditionalCalibration = config.CalibrationChargeDate;
+        // }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/S3Config.cs b/csharp/App_backup/SodiStoreMax/src/S3Config.cs
new file mode 100644
index 000000000..407e93330
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/S3Config.cs
@@ -0,0 +1,79 @@
+using System.Security.Cryptography;
+using Flurl;
+using Flurl.Http;
+using InnovEnergy.Lib.Utils;
+using static System.Text.Encoding;
+using Convert = System.Convert;
+
+namespace InnovEnergy.App.SodiStoreMax;
+
+public record S3Config
+{
+    public required String Bucket      { get; init; } 
+    public required String Region      { get; init; } 
+    public required String Provider    { get; init; } 
+    public required String Key         { get; init; } 
+    public required String Secret      { get; init; } 
+    public required String ContentType { get; init; } 
+
+    public String Host =>  $"{Bucket}.{Region}.{Provider}";
+    public String Url  =>  $"https://{Host}";
+
+    public IFlurlRequest CreatePutRequest(String s3Path) => CreateRequest("PUT", s3Path);
+    public IFlurlRequest CreateGetRequest(String s3Path) => CreateRequest("GET", s3Path);
+
+    private IFlurlRequest CreateRequest(String method, String s3Path)
+    {
+        var date = DateTime.UtcNow.ToString("r");
+        var auth = CreateAuthorization(method, s3Path, date);
+
+        return Url
+              .AppendPathSegment(s3Path)
+              .WithHeader("Host", Host)
+              .WithHeader("Date", date)
+              .WithHeader("Authorization", auth)
+              .AllowAnyHttpStatus();
+    }
+
+    private String CreateAuthorization(String method,
+                                       String s3Path,
+                                       String date)
+    {
+        return CreateAuthorization
+        (
+            method     : method,
+            bucket     : Bucket,
+            s3Path     : s3Path,
+            date       : date,
+            s3Key      : Key,
+            s3Secret   : Secret,
+            contentType: ContentType
+        );
+    }
+
+
+
+    private static String CreateAuthorization(String method,
+                                              String bucket,
+                                              String s3Path,
+                                              String date,
+                                              String s3Key,
+                                              String s3Secret,
+                                              String contentType = "application/base64",
+                                              String md5Hash = "")
+    {
+
+        contentType = "application/base64; charset=utf-8";
+        //contentType = "text/plain; charset=utf-8";  //this to use when sending plain csv to S3
+        
+        var payload = $"{method}\n{md5Hash}\n{contentType}\n{date}\n/{bucket.Trim('/')}/{s3Path.Trim('/')}"; 
+        using var hmacSha1 = new HMACSHA1(UTF8.GetBytes(s3Secret));
+        
+        var signature = UTF8
+                       .GetBytes(payload)
+                       .Apply(hmacSha1.ComputeHash)
+                       .Apply(Convert.ToBase64String);
+
+        return $"AWS {s3Key}:{signature}";
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/SaliMaxRelays/RelaysDevice.cs b/csharp/App_backup/SodiStoreMax/src/SaliMaxRelays/RelaysDevice.cs
new file mode 100644
index 000000000..f6e11f694
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/SaliMaxRelays/RelaysDevice.cs
@@ -0,0 +1,72 @@
+using InnovEnergy.Lib.Devices.Adam6360D;
+using InnovEnergy.Lib.Devices.Amax5070;
+using InnovEnergy.Lib.Protocols.Modbus.Channels;
+
+namespace InnovEnergy.App.SodiStoreMax.SaliMaxRelays;
+
+public class RelaysDevice  
+{
+    private Adam6360DDevice AdamDevice { get; }
+  
+    public RelaysDevice(String hostname) => AdamDevice = new Adam6360DDevice(hostname, 2);
+    public RelaysDevice(Channel channel) => AdamDevice = new Adam6360DDevice(channel, 2);
+
+
+    public RelaysRecord? Read()
+    {
+        try
+        {
+            return AdamDevice.Read();
+        }
+        catch (Exception e)
+        {
+            $"Failed to read from {nameof(RelaysDevice)}\n{e}".LogError();
+            return null;
+        }
+    }
+
+    public void Write(RelaysRecord r)
+    {
+        try
+        {
+            AdamDevice.Write(r);
+        }
+        catch (Exception e)
+        {
+            $"Failed to write to {nameof(RelaysDevice)}\n{e}".LogError();
+        }
+    }
+}
+
+
+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();
+        }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/SaliMaxRelays/RelaysRecord.cs b/csharp/App_backup/SodiStoreMax/src/SaliMaxRelays/RelaysRecord.cs
new file mode 100644
index 000000000..bdfcd37c9
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/SaliMaxRelays/RelaysRecord.cs
@@ -0,0 +1,83 @@
+using InnovEnergy.Lib.Devices.Adam6360D;
+using InnovEnergy.Lib.Devices.Amax5070;
+namespace InnovEnergy.App.SodiStoreMax.SaliMaxRelays;
+
+public interface IRelaysRecord
+{
+    Boolean              K1GridBusIsConnectedToGrid                    { get; }
+    Boolean              K2IslandBusIsConnectedToGridBus               { get; }
+    IEnumerable<Boolean> K3InverterIsConnectedToIslandBus              { get; }
+    Boolean              FiWarning                                     { get; }
+    Boolean              FiError                                       { get; }
+    Boolean              K2ConnectIslandBusToGridBus                   { get; set; }
+}
+
+public class RelaysRecord : IRelaysRecord
+{
+    private readonly Adam6360DRegisters _Regs;
+
+    private RelaysRecord(Adam6360DRegisters regs) => _Regs = regs;
+    
+    public Boolean K1GridBusIsConnectedToGrid      => _Regs.DigitalInput6;
+    public Boolean K2IslandBusIsConnectedToGridBus => !_Regs.DigitalInput4;
+
+    public IEnumerable<Boolean> K3InverterIsConnectedToIslandBus
+    {
+        get
+        {
+            yield return K3Inverter1IsConnectedToIslandBus;
+            yield return K3Inverter2IsConnectedToIslandBus;
+            yield return K3Inverter3IsConnectedToIslandBus;
+            yield return K3Inverter4IsConnectedToIslandBus;
+        }
+    }
+    
+    public Boolean K3Inverter1IsConnectedToIslandBus => !_Regs.DigitalInput0;   
+    public Boolean K3Inverter2IsConnectedToIslandBus => !_Regs.DigitalInput1;  
+    public Boolean K3Inverter3IsConnectedToIslandBus => !_Regs.DigitalInput2;  
+    public Boolean K3Inverter4IsConnectedToIslandBus => !_Regs.DigitalInput3;
+      
+    public Boolean FiWarning => !_Regs.DigitalInput5; 
+    public Boolean FiError   => !_Regs.DigitalInput7;
+    
+    public Boolean K2ConnectIslandBusToGridBus { get => _Regs.Relay0; set => _Regs.Relay0 = value;}
+
+    public static implicit operator Adam6360DRegisters(RelaysRecord d) => d._Regs;
+    public static implicit operator RelaysRecord(Adam6360DRegisters d) => new RelaysRecord(d);
+    
+}
+
+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 IEnumerable<Boolean> K3InverterIsConnectedToIslandBus
+    {
+        get
+        {
+            yield return K3Inverter1IsConnectedToIslandBus;
+            yield return K3Inverter2IsConnectedToIslandBus;
+            yield return K3Inverter3IsConnectedToIslandBus;
+            yield return K3Inverter4IsConnectedToIslandBus;
+        }
+    }
+    
+    public Boolean K3Inverter1IsConnectedToIslandBus => !_Regs.DigitalInput16;   
+    public Boolean K3Inverter2IsConnectedToIslandBus => !_Regs.DigitalInput17;  
+    public Boolean K3Inverter3IsConnectedToIslandBus => !_Regs.DigitalInput18;  
+    public 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);
+    
+}
diff --git a/csharp/App_backup/SodiStoreMax/src/Switch.cs b/csharp/App_backup/SodiStoreMax/src/Switch.cs
new file mode 100644
index 000000000..932fdb8df
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Switch.cs
@@ -0,0 +1,15 @@
+using InnovEnergy.Lib.Utils;
+
+namespace InnovEnergy.App.SodiStoreMax;
+
+public static class Switch
+{
+    public static TextBlock Open(String name)
+    {
+        return TextBlock.AlignCenterHorizontal
+        (
+            "  __╱ __  ",
+            name
+        );
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/System/Controller.cs b/csharp/App_backup/SodiStoreMax/src/System/Controller.cs
new file mode 100644
index 000000000..d25171854
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/System/Controller.cs
@@ -0,0 +1,727 @@
+using InnovEnergy.App.SodiStoreMax.Ess;
+using InnovEnergy.App.SodiStoreMax.SaliMaxRelays;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
+using static InnovEnergy.Lib.Devices.Trumpf.SystemControl.DataTypes.GridType;
+
+namespace InnovEnergy.App.SodiStoreMax.System;
+
+public static class Controller
+{
+    private static Int32 GetSystemState(this StatusRecord r)
+    {
+        var relays = r.Relays;
+
+        if (relays is null)
+            return 101;  //     Message = "Panic: relay device is not available!",
+        
+        var acDcs = r.AcDc;
+
+        if (acDcs.NotAvailable())
+            return 102;
+
+        var k4 = acDcs.AllGridTied() ? 0
+               : acDcs.AllIsland()   ? 1
+               : 4;
+        
+        var k5 = acDcs.AllDisabled() ? 0
+               : acDcs.AllEnabled()  ? 1
+               : 4;
+        
+        if (k4 == 4 || k5 == 4)
+            return 103; //Message = "Panic: ACDCs have unequal grid types or power stage",
+
+        var nInverters = r.AcDc.Devices.Count;
+        
+        var k1 = relays.K1GridBusIsConnectedToGrid                                    ? 1 : 0;
+        var k2 = relays.K2IslandBusIsConnectedToGridBus                               ? 1 : 0;
+        var k3 = relays.K3InverterIsConnectedToIslandBus.Take(nInverters).Any(c => c) ? 1 : 0;
+        
+        
+        // states as defined in states excel sheet 
+        return 1 * k1 
+             + 2 * k2 
+             + 4 * k3 
+             + 8 * k4
+             + 16 * k5;
+    }
+
+    public static Boolean ControlSystemState(this StatusRecord s)
+    {
+        s.StateMachine.State = s.GetSystemState();
+        
+        return s.StateMachine.State switch
+        {
+            0   => State0(s),
+            1   => State1(s),
+            2   => State2(s),
+            3   => State3(s),
+            4   => State4(s),
+            5   => State5(s),
+            6   => State6(s),
+            7   => State7(s),
+            8   => State8(s),
+            9   => State9(s),
+            10  => State10(s),
+            11  => State11(s),
+            12  => State12(s),
+            13  => State13(s),
+            14  => State14(s),
+            15  => State15(s),
+            16  => State16(s),
+            17  => State17(s),
+            18  => State18(s),
+            19  => State19(s),
+            20  => State20(s),
+            21  => State21(s),
+            22  => State22(s),
+            23  => State23(s),
+            24  => State24(s),
+            25  => State25(s),
+            26  => State26(s),
+            27  => State27(s),
+            28  => State28(s),
+            29  => State29(s),
+            30  => State30(s),
+            31  => State31(s),
+       
+            
+            101 => State101(s),
+            102 => State102(s),
+            103 => State103(s),
+            _   => UnknownState(s)
+        };
+        
+    }
+
+    private static Boolean NotAvailable(this AcDcDevicesRecord acDcs)
+    {
+        return acDcs.SystemControl == null || acDcs.Devices.Count == 0;
+    }
+
+    private static Boolean State0(StatusRecord s)
+    {
+        s.StateMachine.Message = "Ac/Dc are off. Switching to Island Mode.";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 8
+    }
+    
+        
+    private static Boolean State1(StatusRecord s)
+    {
+        s.StateMachine.Message = "Grid Tied mode active, closing k2";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.ConnectIslandBusToGrid();
+    
+        return false;
+        
+        // => 3
+    }
+    
+    private static Boolean State2(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 0
+    }
+    
+    private static Boolean State3(StatusRecord s)
+    {
+        s.StateMachine.Message = "K2 closed, Turning on Ac/Dc";
+        
+        s.DcDc.Enable();
+        s.AcDc.Enable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.ConnectIslandBusToGrid();
+    
+        return false;
+        
+        // => 19
+    }
+
+    
+    private static Boolean State4(StatusRecord s)
+    {
+        s.StateMachine.Message = "K2 is open, waiting K3 to open";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return true;
+        
+        // => 0
+    }
+    
+    private static Boolean State5(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => ?
+    }
+        
+    private static Boolean State6(StatusRecord s)
+    {
+        s.StateMachine.Message = "Inverters are off, opening K2";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return true;
+        
+        // => 4
+    }
+
+    private static Boolean State7(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.ConnectIslandBusToGrid();
+        
+        return false;
+
+        // => ?
+    }
+    
+    private static Boolean State8(StatusRecord s)
+    {
+        s.StateMachine.Message = "Ac/Dc are off and in Island Mode.";
+        
+        s.DcDc.Enable();
+        s.AcDc.Enable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+
+        return true;
+        // => 24
+    }   
+    
+    private static Boolean State9(StatusRecord s)
+    {
+        s.StateMachine.Message = "Ac/Dc are disconnected from Island Bus. Switching to GridTie Mode.";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+
+        return false;
+        
+        // => 1
+    } 
+    
+    private static Boolean State10(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 8
+    }
+
+    private static Boolean State11(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 9
+    }
+    
+    private static Boolean State12(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 8
+    }
+    
+    private static Boolean State13(StatusRecord s)
+    {
+        s.StateMachine.Message = "Ac/Dc are off. Waiting for them to disconnect from Island Bus.";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return true;
+        
+        // => 9
+    }
+
+    private static Boolean State14(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 12
+    }
+
+    
+    private static Boolean State15(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 13
+    }
+    
+    
+    private static Boolean State16(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 0
+    }
+    
+    
+    private static Boolean State17(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 1
+    }
+    
+    
+    private static Boolean State18(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 0
+    }
+    
+    private static Boolean State19(StatusRecord s)
+    {
+        s.StateMachine.Message = "Waiting for Ac/Dc to connect to Island Bus";
+         
+        s.DcDc.Enable();
+        s.AcDc.Enable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.ConnectIslandBusToGrid();
+        
+        return true;
+        
+        // => 23
+    }
+
+    
+    private static Boolean State20(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 4
+    }
+
+    private static Boolean State21(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 5
+    }
+    
+    private static Boolean State22(StatusRecord s)
+    {
+        s.StateMachine.Message = "K1 opened, switching inverters off";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.ConnectIslandBusToGrid();
+        
+        return true;
+        
+        // => 6
+    }
+
+    private static Boolean State23(StatusRecord s)
+    {
+        s.StateMachine.Message = "ESS";
+        
+        s.DcDc.Enable();
+        s.AcDc.Enable();
+        s.AcDc.EnableGridTieMode();
+        s.Relays.ConnectIslandBusToGrid();
+        
+        return true;
+        
+        // => 22
+    }
+
+        
+    private static Boolean State24(StatusRecord s)
+    {
+        s.StateMachine.Message = "Inverter are on waiting for k3 to close";
+        
+        s.DcDc.Enable();
+        s.AcDc.Enable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+
+        return false;
+        
+        // => 28
+    }
+    
+    private static Boolean State25(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 9
+    }
+    
+    private static Boolean State26(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid(); 
+        
+        return false;
+
+        // => 10
+    }
+    
+    private static Boolean State27(StatusRecord s)
+    {
+        s.StateMachine.Message = "K2 open and enable island mode";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid(); 
+        
+        return false;
+
+        // => 9
+    }
+    
+    private static Boolean State28(StatusRecord s)
+    {
+        s.StateMachine.Message = "Island Mode";
+        
+        s.DcDc.Enable();
+        s.AcDc.Enable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+
+        return false;
+        
+        // => 29
+    }
+    
+    private static Boolean State29(StatusRecord s)
+    {
+        s.StateMachine.Message = "K1 closed, Switching off Inverters and moving to grid tie";
+        
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+
+        return false;
+        
+        // => 13
+    }
+
+    private static Boolean State30(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 14
+    }
+    
+    private static Boolean State31(StatusRecord s)
+    {
+        s.StateMachine.Message = "";
+
+        s.DcDc.Disable();
+        s.AcDc.Disable();
+        s.AcDc.EnableIslandMode();
+        s.Relays.DisconnectIslandBusFromGrid();
+        
+        return false;
+
+        // => 13
+    }
+    
+    private static Boolean State101(StatusRecord s)
+    {
+        s.StateMachine.Message = "Relay device is not available";
+        return s.EnableSafeDefaults();
+    }
+    
+    private static Boolean State102(StatusRecord s)
+    {
+        s.StateMachine.Message = "ACDCs not available";
+        return s.EnableSafeDefaults();
+    }
+
+    private static Boolean State103(StatusRecord s)
+    {
+        s.StateMachine.Message = "Panic: ACDCs have unequal grid types or PowerStage";
+        s.AcDc.Enable();
+        return false;
+    }
+    
+    // private static Boolean State104(StatusRecord s)
+    // {
+    //     s.StateMachine.Message = "Panic: DCDCs not available";
+    //     return s.EnableSafeDefaults();
+    // }
+    
+    
+    private static Boolean UnknownState(StatusRecord s)
+    {
+        // "Unknown System State"
+        return s.EnableSafeDefaults();
+    }
+
+
+
+    private static Boolean AllDisabled(this AcDcDevicesRecord acDcs)
+    {
+        return acDcs.Devices.All(d => !d.Control.PowerStageEnable);
+    }
+    
+    private static Boolean AllEnabled(this AcDcDevicesRecord acDcs)
+    {
+        return acDcs.Devices.All(d => d.Control.PowerStageEnable);
+    }
+
+
+    private static Boolean AllTheSame(this AcDcDevicesRecord acDcs)
+    {
+        return acDcs.Devices
+                    .Select(d => d.Control.PowerStageEnable)
+                    .Distinct()
+                    .Count() == 1;
+    }
+    
+    private static Boolean AllGridTied(this AcDcDevicesRecord acDcs)
+    {
+        return acDcs.Devices.All(d => d.Status.ActiveGridType is GridTied380V60Hz)
+            || acDcs.Devices.All(d => d.Status.ActiveGridType is GridTied400V50Hz)
+            || acDcs.Devices.All(d => d.Status.ActiveGridType is GridTied480V60Hz);
+    }
+
+    private static Boolean AllIsland(this AcDcDevicesRecord acDcs)
+    {
+        return acDcs.Devices.All(d => d.Status.ActiveGridType is Island400V50Hz)
+            || acDcs.Devices.All(d => d.Status.ActiveGridType is Island480V60Hz);
+    }
+    
+    private static void ForAll<T>(this IEnumerable<T> ts, Action<T> action)
+    {
+        foreach (var t in ts)
+            action(t);
+    }
+    
+    private static void Disable(this AcDcDevicesRecord acDc)
+    {
+        acDc.Devices
+            .Select(d => d.Control)
+            .ForAll(c => c.PowerStageEnable = false);
+    }
+
+    //this is must be deleted
+    private static void Disable(this DcDcDevicesRecord dcDc)
+    {
+        // For Test purpose, The transition from island mode to grid tier and vis versa , may not need to disable Dc/Dc. 
+        // This will keep the Dc link powered.
+        
+        // dcDc.Devices
+        //     .Select(d => d.Control)
+        //     .ForAll(c => c.PowerStageEnable = false);
+    }
+    
+    private static void Enable(this AcDcDevicesRecord acDc)
+    {
+        acDc.Devices
+            .Select(d => d.Control)
+            .ForAll(c => c.PowerStageEnable = true);
+    }
+
+    private static void Enable(this DcDcDevicesRecord dcDc)
+    {
+        dcDc.Devices
+            .Select(d => d.Control)
+            .ForAll(c => c.PowerStageEnable = true);
+    }
+
+
+    private static void EnableGridTieMode(this AcDcDevicesRecord acDc)
+    {
+        acDc.Devices
+            .Select(d => d.Control)
+            .ForAll(c => c.Ac.GridType = GridTied400V50Hz);  // TODO: config grid type
+    }
+
+
+    private static void EnableIslandMode(this AcDcDevicesRecord acDc)
+    {
+        acDc.Devices
+            .Select(d => d.Control)
+            .ForAll(c => c.Ac.GridType = Island400V50Hz);  // TODO: config grid type
+    }
+
+    private static void DisconnectIslandBusFromGrid(this IRelaysRecord? relays)
+    {
+        if (relays is not null)
+            relays.K2ConnectIslandBusToGridBus = false;
+    }
+
+    private static void ConnectIslandBusToGrid(this IRelaysRecord? relays)
+    {
+        if (relays is not null)
+            relays.K2ConnectIslandBusToGridBus = true;
+    }
+
+
+    private static Boolean EnableSafeDefaults(this StatusRecord s)
+    {
+        // After some tests, the safe state is switch off inverter and keep the last state of K2 , Dc/Dc and Grid type to avoid conflict. 
+        
+       // s.DcDc.Disable();
+        s.AcDc.Disable();   // Maybe comment this to avoid opening/closing K3
+       // s.AcDc.EnableGridTieMode();
+       // s.Relays.DisconnectIslandBusFromGrid();
+        return false;
+    }
+    
+    public static DcDcDevicesRecord ResetAlarms(this DcDcDevicesRecord dcDcStatus)
+    {
+        var sc = dcDcStatus.SystemControl;
+
+        if (sc is not null)
+            sc.ResetAlarmsAndWarnings = sc.Alarms.Any();
+
+        foreach (var d in dcDcStatus.Devices)
+            d.Control.ResetAlarmsAndWarnings = d.Status.Alarms.Any() || d.Status.Warnings.Any();
+
+        return dcDcStatus;
+    }
+
+    public static AcDcDevicesRecord ResetAlarms(this AcDcDevicesRecord acDcStatus)
+    {
+        var sc = acDcStatus.SystemControl;
+        
+        if (sc is not null)
+            sc.ResetAlarmsAndWarnings = sc.Alarms.Any() || sc.Warnings.Any();
+
+        foreach (var d in acDcStatus.Devices)
+            d.Control.ResetAlarmsAndWarnings = d.Status.Alarms.Any() || d.Status.Warnings.Any();
+        
+        return acDcStatus;
+    }
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/System/StateMachine.cs b/csharp/App_backup/SodiStoreMax/src/System/StateMachine.cs
new file mode 100644
index 000000000..6e0d498ec
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/System/StateMachine.cs
@@ -0,0 +1,9 @@
+namespace InnovEnergy.App.SodiStoreMax.System;
+
+public record StateMachine
+{
+    public required String Message { get; set; }  // TODO: init only 
+    public required Int32  State   { get; set; }  // TODO: init only
+
+    public static StateMachine Default { get; } = new StateMachine { State = 100, Message = "Unknown State" };
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/SystemConfig/AcDcConfig.cs b/csharp/App_backup/SodiStoreMax/src/SystemConfig/AcDcConfig.cs
new file mode 100644
index 000000000..28b514dc4
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/SystemConfig/AcDcConfig.cs
@@ -0,0 +1,8 @@
+namespace InnovEnergy.App.SodiStoreMax.SystemConfig;
+
+public class AcDcConfig
+{
+    public required Double MaxDcLinkVoltage       { get; set; }
+    public required Double MinDcLinkVoltage       { get; set; }
+    public required Double ReferenceDcLinkVoltage { get; init; }  
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/SystemConfig/CalibrationChargeType.cs b/csharp/App_backup/SodiStoreMax/src/SystemConfig/CalibrationChargeType.cs
new file mode 100644
index 000000000..1b783b808
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/SystemConfig/CalibrationChargeType.cs
@@ -0,0 +1,8 @@
+namespace InnovEnergy.App.SodiStoreMax.SystemConfig;
+
+public enum CalibrationChargeType
+{
+    RepetitivelyEvery,
+    AdditionallyOnce,
+    ChargePermanently
+}
diff --git a/csharp/App_backup/SodiStoreMax/src/SystemConfig/Config.cs b/csharp/App_backup/SodiStoreMax/src/SystemConfig/Config.cs
new file mode 100644
index 000000000..110db56cb
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/SystemConfig/Config.cs
@@ -0,0 +1,272 @@
+using System.Text.Json;
+using InnovEnergy.App.SodiStoreMax.Devices;
+using InnovEnergy.Lib.Utils;
+using static System.Text.Json.JsonSerializer;
+
+namespace InnovEnergy.App.SodiStoreMax.SystemConfig;
+
+// shut up trim warnings
+#pragma warning disable IL2026  
+
+public class Config //TODO: let IE choose from config files (Json) and connect to GUI
+{
+    private static String DefaultConfigFilePath => Path.Combine(Environment.CurrentDirectory, "config.json");
+    private static DateTime DefaultDatetime => new(2024, 03, 11, 09, 00, 00); 
+    
+    private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
+
+    public required Double                MinSoc                             { get; set; }
+    public required UInt16                CurtailP                           { get; set; }// in Kw
+    public required UInt16                PvInstalledPower                   { get; set; }// in Kw 
+    public required CalibrationChargeType ForceCalibrationChargeState        { get; set; } 
+    public required DateTime              DayAndTimeForRepetitiveCalibration { get; set; } 
+    public required DateTime              DayAndTimeForAdditionalCalibration { get; set; }
+    public required Boolean               DisplayIndividualBatteries         { get; set; } 
+    public required Double                PConstant                          { get; set; } 
+    public required Double                GridSetPoint                       { get; set; } 
+    public required Double                BatterySelfDischargePower          { get; set; } 
+    public required Double                HoldSocZone                        { get; set; } 
+    public required DevicesConfig         IslandMode                         { get; set; }
+    public required DevicesConfig         GridTie                            { get; set; }
+    
+    public required DeviceConfig          Devices                            { get; set; }
+    public required S3Config?             S3                                 { get; set; }
+
+    private static String? LastSavedData { get; set; }
+    
+    #if DEBUG 
+    public static Config Default => new()
+    {
+        MinSoc                              = 20,
+        CurtailP                            = 0,
+        PvInstalledPower                    = 20,
+        ForceCalibrationChargeState         = CalibrationChargeType.RepetitivelyEvery,  
+        DayAndTimeForRepetitiveCalibration = DefaultDatetime,
+        DayAndTimeForAdditionalCalibration = DefaultDatetime,
+        DisplayIndividualBatteries         = false,
+        PConstant                          = .5,
+        GridSetPoint                       = 0,
+        BatterySelfDischargePower          = 200,  
+        HoldSocZone                        = 1,   // TODO: find better name,
+        IslandMode = new()
+        {
+            AcDc = new ()
+            {
+                MinDcLinkVoltage       = 690,
+                ReferenceDcLinkVoltage = 750,
+                MaxDcLinkVoltage       = 810,
+            },
+        
+            DcDc = new ()
+            {
+                UpperDcLinkVoltage     = 50,
+                LowerDcLinkVoltage     = 50,
+                ReferenceDcLinkVoltage = 750,
+                            
+                MaxBatteryChargingCurrent    = 210,
+                MaxBatteryDischargingCurrent = 210,
+                MaxDcPower                   = 10000,
+                        
+                MaxChargeBatteryVoltage    = 57,
+                MinDischargeBatteryVoltage = 0,
+            },
+        },
+        
+        GridTie = new()
+        {
+            AcDc = new ()
+            {
+                MinDcLinkVoltage       = 720,
+                ReferenceDcLinkVoltage = 750,
+                MaxDcLinkVoltage       = 810,
+            },
+        
+            DcDc = new ()
+            {
+                UpperDcLinkVoltage     = 50,
+                LowerDcLinkVoltage     = 50,
+                ReferenceDcLinkVoltage = 750,
+                            
+                MaxBatteryChargingCurrent    = 210,
+                MaxBatteryDischargingCurrent = 210,
+                MaxDcPower                   = 10000,
+                        
+                MaxChargeBatteryVoltage    = 57,
+                MinDischargeBatteryVoltage = 0,
+            },
+        },
+        
+        Devices = new ()
+        {
+            RelaysIp             = new() { Host = "localhost", Port = 5006, DeviceState = DeviceState.Measured},
+            TsRelaysIp           = new() { Host = "localhost", Port = 5006, DeviceState = DeviceState.Measured},
+            GridMeterIp          = new() { Host = "localhost", Port = 5003, DeviceState = DeviceState.Measured},  
+            PvOnAcGrid           = new() { Host = "false"    , Port = 0   , DeviceState = DeviceState.Measured},
+            LoadOnAcGrid         = new() { Host = "false"    , Port = 0   , DeviceState = DeviceState.Measured},
+            PvOnAcIsland         = new() { Host = "true"     , Port = 0   , DeviceState = DeviceState.Measured},
+            IslandBusLoadMeterIp = new() { Host = "localhost", Port = 5004, DeviceState = DeviceState.Measured},  
+            TruConvertAcIp       = new() { Host = "localhost", Port = 5001, DeviceState = DeviceState.Measured},  
+            PvOnDc               = new() { Host = "localhost", Port = 5005, DeviceState = DeviceState.Measured},  
+            LoadOnDc             = new() { Host = "false"    , Port = 0   , DeviceState = DeviceState.Measured},
+            TruConvertDcIp       = new() { Host = "localhost", Port = 5002, DeviceState = DeviceState.Measured},           
+            BatteryIp            = new() { Host = "localhost", Port = 5007, DeviceState = DeviceState.Measured}, 
+            BatteryNodes         = new []{ 2, 3, 4, 5, 6 }
+        },
+
+        S3 = new()
+        {
+            Bucket      = "1-3e5b3069-214a-43ee-8d85-57d72000c19d",
+            Region      = "sos-ch-dk-2",
+            Provider    = "exo.io",
+            ContentType = "text/plain; charset=utf-8",
+            Key         = "EXO4ec5faf1a7650b79b5722fb5",
+            Secret      = "LUxu1PGEA-POEIckoEyq6bYyz0RnenW6tmqccMKgkHQ"
+        },
+    };
+    #else 
+    public static Config Default => new()
+    {
+        MinSoc                               = 20,
+        CurtailP                             = 0,
+        PvInstalledPower                     = 20,
+        ForceCalibrationChargeState          = CalibrationChargeType.RepetitivelyEvery,
+        DayAndTimeForRepetitiveCalibration = DefaultDatetime,
+        DayAndTimeForAdditionalCalibration = DefaultDatetime,
+        DisplayIndividualBatteries           = false,
+        PConstant                            = .5,
+        GridSetPoint                         = 0,
+        BatterySelfDischargePower            = 200,  
+        HoldSocZone                          = 1,   // TODO: find better name,
+        IslandMode = new()
+        {
+            AcDc = new ()
+            {
+                MinDcLinkVoltage       = 690,
+                ReferenceDcLinkVoltage = 750,
+                MaxDcLinkVoltage       = 810,
+            },
+        
+            DcDc = new ()
+            {
+                UpperDcLinkVoltage     = 50,
+                LowerDcLinkVoltage     = 50,
+                ReferenceDcLinkVoltage = 750,
+                                            
+                MaxBatteryChargingCurrent    = 210,
+                MaxBatteryDischargingCurrent = 210,
+                MaxDcPower                   = 10000,
+                        
+                MaxChargeBatteryVoltage    = 57,
+                MinDischargeBatteryVoltage = 0,
+            },
+        },
+        
+        GridTie = new()
+        {
+            AcDc = new ()
+            {
+                MinDcLinkVoltage       = 720,
+                ReferenceDcLinkVoltage = 750,
+                MaxDcLinkVoltage       = 780,
+            },
+        
+            DcDc = new ()
+            {
+                UpperDcLinkVoltage     = 20,
+                LowerDcLinkVoltage     = 20,
+                ReferenceDcLinkVoltage = 750,
+                            
+                MaxBatteryChargingCurrent    = 210,
+                MaxBatteryDischargingCurrent = 210,
+                MaxDcPower                   = 10000,
+                        
+                MaxChargeBatteryVoltage    = 57,
+                MinDischargeBatteryVoltage = 0,
+            },
+        },
+
+        
+        S3 = new()
+        {
+            Bucket      = "1-3e5b3069-214a-43ee-8d85-57d72000c19d",
+            Region      = "sos-ch-dk-2",
+            Provider    = "exo.io",
+            Key         = "EXObb5a49acb1061781761895e7",
+            Secret      = "sKhln0w8ii3ezZ1SJFF33yeDo8NWR1V4w2H0D4-350I",
+            ContentType = "text/plain; charset=utf-8"
+        },
+        
+        Devices                  = new ()
+        {
+            RelaysIp             = new() { Host = "10.0.1.1",  Port = 502,  DeviceState = DeviceState.Measured},
+            TsRelaysIp           = new() { Host = "10.0.1.2",  Port = 502,  DeviceState = DeviceState.Measured},
+            GridMeterIp          = new() { Host = "10.0.4.1",  Port = 502,  DeviceState = DeviceState.Measured},
+            PvOnAcGrid           = new() { Host = "false"   ,  Port = 0  ,  DeviceState = DeviceState.Measured},
+            LoadOnAcGrid         = new() { Host = "true"    ,  Port = 0  ,  DeviceState = DeviceState.Measured},
+            PvOnAcIsland         = new() { Host = "false"   ,  Port = 0  ,  DeviceState = DeviceState.Measured},
+            IslandBusLoadMeterIp = new() { Host = "10.0.4.2",  Port = 502,  DeviceState = DeviceState.Measured}, 
+            TruConvertAcIp       = new() { Host = "10.0.2.1",  Port = 502,  DeviceState = DeviceState.Measured}, 
+            PvOnDc               = new() { Host = "10.0.5.1",  Port = 502,  DeviceState = DeviceState.Measured}, 
+            LoadOnDc             = new() { Host = "false"   ,  Port = 0  ,  DeviceState = DeviceState.Measured},
+            TruConvertDcIp       = new() { Host = "10.0.3.1",  Port = 502,  DeviceState = DeviceState.Measured}, 
+            BatteryIp            = new() { Host = "localhost", Port = 6855, DeviceState = DeviceState.Measured },
+            BatteryNodes         = new []{ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11},
+        },
+    };
+    #endif
+
+    public void Save(String? path = null)
+    {
+        var configFilePath = path ?? DefaultConfigFilePath;
+        
+        try
+        {
+            var jsonString = Serialize(this, JsonOptions);
+
+            if (LastSavedData == jsonString)
+                return;
+            
+            LastSavedData = jsonString;
+
+            File.WriteAllText(configFilePath, jsonString);
+        }
+        catch (Exception e)
+        {
+            $"Failed to write config file {configFilePath}\n{e}".WriteLine();
+            throw;
+        }
+    }
+    
+    
+    public static Config Load(String? path = null)
+    {
+        var configFilePath = path ?? DefaultConfigFilePath;
+        try
+        {
+            var jsonString = File.ReadAllText(configFilePath);
+            return Deserialize<Config>(jsonString)!;
+        }
+        catch (Exception e)
+        {
+            $"Failed to read config file {configFilePath}, using default config\n{e}".WriteLine();
+            return Default;
+        }
+    }
+    
+    
+    public static async Task<Config> LoadAsync(String? path = null)
+    {
+        var configFilePath = path ?? DefaultConfigFilePath;
+        try
+        {
+            var jsonString = await File.ReadAllTextAsync(configFilePath);
+            return Deserialize<Config>(jsonString)!;
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine($"Couldn't read config file {configFilePath}, using default config");
+            e.Message.WriteLine();
+            return Default;
+        }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/SystemConfig/DcDcConfig.cs b/csharp/App_backup/SodiStoreMax/src/SystemConfig/DcDcConfig.cs
new file mode 100644
index 000000000..182f71521
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/SystemConfig/DcDcConfig.cs
@@ -0,0 +1,15 @@
+namespace InnovEnergy.App.SodiStoreMax.SystemConfig;
+
+public class DcDcConfig
+{
+    public required Double LowerDcLinkVoltage           { get; set; }
+    public required Double ReferenceDcLinkVoltage       { get; init; } 
+    public required Double UpperDcLinkVoltage           { get; set; }
+    
+    public required Double MaxBatteryChargingCurrent    { get; set; } 
+    public required Double MaxBatteryDischargingCurrent { get; set; }
+    public required Double MaxDcPower                   { get; set; }
+    
+    public required Double MaxChargeBatteryVoltage      { get; set; }
+    public required Double MinDischargeBatteryVoltage   { get; set; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/SystemConfig/DeviceConfig.cs b/csharp/App_backup/SodiStoreMax/src/SystemConfig/DeviceConfig.cs
new file mode 100644
index 000000000..aed893168
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/SystemConfig/DeviceConfig.cs
@@ -0,0 +1,21 @@
+using InnovEnergy.App.SodiStoreMax.Devices;
+using InnovEnergy.Lib.Utils.Net;
+
+namespace InnovEnergy.App.SodiStoreMax.SystemConfig;
+
+public class DeviceConfig
+{
+    public required SalimaxDevice RelaysIp             { get; init; }
+    public required SalimaxDevice TsRelaysIp           { get; init; }
+    public required SalimaxDevice GridMeterIp          { get; init; }
+    public required SalimaxDevice PvOnAcGrid           { get; init; }
+    public required SalimaxDevice LoadOnAcGrid         { get; init; }
+    public required SalimaxDevice PvOnAcIsland         { get; init; }
+    public required SalimaxDevice IslandBusLoadMeterIp { get; init; }
+    public required SalimaxDevice TruConvertAcIp       { get; init; }
+    public required SalimaxDevice PvOnDc               { get; init; }
+    public required SalimaxDevice LoadOnDc             { get; init; }
+    public required SalimaxDevice TruConvertDcIp       { get; init; }
+    public required SalimaxDevice BatteryIp            { get; init; }
+    public required Int32[]       BatteryNodes         { get; init; }
+}
diff --git a/csharp/App_backup/SodiStoreMax/src/SystemConfig/DevicesConfig.cs b/csharp/App_backup/SodiStoreMax/src/SystemConfig/DevicesConfig.cs
new file mode 100644
index 000000000..ba049f5cb
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/SystemConfig/DevicesConfig.cs
@@ -0,0 +1,7 @@
+namespace InnovEnergy.App.SodiStoreMax.SystemConfig;
+
+public class DevicesConfig
+{
+    public required AcDcConfig AcDc { get; init; }
+    public required DcDcConfig DcDc { get; init; }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/SodiStoreMax/src/Topology.cs b/csharp/App_backup/SodiStoreMax/src/Topology.cs
new file mode 100644
index 000000000..5ad0190e1
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/src/Topology.cs
@@ -0,0 +1,530 @@
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using InnovEnergy.App.SodiStoreMax.Devices;
+using InnovEnergy.App.SodiStoreMax.Ess;
+using InnovEnergy.Lib.Devices.AMPT;
+using InnovEnergy.Lib.Devices.BatteryDeligreen;
+using InnovEnergy.Lib.Devices.EmuMeter;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc;
+using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc;
+using InnovEnergy.Lib.Units;
+using InnovEnergy.Lib.Units.Power;
+using InnovEnergy.Lib.Utils;
+using Ac3Bus = InnovEnergy.Lib.Units.Composite.Ac3Bus;
+
+namespace InnovEnergy.App.SodiStoreMax;
+
+//                         ┌────┐                 ┌────┐
+//                         │ Pv │                 │ Pv │                                     ┌────┐
+//                         └────┘                 └────┘                                     │ Pv │
+//                           V                      V                                        └────┘
+//                           V                      V                                          V
+//                     (b)  0 W               (e)  0 W                                         V
+//                           V                      V                                   (i)  13.2 kW                              ┌────────────┐
+//                           V                      V                                          V                                  │  Battery   │
+// ┌─────────┐          ┌──────────┐          ┌────────────┐          ┌─────────┐              V                                  ├────────────┤
+// │  Grid   │          │ Grid Bus │          │ Island Bus │          │  AC/DC  │          ┌────────┐          ┌───────┐          │ 52.3 V     │
+// ├─────────┤ -10.3 kW ├──────────┤ -11.7 kW ├────────────┤ -11.7 kW ├─────────┤ -11.7 kW │ Dc Bus │  1008 W  │ DC/DC │  1008 W  │ 99.1 %     │
+// │ -3205 W │<<<<<<<<<<│ 244 V    │<<<<<<<<<<│ 244 V      │<<<<<<<<<<│ -6646 W │<<<<<<<<<<├────────┤>>>>>>>>>>├───────┤>>>>>>>>>>│ 490 mA     │
+// │ -3507 W │   (a)    │ 244 V    │   (d)    │ 244 V      │    (g)   │ -5071 W │    (h)   │ 776 V  │    (k)   │ 56 V  │   (l)    │ 250 °C     │
+// │ -3605 W │   K1     │ 246 V    │   K2     │ 246 V      │    K3    └─────────┘          └────────┘          └───────┘          │ 445 A      │
+// └─────────┘          └──────────┘          └────────────┘                                   V                                  │ 0 Warnings │
+//                           V                      V                                          V                                  │ 0 Alarms   │
+//                           V                      V                                    (j)  0 W                                 └────────────┘
+//                     (c) 1400 W             (f)  0 W                                         V
+//                           V                      V                                          V
+//                           V                      V                                       ┌──────┐
+//                        ┌──────┐               ┌──────┐                                   │ Load │
+//                        │ Load │               │ Load │                                   └──────┘
+//                        └──────┘               └──────┘
+
+
+//   Calculated values: c,d & h
+//   ==========================
+//
+//   
+//    AC side
+//    a + b - c - d = 0     [eq1]
+//    d + e - f - g = 0     [eq2]
+//   
+//    c & d are not measured!
+//   
+//    d = f + g - e  [eq2]
+//    c = a + b - d  [eq1]
+//   
+//    DC side
+//    h + i - j - k = 0  [eq3]
+//   
+//    if Dc load not existing,  h = i - k [eq4]
+
+//    k = l        assuming no losses in DCDC // this is changed now l is equal total battery power
+//    j = h + i - k   [eq3]
+
+
+public static class Topology
+{
+    
+    public static TextBlock CreateTopologyTextBlock(this StatusRecord status)
+    {
+        var a = status.GridMeter?.Ac.Power.Active;
+        var b = status.PvOnAcGrid?.Dc.Power.Value;
+        var e = status.PvOnAcIsland?.Dc.Power.Value;
+        var f = status.LoadOnAcIsland?.Ac.Power.Active;
+        var g = status.AcDc.Dc.Power.Value;
+        var h = status.AcDcToDcLink?.Power.Value;
+        var i = status.PvOnDc?.Dc.Power.Value;
+        var k = status.DcDc.Dc.Link.Power.Value;
+        var l = status.Battery is not null ? status.Battery.Power : 0;
+        var j = status.LoadOnDc?.Power.Value;
+        var d = status.AcGridToAcIsland?.Power.Active;
+        var c = status.LoadOnAcGrid?.Power.Active;
+
+        /////////////////////////////
+        
+        var grid      = status.CreateGridColumn(a);
+        var gridBus   = status.CreateGridBusColumn(b, c, d);
+        var islandBus = status.CreateIslandBusColumn(e, f, g);
+        var inverter  = status.CreateInverterColumn(h);
+        var dcBus     = status.CreateDcBusColumn(i, j, k);
+        var dcDc      = status.CreateDcDcColumn(l);
+        var batteries = status.CreateBatteryColumn();
+
+        return TextBlock.AlignCenterVertical
+        (
+            grid,
+            gridBus,
+            islandBus,
+            inverter,
+            dcBus,
+            dcDc,
+            batteries
+        );
+    }
+
+    private static TextBlock CreateGridColumn(this StatusRecord status, ActivePower? a)
+    {
+        //  ┌─────────┐          
+        //  │  Grid   │          
+        //  ├─────────┤ -10.3 kW 
+        //  │ -3205 W │<<<<<<<<<<
+        //  │ -3507 W │   (a)    
+        //  │ -3605 W │   K1     
+        //  └─────────┘             
+        
+        var gridMeterAc = status.GridMeter?.Ac;
+        var k1          = status.Relays?.K1GridBusIsConnectedToGrid;
+        
+        var gridBox  = PhasePowersActive(gridMeterAc).TitleBox("Grid");
+        var gridFlow = SwitchedFlow(k1, a, "K1");
+
+        return TextBlock.AlignCenterVertical(gridBox, gridFlow);
+    }
+
+
+    private static TextBlock CreateGridBusColumn(this StatusRecord status, 
+                                                      ActivePower? b, 
+                                                      ActivePower? c, 
+                                                      ActivePower? d)
+    {
+        
+        //                ┌────┐                          
+        //                │ Pv │                          
+        //                └────┘                          
+        //                  V                                
+        //                  V                                
+        //            (b)  0 W                              
+        //                  V                                
+        //                  V                                
+        //             ┌──────────┐                    
+        //             │ Grid Bus │                    
+        //             ├──────────┤ -11.7 kW  
+        //             │ 244 V    │<<<<<<<<<<
+        //             │ 244 V    │   (d)           
+        //             │ 246 V    │   K2               
+        //             └──────────┘                    
+        //                  V                                
+        //                  V                                
+        //            (c) 1400 W                          
+        //                  V                                
+        //                  V                                
+        //               ┌──────┐                        
+        //               │ Load │                        
+        //               └──────┘                        
+
+
+        ////////////// top //////////////
+        
+        var pvBox  = TextBlock.FromString("PV").Box();
+        var pvFlow = Flow.Vertical(b);
+
+        ////////////// center  //////////////
+
+        // on IslandBus show voltages measured by inverter
+        // on GridBus show voltages measured by grid meter
+        // ought to be approx the same
+        
+        var gridMeterAc = status.GridMeter?.Ac;
+        var k2 = status.Relays?.K2IslandBusIsConnectedToGridBus;
+        
+        var busBox  = PhaseVoltages(gridMeterAc).TitleBox("Grid Bus");
+        var busFlow = SwitchedFlow(k2, d, "K2");
+        
+        ////////////// bottom //////////////
+        
+        var loadFlow = Flow.Vertical(c);
+        var loadBox  = TextBlock.FromString("Load").Box();
+
+        ////////////// assemble //////////////
+        
+        return TextBlock.AlignCenterVertical
+        (
+            TextBlock.AlignCenterHorizontal(pvBox, pvFlow, busBox, loadFlow, loadBox),
+            busFlow
+        );
+    }
+
+    
+    private static TextBlock CreateIslandBusColumn(this StatusRecord status, 
+                                                        ActivePower? e, 
+                                                        ActivePower? f, 
+                                                        ActivePower? g)
+    {
+        
+        //           ┌────┐                                       
+        //           │ Pv │                                                         
+        //           └────┘                                                         
+        //             V                                                                        
+        //             V                                                                        
+        //       (e)  0 W                                                                   
+        //             V                                                                        
+        //             V                                                                        
+        //       ┌────────────┐                                        
+        //       │ Island Bus │                                        
+        //       ├────────────┤ -11.7 kW    
+        //       │ 244 V      │<<<<<<<<<<
+        //       │ 244 V      │    (g)                  
+        //       │ 246 V      │    K3                        
+        //       └────────────┘                                        
+        //             V                                                                        
+        //             V                                                                        
+        //       (f)  0 W                                                             
+        //             V                                                                        
+        //             V                                                                        
+        //          ┌──────┐                                                    
+        //          │ Load │                                                    
+        //          └──────┘                                    
+
+
+        ////////////// top //////////////
+        
+        var pvBox  = TextBlock.FromString("PV").Box();
+        var pvFlow = Flow.Vertical(e);
+
+        ////////////// center  //////////////
+
+        // on IslandBus show voltages measured by inverter
+        // on GridBus show voltages measured by grid meter
+        // ought to be approx the same
+        
+        var inverterAc = status.AcDc.Ac;
+        var busBox     = PhaseVoltages(inverterAc).TitleBox("Island Bus");  
+        var busFlow    = status.IslandBusToInverterConnection(g);
+        
+        ////////////// bottom //////////////
+        
+        var loadFlow = Flow.Vertical(f);
+        var loadBox  = TextBlock.FromString("Load").Box();
+
+        ////////////// assemble //////////////
+        
+        return TextBlock.AlignCenterVertical
+        (
+            TextBlock.AlignCenterHorizontal(pvBox, pvFlow, busBox, loadFlow, loadBox),
+            busFlow
+        );
+    }
+
+
+
+    private static TextBlock CreateInverterColumn(this StatusRecord status, ActivePower? h)
+    {
+        //    ┌─────────┐          
+        //    │  AC/DC  │          
+        //    ├─────────┤ -11.7 kW 
+        //    │ -6646 W │<<<<<<<<<<
+        //    │ -5071 W │    (h)   
+        //    └─────────┘              
+        
+        var inverterBox = status
+                         .AcDc
+                         .Devices
+                         .Select(d => d.Status.Ac.Power.Active)
+                         .Apply(TextBlock.AlignLeft)
+                         .TitleBox("AC/DC");
+
+        var dcFlow = Flow.Horizontal(h);
+
+        return TextBlock.AlignCenterVertical(inverterBox, dcFlow);
+    }
+
+
+    [SuppressMessage("ReSharper", "PossibleMultipleEnumeration")]
+    private static TextBlock IslandBusToInverterConnection(this StatusRecord status, ActivePower? g)
+    {
+        if (status.Relays is null)
+            return TextBlock.FromString("????????");
+        
+        var nInverters = status.AcDc.Devices.Count;
+        
+        var k3S = status
+                 .Relays
+                 .K3InverterIsConnectedToIslandBus
+                 .Take(nInverters);
+
+        if (k3S.Prepend(true).All(s => s))   // TODO: display when no ACDC present
+            return Flow.Horizontal(g);
+
+        return Switch.Open("K3");
+    }
+
+    private static TextBlock CreateDcDcColumn(this StatusRecord status, ActivePower? p)
+    {
+        var dc48Voltage = status.DcDc.Dc.Battery.Voltage.ToDisplayString();
+
+        var busBox = TextBlock
+                    .AlignLeft(dc48Voltage)
+                    .TitleBox("DC/DC");
+
+        var busFlow = Flow.Horizontal(p);
+        
+        return TextBlock.AlignCenterVertical(busBox, busFlow);
+    }
+
+    private static TextBlock CreateDcBusColumn(this StatusRecord status, 
+                                                    ActivePower? i, 
+                                                    ActivePower? j, 
+                                                    ActivePower? k)
+    {
+        //          ┌────┐
+        //          │ Pv │
+        //          └────┘
+        //            V
+        //            V
+        //     (i)  13.2 kW           
+        //            V               
+        //            V               
+        //        ┌────────┐          
+        //        │ Dc Bus │  1008 W  
+        //        ├────────┤>>>>>>>>>>
+        //        │ 776 V  │    (k)   
+        //        └────────┘          
+        //            V               
+        //            V               
+        //      (j)  0 W              
+        //            V
+        //            V
+        //         ┌──────┐
+        //         │ Load │
+        //         └──────┘
+        //            
+        
+        
+        /////////////////// top ///////////////////
+
+        var mppt = status.PvOnDc;
+
+        var nStrings = mppt is not null
+                     ? "x" + mppt.Strings.Count
+                     : "?";
+            
+        var pvBox    = TextBlock.FromString($"PV {nStrings}").Box();
+        var pvToBus  = Flow.Vertical(i);
+        
+        /////////////////// center ///////////////////
+
+        var dcBusVoltage = status.DcDc.Dc.Link.Voltage;
+        
+        var dcBusBox = dcBusVoltage
+                      .ToDisplayString()
+                      .Apply(TextBlock.FromString)
+                      .TitleBox("DC Bus ");
+
+        var busFlow = Flow.Horizontal(k);
+        
+        /////////////////// bottom ///////////////////
+        
+        var busToLoad = Flow.Vertical(j);
+        var loadBox   = TextBlock.FromString("DC Load").Box();
+        
+        ////////////// assemble //////////////
+        
+        return TextBlock.AlignCenterVertical
+        (
+            TextBlock.AlignCenterHorizontal(pvBox, pvToBus, dcBusBox, busToLoad, loadBox),
+            busFlow
+        );
+    }
+
+    private static TextBlock CreateBatteryColumn(this StatusRecord status)
+    {
+        var bat = status.Battery;
+        if (bat is null)
+            return TextBlock.AlignLeft("no battery").Box();
+        
+        
+        var batteryAvgBox = CreateAveragedBatteryBox(bat);
+
+        var batteryBoxes = bat
+                          .Devices
+                          .Select(CreateBatteryBox)
+                          .ToReadOnlyList();
+
+
+        var individualWithAvgBox = TextBlock
+                                    .AlignCenterVertical
+                                    (
+                                        batteryAvgBox ,
+                                        batteryBoxes.Any()
+                                            ? TextBlock.AlignLeft(batteryBoxes)
+                                            : TextBlock.Empty 
+                                    );
+
+        return status.Config.DisplayIndividualBatteries ? individualWithAvgBox : batteryAvgBox;
+    }
+
+    private static TextBlock CreateAveragedBatteryBox(BatteryDeligreenRecords bat)
+    {
+        var voltage        = bat.Voltage.ToDisplayString();
+        var soc            = bat.Devices.Any() ? bat.Devices.Average(b => b.BatteryDeligreenDataRecord.Soc).Percent().ToDisplayString() : "0"; // TODO
+        var current        = bat.Current.ToDisplayString();
+        var busCurrent     = bat.Devices.Any() ? bat.Devices.Sum(b => b.BatteryDeligreenDataRecord.BusCurrent).A().ToDisplayString() : "0";
+        var temp           = bat.TemperatureCell1.ToDisplayString();
+        //var alarms         = bat.Alarms.Count   + " Alarms";
+        //var warnings       = bat.Warnings.Count + " Warnings";
+        var nBatteries     = bat.Devices.Count;
+        
+        return TextBlock
+              .AlignLeft
+              (
+                  voltage,
+                  soc,
+                  current,
+                  busCurrent,
+                  temp
+              )
+              .TitleBox($"Battery x{nBatteries}");
+    }
+
+    private static TextBlock PhaseVoltages(Ac3Bus? ac)
+    {
+        return TextBlock.AlignLeft
+        (
+            ac?.L1.Voltage.ToDisplayString() ?? "???",
+            ac?.L2.Voltage.ToDisplayString() ?? "???",
+            ac?.L3.Voltage.ToDisplayString() ?? "???"
+        );
+    }
+
+    private static TextBlock PhasePowersActive(Ac3Bus? ac)
+    {
+        return TextBlock.AlignLeft
+        (
+            ac?.L1.Power.Active.ToDisplayString() ?? "???",
+            ac?.L2.Power.Active.ToDisplayString() ?? "???",
+            ac?.L3.Power.Active.ToDisplayString() ?? "???"
+        );
+    }
+
+    private static TextBlock CreateBatteryBox(BatteryDeligreenRecord battery, Int32 i)
+    {
+        var batteryWarnings = "";// battery.Warnings.Any();
+        var batteryAlarms   = "";// battery.Alarms.Any();
+
+        var content = TextBlock.AlignLeft
+        (
+            battery.BatteryDeligreenDataRecord.BusVoltage.ToDisplayString(),
+            battery.BatteryDeligreenDataRecord.Soc.ToDisplayString(),
+            battery.BatteryDeligreenDataRecord.BusCurrent.ToDisplayString() + " C/D",
+            battery.BatteryDeligreenDataRecord.TemperaturesList.PowerTemperature.ToDisplayString(),
+            battery.BatteryDeligreenDataRecord.BatteryCapacity.ToString(CultureInfo.CurrentCulture) ,
+            batteryWarnings,
+            batteryAlarms
+        );
+
+        var box  = content.TitleBox($"Battery {i + 1}");
+        var flow = Flow.Horizontal(battery.BatteryDeligreenDataRecord.Power);
+
+        return TextBlock.AlignCenterVertical(flow, box);
+    }
+
+
+    private static TextBlock SwitchedFlow(Boolean? switchClosed, ActivePower? power, String kx)
+    {
+        return switchClosed is null ? TextBlock.FromString("??????????")
+             : !switchClosed.Value  ? Switch.Open(kx)
+             : power is null        ? TextBlock.FromString("??????????") 
+             :                        Flow.Horizontal(power);
+    }
+
+    //We are fake using the ampt instead of PvOnAc, We dont have a Pv on Ac at the moment and we don't have it classes :TODO 
+    public static AcPowerDevice? CalculateGridBusLoad(EmuMeterRegisters? gridMeter, AmptStatus? pvOnAcGrid, AcPowerDevice? gridBusToIslandBusPower)
+    {
+        var a = gridMeter              ?.Ac.Power;
+        var b = pvOnAcGrid  is not null? pvOnAcGrid?.Dc.Power.Value: 0;
+        var d = gridBusToIslandBusPower?.Power;
+        
+        if (a is null || b is null || d is null)
+            return null;
+        
+        var c = a + b - d;  // [eq1]
+        
+        return new AcPowerDevice { Power = c };        
+    }
+    
+    //We are fake using the ampt instead of PvOnAc, We dont have a Pv on Ac at the moment and we don't have it classes :TODO 
+    public static DcPowerDevice? CalculateAcDcToDcLink(AmptStatus? pvOnDc, DcDcDevicesRecord? dcDc, AcDcDevicesRecord acDc)
+    {
+        var i = pvOnDc?.Dc.Power;
+        var k = dcDc?.Dc.Link.Power;        // We don't check on the DcDc because this device is mandatory
+        var g = acDc?.Dc.Power;
+        
+        if (i is null || k is null )
+        {
+            return new DcPowerDevice { Power = g };  
+        }
+        
+        var h = -(i - k);  // [eq4]  
+        
+        return new DcPowerDevice { Power = h };        
+    }
+    
+    //We are fake using the ampt instead of PvOnAc, We dont have a Pv on Ac at the moment and we don't have it classes :TODO 
+    public static AcPowerDevice? CalculateGridBusToIslandBusPower(AmptStatus? pvOnAcIsland, EmuMeterRegisters? loadOnAcIsland, AcDcDevicesRecord? acDc)
+    {
+        var e = pvOnAcIsland   is not null? pvOnAcIsland?.Dc.Power.Value: 0;
+        var f = loadOnAcIsland is not null? loadOnAcIsland?.Ac.Power    : 0;
+        var g = acDc          ?.Ac.Power; // We don't check on the AcDc because this device is mandatory, if this does not exist the system will not start
+
+        if (e is null || f is null || g is null)
+            return null;
+
+        var d = f + g - e; // [eq2]
+
+        return new AcPowerDevice { Power = d };
+    }
+
+    public static DcPowerDevice? CalculateDcLoad(AcDcDevicesRecord? acDc, AmptStatus? pvOnDc, DcDcDevicesRecord? dcDc)
+    {
+        var h = acDc?.Dc.Power;             // We don't check on the AcDc because this device is mandatory
+        var i = pvOnDc is not null?  pvOnDc?.Dc.Power: 0;
+        var k = dcDc?.Dc.Link.Power;        // We don't check on the DcDc because this device is mandatory
+        
+        if (h is null || i is null || k is null)
+            return null;
+
+        var j = h + i - k; // [eq3]
+
+        return new DcPowerDevice { Power = j};
+    }
+    
+}
diff --git a/csharp/App_backup/SodiStoreMax/tunnelstoSalimaxX.sh b/csharp/App_backup/SodiStoreMax/tunnelstoSalimaxX.sh
new file mode 100755
index 000000000..03b256195
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/tunnelstoSalimaxX.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+
+host="ie-entwicklung@$1"
+
+tunnel() {
+  name=$1
+  ip=$2
+  rPort=$3
+  lPort=$4
+
+  echo -n "$name @ $ip    mapped to    localhost:$lPort  "
+  ssh -nNTL "$lPort:$ip:$rPort" "$host" 2> /dev/null &
+
+  until nc -vz 127.0.0.1 $lPort 2> /dev/null
+  do
+    echo -n .
+    sleep 0.3
+  done
+
+  echo "ok"
+}
+
+echo ""
+
+tunnel "Trumpf Inverter (http)  " 10.0.2.1 80 8001
+tunnel "Trumpf DCDC (http)      " 10.0.3.1 80 8002
+tunnel "Ext Emu Meter (http)    " 10.0.4.1 80 8003
+tunnel "Int Emu Meter (http)    " 10.0.4.2 80 8004
+tunnel "AMPT (http)             " 10.0.5.1 8080 8005
+
+tunnel "Trumpf Inverter (modbus)" 10.0.2.1 502 5001
+tunnel "Trumpf DCDC (modbus)    " 10.0.3.1 502 5002
+tunnel "Ext Emu Meter (modbus)  " 10.0.4.1 502 5003
+tunnel "Int Emu Meter           " 10.0.4.2 502 5004
+tunnel "AMPT (modbus)           " 10.0.5.1 502 5005
+tunnel "Adam                    " 10.0.1.1 502 5006  #for AMAX is 10.0.1.3
+tunnel "Batteries               " 127.0.0.1 6855 5007
+
+echo
+echo "press any key to close the tunnels ..."
+read -r -n 1 -s
+kill $(jobs -p)
+echo "done"
+
diff --git a/csharp/App_backup/SodiStoreMax/uploadBatteryFw/AF0A.bin b/csharp/App_backup/SodiStoreMax/uploadBatteryFw/AF0A.bin
new file mode 100644
index 000000000..e5b7b9aba
Binary files /dev/null and b/csharp/App_backup/SodiStoreMax/uploadBatteryFw/AF0A.bin differ
diff --git a/csharp/App_backup/SodiStoreMax/uploadBatteryFw/update_firmware.sh b/csharp/App_backup/SodiStoreMax/uploadBatteryFw/update_firmware.sh
new file mode 100755
index 000000000..fab8d1174
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/uploadBatteryFw/update_firmware.sh
@@ -0,0 +1,29 @@
+#!/bin/bash
+
+dotnet_version='net6.0'
+salimax_ip="$1"
+username='ie-entwicklung'
+root_password='Salimax4x25'
+
+set -e
+
+ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.29")
+battery_ids=("2" "3" "4" "5" "6" "7" "8" "9" "10" "11")
+
+
+for ip_address in "${ip_addresses[@]}"; do
+    scp upload-bms-firmware AF0A.bin "$username"@"$ip_address":/home/"$username"
+    ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S systemctl stop battery.service"
+    ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S apt install python3-pip -y"
+    ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S pip3 install pymodbus"
+    
+    for battery in "${battery_ids[@]}"; do
+        ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S python3 upload-bms-firmware ttyUSB0 " "$battery" " AF0A.bin"
+    done
+    ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S systemctl start battery.service"
+    ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S systemctl rm upload-bms-firmware AF0A.bin"
+
+echo "Deployed and ran commands on $ip_address"
+done
+
+
diff --git a/csharp/App_backup/SodiStoreMax/uploadBatteryFw/upload-bms-firmware b/csharp/App_backup/SodiStoreMax/uploadBatteryFw/upload-bms-firmware
new file mode 100755
index 000000000..58d2c804d
--- /dev/null
+++ b/csharp/App_backup/SodiStoreMax/uploadBatteryFw/upload-bms-firmware
@@ -0,0 +1,288 @@
+#!/usr/bin/python2 -u
+# coding=utf-8
+
+import os
+import struct
+from time import sleep
+
+import serial
+from os import system
+import logging
+
+from pymodbus.client import ModbusSerialClient as Modbus
+from pymodbus.exceptions import ModbusIOException
+from pymodbus.pdu import ModbusResponse
+from os.path import dirname, abspath
+from sys import path, argv, exit
+
+path.append(dirname(dirname(abspath(__file__))))
+
+PAGE_SIZE = 0x100
+HALF_PAGE =int( PAGE_SIZE / 2)
+WRITE_ENABLE = [1]
+FIRMWARE_VERSION_REGISTER = 1054
+
+ERASE_FLASH_REGISTER = 0x2084
+RESET_REGISTER = 0x2087
+logging.basicConfig(level=logging.INFO)
+
+
+# trick the pycharm type-checker into thinking Callable is in scope, not used at runtime
+# noinspection PyUnreachableCode
+if False:
+    from typing import List, NoReturn, Iterable, Optional
+
+def calc_stm32_crc_round(crc, data):
+    # type: (int, int) -> int
+    crc = crc ^ data
+    for _ in range(32):
+        xor = (crc & 0x80000000) != 0
+        crc = (crc & 0x7FFFFFFF) << 1    # clear bit 31 because python ints have "infinite" bits
+        if xor:
+            crc = crc ^ 0x04C11DB7
+
+    return crc
+
+
+def calc_stm32_crc(data):
+    # type: (Iterable[int]) -> int
+    crc = 0xFFFFFFFF
+
+    for dw in data:
+        crc = calc_stm32_crc_round(crc, dw)
+
+    return crc
+
+
+def init_modbus(tty):
+    # type: (str) -> Modbus
+
+    return Modbus(
+        port='/dev/' + tty,
+        method='rtu',
+        baudrate=115200,
+        stopbits=1,
+        bytesize=8,
+        timeout=0.5,  # seconds
+        parity=serial.PARITY_ODD)
+
+
+def failed(response):
+    # type: (ModbusResponse) -> bool
+
+    # Todo 'ModbusIOException' object has no attribute 'function_code'
+    return response.function_code > 0x80
+
+
+def clear_flash(modbus, slave_address):
+    # type: (Modbus, int) -> bool
+
+    print ('erasing flash...')
+
+    write_response = modbus.write_registers(address=0x2084, values=[1], slave=slave_address)
+
+    if failed(write_response):
+        print('erasing flash FAILED')
+        return False
+
+    flash_countdown = 17
+    while flash_countdown > 0:
+        read_response = modbus.read_holding_registers(address=0x2085, count=1, slave=slave_address)
+
+        if failed(read_response):
+            print('erasing flash FAILED')
+            return False
+
+        if read_response.registers[0] != flash_countdown:
+            flash_countdown = read_response.registers[0]
+
+            msg = str(100 * (16 - flash_countdown) / 16) + '%'
+            print('\r{0} '.format(msg), end=' ')
+
+    print('done!')
+
+    return True
+
+
+# noinspection PyShadowingBuiltins
+def bytes_to_words(bytes):
+    # type: (str) -> List[int]
+    return list(struct.unpack('>' + int(len(bytes)/2) * 'H', bytes))
+
+
+def send_half_page_1(modbus, slave_address, data, page):
+    # type: (Modbus, int, str, int) -> NoReturn
+
+    first_half = [page] + bytes_to_words(data[:HALF_PAGE])
+    write_first_half = modbus.write_registers(0x2000, first_half, slave=slave_address)
+
+    if failed(write_first_half):
+        raise Exception("Failed to write page " + str(page))
+
+
+def send_half_page_2(modbus, slave_address, data, page):
+    # type: (Modbus, int, str, int) -> NoReturn
+
+    registers = bytes_to_words(data[HALF_PAGE:]) + calc_crc(page, data) + WRITE_ENABLE
+    result = modbus.write_registers(0x2041, registers, slave=slave_address)
+
+    if failed(result):
+        raise Exception("Failed to write page " + str(page))
+
+
+def get_fw_name(fw_path):
+    # type: (str) -> str
+    return fw_path.split('/')[-1].split('.')[0]
+
+
+def upload_fw(modbus, slave_id, fw_path, fw_name):
+    # type: (Modbus, int, str, str) -> NoReturn
+
+    with open(fw_path, "rb") as f:
+
+        size = os.fstat(f.fileno()).st_size
+        n_pages = int(size / PAGE_SIZE)
+
+        print('uploading firmware ' + fw_name + ' to BMS ...')
+
+        for page in range(0, n_pages):
+            page_data = f.read(PAGE_SIZE)
+
+            msg = "page " + str(page + 1) + '/' + str(n_pages) + ' ' + str(100 * page / n_pages + 1) + '%'
+            print('\r{0} '.format(msg), end=' ')
+
+            if is_page_empty(page_data):
+                continue
+            sleep(0.01)
+            send_half_page_1(modbus, slave_id, page_data, page)
+            sleep(0.01)
+            send_half_page_2(modbus, slave_id, page_data, page)
+
+
+def is_page_empty(page):
+    # type: (str) -> bool
+    return  page.count(b'\xff') == len(page)
+
+
+def reset_bms(modbus, slave_id):
+    # type: (Modbus, int) -> bool
+
+    print ('resetting BMS...')
+
+    result = modbus.write_registers(RESET_REGISTER, [1], slave=slave_id)
+
+    # expecting a ModbusIOException (timeout)
+    # BMS can no longer reply because it is already reset
+    success = isinstance(result, ModbusIOException)
+
+    if success:
+        print('done')
+    else:
+        print('FAILED to reset battery!')
+
+    return success
+
+
+def calc_crc(page, data):
+    # type: (int, str) -> List[int]
+
+    crc = calc_stm32_crc([page] + bytes_to_words(data))
+    crc_bytes = struct.pack('>L', crc)
+
+    return bytes_to_words(crc_bytes)
+
+
+def identify_battery(modbus, slave_id):
+    # type: (Modbus, int) -> Optional[str]
+    print("slave id=",slave_id)
+    target = 'battery ' + str(slave_id) + ' at ' + '502'
+
+    try:
+
+        print(('contacting  ...'))
+
+        response = modbus.read_input_registers(address=FIRMWARE_VERSION_REGISTER, count=1, slave=slave_id)
+        fw = '{0:0>4X}'.format(response.registers[0])
+
+        print(('found battery with firmware ' + fw))
+
+        return fw
+
+    except:
+        print(('failed to communicate with  '))
+        return None
+
+
+def print_usage():
+    print(('Usage:   ' + __file__ + ' <serial device> <battery id> <firmware>'))
+    print(('Example: ' + __file__ + ' ttyUSB0 2 A08C.bin'))
+
+
+def parse_cmdline_args(argv):
+    # type: (List[str]) -> (str, str, str, str)
+
+    def fail_with(msg):
+        print(msg)
+        print_usage()
+        exit(1)
+
+    if len(argv) < 1:
+        fail_with('missing argument for tty device')
+
+    if len(argv) < 2:
+        fail_with('missing argument for battery ID')
+
+    if len(argv) < 3:
+        fail_with('missing argument for firmware')
+
+    return argv[0], int(argv[1]), argv[2], get_fw_name(argv[2])
+
+
+def verify_firmware(modbus, battery_id, fw_name):
+    # type: (Modbus, int, str) -> NoReturn
+
+    fw_verify = identify_battery(modbus, battery_id)
+
+    if fw_verify == fw_name:
+        print('SUCCESS')
+    else:
+        print('FAILED to verify uploaded firmware!')
+        if fw_verify is not None:
+            print('expected firmware version ' + fw_name + ' but got ' + fw_verify)
+
+
+def wait_for_bms_reboot():
+    # type: () -> NoReturn
+
+    # wait 20s for the battery to reboot
+
+    print('waiting for BMS to reboot...')
+
+    for t in range(20, 0, -1):
+        print('\r{0} '.format(t), end=' ')
+        sleep(1)
+
+    print('0')
+
+
+def main(argv):
+    # type: (List[str]) -> NoReturn
+
+    tty, battery_id, fw_path, fw_name = parse_cmdline_args(argv)
+    with  init_modbus(tty) as modbus:
+
+        if identify_battery(modbus, battery_id) is None:
+            return
+
+        clear_flash(modbus, battery_id)
+        upload_fw(modbus, battery_id, fw_path, fw_name)
+
+        if not reset_bms(modbus, battery_id):
+            return
+
+        wait_for_bms_reboot()
+
+        verify_firmware(modbus, battery_id, fw_name)
+
+
+main(argv[1:])
diff --git a/csharp/App_backup/VrmGrabber/Controller.cs b/csharp/App_backup/VrmGrabber/Controller.cs
new file mode 100644
index 000000000..46dddd884
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/Controller.cs
@@ -0,0 +1,260 @@
+using CliWrap;
+using HandlebarsDotNet;
+using InnovEnergy.App.VrmGrabber.Database;
+using InnovEnergy.Lib.Utils;
+using Microsoft.AspNetCore.Mvc;
+using VrmInstallation = InnovEnergy.Lib.Victron.VictronVRM.Installation;
+
+namespace InnovEnergy.App.VrmGrabber;
+
+public record InstallationToHtmlInterface(
+    String Name,
+    String Ip,
+    Int64 Vrm,
+    String Identifier,
+    String Serial,
+    String EscapedName,
+    String Online,
+    String LastSeen,
+    String NumBatteries,
+    String BatteryVersion,
+    String BatteryUpdateStatus,
+    String ServerIp = "10.2.0.1",      //TODO MAKE ME DYNAMIC
+    String FirmwareVersion = "AF09"    //Todo automatically grab newest version?
+    );
+
+[Controller]
+public class Controller : ControllerBase  
+{
+    
+    //Todo automatically grab newest version?
+    private const String FirmwareVersion = "AF09";
+
+
+    [HttpGet]
+    [Route("/")]
+    [Produces("text/html")]
+    public ActionResult Index()
+    {
+        const String source = @"<head>
+                                                          <style>
+                                                          tbody {
+                                                              background-color: #e4f0f5;
+                                                          }
+                                                         
+
+tbody tr:nth-child(odd) {
+  background-color: #ECE9E9;
+}
+
+th, td { /* cell */
+  padding: 0.75rem;
+  font-size: 0.9375rem;
+}
+
+th { /* header cell */
+  font-weight: 700;
+  text-align: left;
+  color: #272838;
+  border-bottom: 2px solid #EB9486;
+
+  position: sticky;
+  top: 0;
+  background-color: #F9F8F8;
+}
+                                                          table {
+                                                              border-collapse: collapse;
+                                                              width: 100%;
+                                                              border: 2px solid rgb(200, 200, 200);
+                                                              letter-spacing: 1px;
+                                                              font-family: sans-serif;
+                                                              font-size: 0.8rem;
+                                                              position: absolute; top: 0; bottom: 0; left: 0; right: 0;
+                                                          }
+                                                          
+                                                          thead th {
+                                                              border: 1px solid rgb(190, 190, 190);
+                                                              padding: 5px 10px;
+                                                              position: sticky;
+                                                              position: -webkit-sticky;
+                                                              top: 0px;
+                                                              background: white;
+                                                              z-index: 999;
+                                                          }
+                                                          
+                                                          td {
+                                                              text-align: left;
+                                                          }
+                                                          #managerTable {
+                                                              overflow: hidden;
+                                                          }</style></head>
+
+                                                          <div id='managerTable'>
+                                                          <table>
+                                                               <tbody>
+                                                               <tr>
+                                                               <th>Name    This site is updated once per day!</th>
+                                                               <th>Gui</th>
+                                                               <th>VRM</th>
+                                                               <th>Grafana</th>
+                                                               <th>Identifier</th>
+                                                               <th>Last Seen</th>
+                                                               <th>Serial</th>
+                                                               <th>#Batteries</th>
+                                                               <th>Firmware-Version</th>
+                                                               <th>Update</th>
+                                                               <th>Last Update Status</th>
+                                                               </tr>
+                                                               {{#inst}}
+                                                                   {{> installations}}
+                                                               {{/inst}}
+                                                               </tbody>
+                                                          </table>
+                                                          <div id='managerTable'>";
+
+
+
+        const String partialSource = @"<tr><td>{{Name}}</td>
+            <td><a target='_blank' href=http://{{Ip}}>{{online}} {{Ip}}</a></td>
+            <td><a target='_blank' href=https://vrm.victronenergy.com/installation/{{Vrm}}/dashboard>VRM</a></td>
+            <td><a target='_blank' href='https://salidomo.innovenergy.ch/d/ENkNRQXmk/installation?refresh=5s&orgId=1&var-Installation={{EscapedName}}&kiosk=tv'>Grafana</a></td>
+            <td>{{Identifier}}</td>
+            <td>{{LastSeen}}</td>
+            <td>{{Serial}}</td>
+            <td>{{NumBatteries}}</td>
+            <td>{{BatteryVersion}}</td>
+            <td><a target='_blank' href=http://{{ServerIp}}/UpdateBatteryFirmware/{{Ip}}>⬆️{{FirmwareVersion}}</a></td>
+            <td>{{BatteryUpdateStatus}}</td>
+            </tr>";
+        
+        var installationsInDb = Db.Installations.OrderBy(i => i.Name, StringComparer.OrdinalIgnoreCase).ToList();
+        if (installationsInDb.Count == 0) return new ContentResult 
+        {
+            ContentType = "text/html",
+            Content = "<p>Please wait page is still loading</p>"
+        };
+        
+        Handlebars.RegisterTemplate("installations", partialSource);
+        var template = Handlebars.Compile(source);
+        var installsForHtml = installationsInDb.Select(i => new InstallationToHtmlInterface(
+            i.Name,
+            i.Ip,
+            i.Vrm,
+            i.Identifier,
+            i.Serial, 
+            i.EscapedName, 
+            i.Online,
+            DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(i.LastSeen)).ToString(),
+            i.NumberOfBatteries,
+            i.BatteryFirmwareVersion,
+            i.BatteryUpdateStatus));
+
+        var data = new
+        {
+            inst = installsForHtml,
+        };
+
+        var result = template(data);
+        
+        return new ContentResult 
+        {
+            ContentType = "text/html",
+            Content = result
+        };
+    }
+
+
+    [HttpGet("UpdateBatteryFirmware/{installationIp}")]
+    public async Task<String> UpdateBatteryFirmware(String installationIp)
+    {
+        //We need the DeviceName of the battery (ttyUSB?)
+        var pathToBattery = await Db.ExecuteBufferedAsyncCommandOnIp(installationIp, "dbus-send --system --dest=com.victronenergy.system --type=method_call --print-reply /ServiceMapping/com_victronenergy_battery_1 com.victronenergy.BusItem.GetText");
+
+        var split = pathToBattery.Split('"');
+        var split2 = pathToBattery.Split(' ');
+        
+        if (split.Length < 2 || split2.Length < 1)
+        {
+            Console.WriteLine(pathToBattery + " Split failed ");
+            return "Update failed";
+        }
+        if (split[1] == "Failed" || split2[0] == "Error") return "Update failed";
+
+        
+
+        await SendNewBatteryFirmware(installationIp);
+        var batteryTtyName = split[1].Split(".").Last();
+        var localCommand = "echo start";
+        var installation = Db.Installations.First(installation => installation.Ip == installationIp);
+        installation.BatteryUpdateStatus = "Running";
+        Db.Update(installation: installation);
+        var batteryIdsResult = await Db.ExecuteBufferedAsyncCommandOnIp(installationIp, $"dbus-send --system --dest=com.victronenergy.battery.{batteryTtyName} --type=method_call --print-reply / com.victronenergy.BusItem.GetText  | grep -E -o '_Battery/[0-9]+/' | grep -E -o '[0-9]+'| sort -u");
+        var batteryIds = batteryIdsResult.Split("\n").ToList();
+        batteryIds.Pop();
+        
+        foreach (var batteryId in batteryIds)
+        {
+            localCommand = localCommand.Append(
+                $" && /opt/innovenergy/scripts/upload-bms-firmware {batteryTtyName} {batteryId} /opt/innovenergy/bms-firmware/{FirmwareVersion}.bin");
+        }
+        #pragma warning disable CS4014
+        // Console.WriteLine(localCommand);
+        Db.ExecuteBufferedAsyncCommandOnIp(installationIp, localCommand)
+            .ContinueWith(async t =>
+            {
+                    Console.WriteLine(t.Result);
+                    installation.BatteryUpdateStatus = "Complete";
+                    // installation.BatteryFirmwareVersion = FirmwareVersion;
+                    Db.Update(installation: installation);
+                    var vrmInst = await FindVrmInstallationByIp(installation.Ip!);
+                    await UpdateVrmTagsToNewFirmware(installationIp);
+                    await Db.UpdateAlarms(vrmInst);
+            });
+        #pragma warning restore CS4014
+        return "Battery update is successfully initiated, it will take around 15 minutes to complete! You can close this page now.";
+    }
+
+    private static async Task UpdateVrmTagsToNewFirmware(String installationIp)
+    {
+        var vrmInstallation = await FindVrmInstallationByIp(installationIp);
+        var tags = await vrmInstallation.GetTags();
+
+        async void RemoveTag(String t) => await vrmInstallation.RemoveTags(t);
+
+        tags.Where(tag => tag.StartsWith("FM-"))
+            .Do(RemoveTag);
+
+        await vrmInstallation.AddTags("FM-" + FirmwareVersion);
+    }
+
+    private static async Task<VrmInstallation> FindVrmInstallationByIp(String installationIp)
+    {
+        var installationId = Db.Installations.Where(i => i.Ip == installationIp).Select(i => i.Vrm).First();
+        var vrmAccount = await Db.GetVrmAccount();
+        return await vrmAccount.GetInstallation(installationId!);
+    }
+
+    private static async Task SendNewBatteryFirmware(String installationIp)
+    {
+        await Cli.Wrap("rsync")
+            .WithArguments($@"-r --relative bms-firmware/{FirmwareVersion}.bin")
+            .AppendArgument($@"root@{installationIp}:/opt/innovenergy")
+            .ExecuteAsync();
+    }
+    // [HttpGet(nameof(GetInstallation))]
+    // [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
+    // public Object GetInstallation(UInt64 serialNumber)
+    // {
+    //     var instList = Db.InstallationsAndDetails.Values.ToList();
+    //     foreach (var detailList in instList.Select((value, index) => new { Value = value, Index = index}))
+    //     {
+    //         if (detailList.Value.All(detail => detail.Json["idSite"]?.GetValue<UInt64>() != serialNumber)) continue;
+    //         var retour = Db.InstallationsAndDetails.Keys.ToList()[detailList.Index].Json;
+    //         retour["details"] = JsonSerializer.Deserialize<JsonArray>(JsonSerializer.Serialize(detailList.Value.Select(d => d.Json).ToArray()));
+    //         return retour;
+    //     }
+    //
+    //     return new NotFoundResult();
+    // }
+}
+
diff --git a/csharp/App_backup/VrmGrabber/Controller_test.cs b/csharp/App_backup/VrmGrabber/Controller_test.cs
new file mode 100644
index 000000000..40213effd
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/Controller_test.cs
@@ -0,0 +1,311 @@
+using CliWrap;
+using HandlebarsDotNet;
+using InnovEnergy.App.VrmGrabber.Database;
+using InnovEnergy.Lib.Utils;
+using Microsoft.AspNetCore.Mvc;
+using VrmInstallation = InnovEnergy.Lib.Victron.VictronVRM.Installation;
+
+namespace InnovEnergy.App.VrmGrabber;
+
+public record InstallationToHtmlInterface(
+    String Name,
+    String Ip,
+    Int64 Vrm,
+    String Identifier,
+    String Serial,
+    String EscapedName,
+    String Online,
+    String LastSeen,
+    String NumBatteries,
+    String BatteryVersion,
+    String BatteryUpdateStatus,
+    String ServerIp = "10.2.0.1",      //TODO MAKE ME DYNAMIC
+    String FirmwareVersion = "AF09",    //Todo automatically grab newest version?
+    String NodeRedFiles = "NodeRedFiles"
+    );
+
+[Controller]
+public class Controller : ControllerBase  
+{
+    
+    //Todo automatically grab newest version?
+    private const String FirmwareVersion = "AF09";
+
+
+    [HttpGet]
+    [Route("/")]
+    [Produces("text/html")]
+    public ActionResult Index()
+    {
+        const String source = @"<head>
+                                                          <style>
+                                                          tbody {
+                                                              background-color: #e4f0f5;
+                                                          }
+                                                         
+
+tbody tr:nth-child(odd) {
+  background-color: #ECE9E9;
+}
+
+th, td { /* cell */
+  padding: 0.75rem;
+  font-size: 0.9375rem;
+}
+
+th { /* header cell */
+  font-weight: 700;
+  text-align: left;
+  color: #272838;
+  border-bottom: 2px solid #EB9486;
+
+  position: sticky;
+  top: 0;
+  background-color: #F9F8F8;
+}
+                                                          table {
+                                                              border-collapse: collapse;
+                                                              width: 100%;
+                                                              border: 2px solid rgb(200, 200, 200);
+                                                              letter-spacing: 1px;
+                                                              font-family: sans-serif;
+                                                              font-size: 0.8rem;
+                                                              position: absolute; top: 0; bottom: 0; left: 0; right: 0;
+                                                          }
+                                                          
+                                                          thead th {
+                                                              border: 1px solid rgb(190, 190, 190);
+                                                              padding: 5px 10px;
+                                                              position: sticky;
+                                                              position: -webkit-sticky;
+                                                              top: 0px;
+                                                              background: white;
+                                                              z-index: 999;
+                                                          }
+                                                          
+                                                          td {
+                                                              text-align: left;
+                                                          }
+                                                          #managerTable {
+                                                              overflow: hidden;
+                                                          }</style></head>
+
+                                                          <div id='managerTable'>
+                                                          <table>
+                                                               <tbody>
+                                                               <tr>
+                                                               <th>Name    This site is updated once per day!</th>
+                                                               <th>Gui</th>
+                                                               <th>VRM</th>
+                                                               <th>Grafana</th>
+                                                               <th>Identifier</th>
+                                                               <th>Last Seen</th>
+                                                               <th>Serial</th>
+                                                               <th>#Batteries</th>
+                                                               <th>Firmware-Version</th>
+                                                               <th>Update</th>
+                                                               <th>Last Update Status</th>
+                                                               <th>Upload Node Red Files</th>
+                                                               </tr>
+                                                               {{#inst}}
+                                                                   {{> installations}}
+                                                               {{/inst}}
+                                                               </tbody>
+                                                          </table>
+                                                          <div id='managerTable'>";
+
+
+
+        const String partialSource = @"<tr><td>{{Name}}</td>
+            <td><a target='_blank' href=http://{{Ip}}>{{online}} {{Ip}}</a></td>
+            <td><a target='_blank' href=https://vrm.victronenergy.com/installation/{{Vrm}}/dashboard>VRM</a></td>
+            <td><a target='_blank' href='https://salidomo.innovenergy.ch/d/ENkNRQXmk/installation?refresh=5s&orgId=1&var-Installation={{EscapedName}}&kiosk=tv'>Grafana</a></td>
+            <td>{{Identifier}}</td>
+            <td>{{LastSeen}}</td>
+            <td>{{Serial}}</td>
+            <td>{{NumBatteries}}</td>
+            <td>{{BatteryVersion}}</td>
+            <td><a target='_blank' href=http://{{ServerIp}}/UpdateBatteryFirmware/{{Ip}}>⬆️{{FirmwareVersion}}</a></td>
+            <td>{{BatteryUpdateStatus}}</td>
+            <td><a target='_blank' href=http://{{ServerIp}}/UploadNodeRedFiles/{{Ip}}>⬆️{{NodeRedFiles}}</a></td>
+            </tr>";
+        
+        var installationsInDb = Db.Installations.OrderBy(i => i.Name, StringComparer.OrdinalIgnoreCase).ToList();
+        if (installationsInDb.Count == 0) return new ContentResult 
+        {
+            ContentType = "text/html",
+            Content = "<p>Please wait page is still loading</p>"
+        };
+        
+        Handlebars.RegisterTemplate("installations", partialSource);
+        var template = Handlebars.Compile(source);
+        var installsForHtml = installationsInDb.Select(i => new InstallationToHtmlInterface(
+            i.Name,
+            i.Ip,
+            i.Vrm,
+            i.Identifier,
+            i.Serial, 
+            i.EscapedName, 
+            i.Online,
+            DateTimeOffset.FromUnixTimeSeconds(Convert.ToInt64(i.LastSeen)).ToString(),
+            i.NumberOfBatteries,
+            i.BatteryFirmwareVersion,
+            i.BatteryUpdateStatus));
+
+        var data = new
+        {
+            inst = installsForHtml,
+        };
+
+        var result = template(data);
+        
+        return new ContentResult 
+        {
+            ContentType = "text/html",
+            Content = result
+        };
+    }
+
+
+    [HttpGet("UpdateBatteryFirmware/{installationIp}")]
+    public async Task<String> UpdateBatteryFirmware(String installationIp)
+    {
+        //We need the DeviceName of the battery (ttyUSB?)
+        var pathToBattery = await Db.ExecuteBufferedAsyncCommandOnIp(installationIp, "dbus-send --system --dest=com.victronenergy.system --type=method_call --print-reply /ServiceMapping/com_victronenergy_battery_1 com.victronenergy.BusItem.GetText");
+
+        var split = pathToBattery.Split('"');
+        var split2 = pathToBattery.Split(' ');
+        
+        if (split.Length < 2 || split2.Length < 1)
+        {
+            Console.WriteLine(pathToBattery + " Split failed ");
+            return "Update failed";
+        }
+        if (split[1] == "Failed" || split2[0] == "Error") return "Update failed";
+
+        
+
+        await SendNewBatteryFirmware(installationIp);
+        var batteryTtyName = split[1].Split(".").Last();
+        var localCommand = "echo start";
+        var installation = Db.Installations.First(installation => installation.Ip == installationIp);
+        installation.BatteryUpdateStatus = "Running";
+        Db.Update(installation: installation);
+        var batteryIdsResult = await Db.ExecuteBufferedAsyncCommandOnIp(installationIp, $"dbus-send --system --dest=com.victronenergy.battery.{batteryTtyName} --type=method_call --print-reply / com.victronenergy.BusItem.GetText  | grep -E -o '_Battery/[0-9]+/' | grep -E -o '[0-9]+'| sort -u");
+        var batteryIds = batteryIdsResult.Split("\n").ToList();
+        batteryIds.Pop();
+        
+        foreach (var batteryId in batteryIds)
+        {
+            localCommand = localCommand.Append(
+                $" && /opt/innovenergy/scripts/upload-bms-firmware {batteryTtyName} {batteryId} /opt/innovenergy/bms-firmware/{FirmwareVersion}.bin");
+        }
+        #pragma warning disable CS4014
+        // Console.WriteLine(localCommand);
+        Db.ExecuteBufferedAsyncCommandOnIp(installationIp, localCommand)
+            .ContinueWith(async t =>
+            {
+                    Console.WriteLine(t.Result);
+                    installation.BatteryUpdateStatus = "Complete";
+                    // installation.BatteryFirmwareVersion = FirmwareVersion;
+                    Db.Update(installation: installation);
+                    var vrmInst = await FindVrmInstallationByIp(installation.Ip!);
+                    await UpdateVrmTagsToNewFirmware(installationIp);
+                    await Db.UpdateAlarms(vrmInst);
+            });
+        #pragma warning restore CS4014
+        return "Battery update is successfully initiated, it will take around 15 minutes to complete! You can close this page now.";
+    }
+
+    private static async Task UpdateVrmTagsToNewFirmware(String installationIp)
+    {
+        var vrmInstallation = await FindVrmInstallationByIp(installationIp);
+        var tags = await vrmInstallation.GetTags();
+
+        async void RemoveTag(String t) => await vrmInstallation.RemoveTags(t);
+
+        tags.Where(tag => tag.StartsWith("FM-"))
+            .Do(RemoveTag);
+
+        await vrmInstallation.AddTags("FM-" + FirmwareVersion);
+    }
+
+    private static async Task<VrmInstallation> FindVrmInstallationByIp(String installationIp)
+    {
+        var installationId = Db.Installations.Where(i => i.Ip == installationIp).Select(i => i.Vrm).First();
+        var vrmAccount = await Db.GetVrmAccount();
+        return await vrmAccount.GetInstallation(installationId!);
+    }
+
+    private static async Task SendNewBatteryFirmware(String installationIp)
+    {
+        await Cli.Wrap("rsync")
+            .WithArguments($@"-r --relative bms-firmware/{FirmwareVersion}.bin")
+            .AppendArgument($@"root@{installationIp}:/opt/innovenergy")
+            .ExecuteAsync();
+    }
+    // [HttpGet(nameof(GetInstallation))]
+    // [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
+    // public Object GetInstallation(UInt64 serialNumber)
+    // {
+    //     var instList = Db.InstallationsAndDetails.Values.ToList();
+    //     foreach (var detailList in instList.Select((value, index) => new { Value = value, Index = index}))
+    //     {
+    //         if (detailList.Value.All(detail => detail.Json["idSite"]?.GetValue<UInt64>() != serialNumber)) continue;
+    //         var retour = Db.InstallationsAndDetails.Keys.ToList()[detailList.Index].Json;
+    //         retour["details"] = JsonSerializer.Deserialize<JsonArray>(JsonSerializer.Serialize(detailList.Value.Select(d => d.Json).ToArray()));
+    //         return retour;
+    //     }
+    //
+    //     return new NotFoundResult();
+    // }
+
+    // remove the original ones????????
+    [HttpPost("UploadNodeRedFiles/{installationIp}")]
+    public async Task<IActionResult> UploadNodeRedFiles(String installationIp)
+    {
+        // Define the mapping of files to remote locations
+        var fileLocationMappings = new Dictionary<string, string>
+        {
+            { "flows.json", "/opt/data/nodered/.node-red/" },
+            { "settings-user.js", "/opt/data/nodered/.node-red/" },
+            { "rc.local", "/data/" },
+            { "dbus-fzsonick-48tl", "/data/"}
+        };
+
+        var nodeRedFilesFolder = Path.Combine(Directory.GetCurrentDirectory(), "NodeRedFiles");
+        if (!Directory.Exists(nodeRedFilesFolder))
+        {
+            return BadRequest("NodeRedFiles folder does not exist.");
+        }
+
+        var tasks = fileLocationMappings.Select(async mapping =>
+        {
+            var fileName = mapping.Key;
+            var remoteLocation = mapping.Value;
+
+            var filePath = Path.Combine(nodeRedFilesFolder, fileName);
+            if (!System.IO.File.Exists(filePath))
+            {
+                throw new FileNotFoundException($"File {fileName} not found in {nodeRedFilesFolder}.");
+            }
+
+            // Execute the SCP command to upload the file
+            await Cli.Wrap("rsync")
+            .WithArguments($@"-r {filePath}")
+            .AppendArgument($@"root@{installationIp}:{remoteLocation}")
+            .ExecuteAsync();
+        });
+
+        try
+        {
+            await Task.WhenAll(tasks);
+            return Ok("All files uploaded successfully.");
+        }
+        catch (Exception ex)
+        {
+            return StatusCode(500, $"An error occurred while uploading files: {ex.Message}");
+        }
+    }
+}
+
diff --git a/csharp/App_backup/VrmGrabber/DataTypes/Installation.cs b/csharp/App_backup/VrmGrabber/DataTypes/Installation.cs
new file mode 100644
index 000000000..58e68a4d5
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/DataTypes/Installation.cs
@@ -0,0 +1,43 @@
+using SQLite;
+
+namespace InnovEnergy.App.VrmGrabber.DataTypes;
+
+
+public class Installation
+{
+    public Installation(String? argName, String? argIp, Int64 argVrm, String? argIdentifier, String? serial, String? urlEncode, String? online, String? lastSeen, String details, String numberOfBatteries, String batteryFirmwareVersion, String? batteryUpdateStatus)
+    {
+        Name = argName;
+        Ip          = argIp;
+        Vrm         = argVrm;
+        Identifier  = argIdentifier;
+        Serial      = serial;
+        EscapedName = urlEncode;
+        Online      = online;
+        LastSeen    = lastSeen;
+        Details = details;
+        NumberOfBatteries = numberOfBatteries;
+        BatteryFirmwareVersion = batteryFirmwareVersion;
+        BatteryUpdateStatus = batteryUpdateStatus;
+    }
+
+    public Installation()
+    {
+    }
+
+    public String?        Name                              { get; set;} 
+    public String?        Ip                                { get; set;} 
+    public Int64          Vrm                               { get; set;} 
+    
+    [PrimaryKey]
+    public String?        Identifier                        { get; set;} 
+    public String?        Serial                            { get; set;} 
+    public String?        EscapedName                       { get; set;} 
+    public String?        Online                            { get; set;} 
+    public String?        LastSeen                          { get; set;}
+    public String?        NumberOfBatteries                 { get; set;}
+    public String?        BatteryFirmwareVersion            { get; set;}
+    public String?        BatteryUpdateStatus               { get; set;}
+
+    public String? Details { get; set; }        //JSON
+}
\ No newline at end of file
diff --git a/csharp/App_backup/VrmGrabber/DataTypes/Methods/Installation.cs b/csharp/App_backup/VrmGrabber/DataTypes/Methods/Installation.cs
new file mode 100644
index 000000000..7f89dd8d5
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/DataTypes/Methods/Installation.cs
@@ -0,0 +1,7 @@
+namespace InnovEnergy.App.VrmGrabber.DataTypes.Methods;
+
+
+public static class InstallationMethods
+{
+}
+
diff --git a/csharp/App_backup/VrmGrabber/DataTypes/TreeNode.cs b/csharp/App_backup/VrmGrabber/DataTypes/TreeNode.cs
new file mode 100644
index 000000000..e141688b7
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/DataTypes/TreeNode.cs
@@ -0,0 +1,11 @@
+using SQLite;
+
+namespace InnovEnergy.App.VrmGrabber.DataTypes;
+
+public abstract partial class TreeNode
+{
+    [PrimaryKey, AutoIncrement]
+    public virtual         Int64  Id          { get; set; }
+    public         String Information { get; set; } = "";  // unstructured random info
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/VrmGrabber/Database/Create.cs b/csharp/App_backup/VrmGrabber/Database/Create.cs
new file mode 100644
index 000000000..b8d7dc25f
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/Database/Create.cs
@@ -0,0 +1,13 @@
+using InnovEnergy.App.VrmGrabber.DataTypes;
+
+namespace InnovEnergy.App.VrmGrabber.Database;
+
+
+public static partial class Db 
+{
+    public static Boolean Create(Installation installation)
+    {
+        // SQLite wrapper is smart and *modifies* t's Id to the one generated (autoincrement) by the insertion
+        return Connection.Insert(installation) > 0;
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/VrmGrabber/Database/Db.cs b/csharp/App_backup/VrmGrabber/Database/Db.cs
new file mode 100644
index 000000000..f0adf3cda
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/Database/Db.cs
@@ -0,0 +1,299 @@
+using System.Diagnostics.CodeAnalysis;
+using System.IO.Enumeration;
+using System.Runtime.InteropServices;
+using System.Text.Json.Nodes;
+using System.Web;
+using CliWrap;
+using CliWrap.Buffered;
+using InnovEnergy.App.RemoteSupportConsole;
+using InnovEnergy.Lib.Utils;
+using InnovEnergy.Lib.Victron.VictronVRM;
+using SQLite;
+using static System.Text.Json.JsonSerializer;
+using static InnovEnergy.App.VrmGrabber.Database.Systemd;
+using Installation = InnovEnergy.App.VrmGrabber.DataTypes.Installation;
+using VrmInstallation = InnovEnergy.Lib.Victron.VictronVRM.Installation;
+using FILE=System.IO.File;
+
+
+namespace InnovEnergy.App.VrmGrabber.Database;
+
+public static class Systemd
+{
+    [DllImport("libsystemd.so.0", CharSet = CharSet.Unicode)]
+    public static extern Int32 sd_notify(Int32 unsetEnvironment, String state);
+}
+
+public class InstallationDetails
+{
+    public InstallationDetails(String? ip, IReadOnlyList<Detail> details)
+    {
+        Details = details;
+        Ip = ip;
+    }
+
+    public IReadOnlyList<Detail>? Details { get; set; }
+    public String? Ip { get; set; }
+}
+
+public static partial class Db
+{
+    public static Dictionary<Installation, InstallationDetails> InstallationsAndDetails;
+
+
+    private const String DbPath = "./db.sqlite";
+
+    private static SQLiteConnection Connection { get; } = new SQLiteConnection(DbPath);
+
+    public static TableQuery<Installation> Installations => Connection.Table<Installation>();
+
+    public static void Init()
+    {
+        // used to force static constructor
+    }
+
+
+    static Db()
+    {
+        // on startup create/migrate tables
+
+        Connection.RunInTransaction(() => { Connection.CreateTable<Installation>(); });
+    }
+    
+    public static async Task UpdateDetailsAndInstallations()
+    {
+        sd_notify(0, "READY=1");
+        do {
+            await UpdateInstallationsAndDetailsFromVrm(0);
+        }
+        while (true) ;
+    }
+
+    [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
+    private static async Task UpdateInstallationsAndDetailsFromVrm(Int32 _)
+    {
+        var user = await GetVrmAccount();
+        var readOnlyInstallations = await user.GetInstallations();
+        
+        var installations = readOnlyInstallations.ToList();
+        installations.Shuffle();        // This
+
+        foreach (var installation in installations)
+        {
+            Console.WriteLine(installation.Name);
+             sd_notify(0, "WATCHDOG=1");
+            var details = await GetInstallationDetails(installation);
+            // Thread.Sleep(1000);
+            await UpdateAlarms(installation);
+            var ip = Ip(details);
+            var updatedInstallation = new Installation(
+                installation.Name,
+                ip[0],
+                (Int64)installation.IdSite,
+                installation.Identifier,
+                details.Details.MachineSerial() ?? "Unknown",
+                HttpUtility.UrlEncode(installation.Name),
+                ip[1],
+                details.Details.Last().Json["timestamp"].ToString(),
+                Serialize(details.Details),
+                await NumberOfBatteries(ip[0], ip[1]),
+                await BatteryFirmwareVersion(ip[0], ip[1]),
+                "No updates"
+            );
+            
+            if (ip[0] != "Unknown")
+                await UpdateInstallationName(installation, ip[0]);
+            
+            if (GetInstallationByIdentifier(installation.Identifier) == null)
+            {
+                Create(updatedInstallation);
+            }
+            else
+            {
+                Update(updatedInstallation);
+            }
+        }
+    }
+
+    [RequiresUnreferencedCode(
+        "Calls System.Text.Json.JsonSerializer.SerializeToElement<TValue>(TValue, JsonSerializerOptions)")]
+    public static async Task<Boolean> UpdateAlarms(VrmInstallation installation)
+    {
+        // installation.GetDevices().batteryMonitor.setTemperatureAlarms(245,250,315,313);
+        var alarmJson = JsonNode.Parse(File.ReadAllText("./alarm.json"))!.AsObject();
+        var tags = await installation.GetTags();
+        if (tags.Contains("FM-AF09"))
+        {
+            // Console.WriteLine(installation.IdSite.ToString());
+            // await installation.GetAlarms();
+            return await installation.SetAlarms(alarmJson);
+            
+        }
+
+        return true;
+    }
+
+    public static async Task<VrmAccount> GetVrmAccount()
+    {
+        var fileContent = await File.ReadAllTextAsync("./token.json");
+        var acc = Deserialize<AccToken>(fileContent);
+        var user = VrmAccount.Token(acc!.idUser, acc.token);
+        return user;
+    }
+
+    private static async Task<String> BatteryFirmwareVersion(String? ip, String? online)
+    {
+        if (ip is null or "Unknown" || online == "❌") return "Unknown";
+        var pathToBattery = await ExecuteBufferedAsyncCommandOnIp(ip, "dbus-send --system --dest=com.victronenergy.system --type=method_call --print-reply /ServiceMapping/com_victronenergy_battery_1 com.victronenergy.BusItem.GetText");
+        
+        if (pathToBattery.Split('"')[1].StartsWith("Error")) return "Unknown";
+
+        
+        var command = $"dbus-send --system --dest={pathToBattery.Split('"')[1]} --type=method_call --print-reply /FirmwareVersion com.victronenergy.BusItem.GetText";
+        var returnString = await ExecuteBufferedAsyncCommandOnIp(ip, command);
+        var returnStringShortened = returnString.Split('"')[1];
+        return returnStringShortened.Length > 10 ? "Unknown" : returnStringShortened;
+    }
+
+    private static async Task<String> NumberOfBatteries(String? ip, String? online)
+    {
+        if (ip is null or "Unknown" || online == "❌") return "Failed";
+        var pathToBattery = await ExecuteBufferedAsyncCommandOnIp(ip, "dbus-send --system --dest=com.victronenergy.system --type=method_call --print-reply /ServiceMapping/com_victronenergy_battery_1 com.victronenergy.BusItem.GetText");
+
+        if (pathToBattery.Split('"')[1].StartsWith("Error")) return "Failed";
+        
+        var cmd = await ExecuteBufferedAsyncCommandOnIp(ip,$"dbus-send --system --dest={pathToBattery.Split('"')[1]} --type=method_call --print-reply /NbOfBatteries com.victronenergy.BusItem.GetText" );
+        var cmdResult = cmd.Split('"')[1];
+        return cmdResult;  //No Batteries can be found
+    }
+    
+    
+    public static async Task<String> ExecuteBufferedAsyncCommandOnIp(String? ip, String command)
+    {
+        if (ip is null or "Unknown") return "Failed";
+        
+        var cmd = await Cli.Wrap("ssh")
+            .WithArguments($@"root@{ip}")
+            .AppendArgument("-o StrictHostKeyChecking=no")
+            .AppendArgument(command)
+            .WithValidation(CommandResultValidation.None).ExecuteBufferedAsync();
+        return  cmd.ExitCode == 0 ? cmd.StandardOutput : cmd.StandardError;
+    }
+
+    private static String?[] Ip(InstallationDetails details)
+    {
+        var online = "❌";
+        var lookup = details.Ip;
+        if (lookup == "Unknown")
+        {
+            var serial = details.Details.MachineSerial() ?? "Unknown";
+            
+            if (serial != "Unknown" && FILE.Exists($@"/etc/openvpn/server/Salino/ccd/{serial}"))
+                lookup = FILE.ReadAllText($@"/etc/openvpn/server/Salino/ccd/{serial}").Split(' ')[1];
+        }
+        else
+        {
+            online = "✅";
+        }
+
+        return new[] { lookup, online };
+    }
+
+    private static async Task<InstallationDetails> GetInstallationDetails(VrmInstallation i)
+    {
+        await Task.Delay(1000);
+        try
+        {
+            var details = await i.GetDetails();
+
+            var ip = await VpnInfo.LookUpIp(i.Identifier, details.MachineSerial()) ?? "Unknown";
+
+            var installationDetails = new InstallationDetails(ip, details);
+
+            return installationDetails;
+        }
+        catch (Exception e)
+        {
+            Console.WriteLine(e);
+        }
+
+        return new InstallationDetails("Unknown", Array.Empty<Detail>());
+    }
+
+    private static async Task UpdateInstallationName(VrmInstallation installation, String? ip)
+    {
+        var oldNameInFileRequest = await Cli.Wrap("ssh")
+            .WithArguments($@"root@{ip}")
+            .AppendArgument("-o StrictHostKeyChecking=accept-new")
+            .AppendArgument("cat /data/innovenergy/openvpn/installation-name")
+            .WithValidation(CommandResultValidation.None).ExecuteBufferedAsync();
+
+        var oldNameInFileWithoutNewLine = oldNameInFileRequest.StandardOutput.TrimEnd();
+
+        if (oldNameInFileRequest.ExitCode == 0 && oldNameInFileWithoutNewLine != installation.Name)
+        {
+            var overwriteNameCommand = Cli.Wrap("ssh")
+                .WithArguments($@"root@{ip}")
+                .AppendArgument($"echo '{installation.Name}' > /data/innovenergy/openvpn/installation-name");
+
+            var overwriteNameResponse = await overwriteNameCommand
+                .WithValidation(CommandResultValidation.None).ExecuteBufferedAsync();
+
+            if (overwriteNameResponse.ExitCode != 0)
+            {
+                Console.WriteLine(overwriteNameResponse.StandardError);
+                Console.WriteLine("Renaming did not work");
+            }
+            else
+            {
+                var rebootAfterRename = await Cli.Wrap("ssh")
+                    .WithArguments($@"root@{ip}")
+                    .AppendArgument("reboot")
+                    .WithValidation(CommandResultValidation.None).ExecuteBufferedAsync();
+
+                if (rebootAfterRename.ExitCode != 0)
+                {
+                    Console.WriteLine(overwriteNameResponse.StandardError);
+                    Console.WriteLine("Rebooting did not work");
+                }
+                else
+                {
+                    Console.WriteLine($"Renamed and rebooted {installation.Name}");
+                }
+            }
+        }
+    }
+
+
+    private static Boolean RunTransaction(Func<Boolean> func)
+    {
+        var savepoint = Connection.SaveTransactionPoint();
+        var success = false;
+
+        try
+        {
+            success = func();
+        }
+        finally
+        {
+            if (success)
+                Connection.Release(savepoint);
+            else
+                Connection.RollbackTo(savepoint);
+        }
+
+        return success;
+    }
+}
+
+public class AccToken
+{
+    public UInt64 idUser { get; init;} 
+    public String token { get; init;}
+}
+
+
+/*
+dbus-send --system --dest=com.victronenergy.battery.ttyUSB1 --print-reply /FirmwareVersion \
+org.freedesktop.DBus.Properties.Get string:com.victronenergy.battery.ttyUSB1
+ */ 
diff --git a/csharp/App_backup/VrmGrabber/Database/Delete.cs b/csharp/App_backup/VrmGrabber/Database/Delete.cs
new file mode 100644
index 000000000..2bcd06a6a
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/Database/Delete.cs
@@ -0,0 +1,17 @@
+using InnovEnergy.App.VrmGrabber.DataTypes;
+
+namespace InnovEnergy.App.VrmGrabber.Database;
+
+
+public static partial class Db 
+{
+    public static Boolean Delete(Installation installation)
+    {
+        return RunTransaction(DeleteInstallationAndItsDependencies);
+
+        Boolean DeleteInstallationAndItsDependencies()
+        {
+            return Installations.Delete(i => i.Identifier == installation.Identifier) > 0;
+        }
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/VrmGrabber/Database/Read.cs b/csharp/App_backup/VrmGrabber/Database/Read.cs
new file mode 100644
index 000000000..9f7643862
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/Database/Read.cs
@@ -0,0 +1,14 @@
+using InnovEnergy.App.VrmGrabber.DataTypes;
+
+namespace InnovEnergy.App.VrmGrabber.Database;
+
+
+public static partial class Db 
+{
+
+    public static Installation? GetInstallationByIdentifier(String? identifier)
+    {
+        return Installations
+            .FirstOrDefault(i => i.Identifier == identifier);
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/VrmGrabber/Database/Update.cs b/csharp/App_backup/VrmGrabber/Database/Update.cs
new file mode 100644
index 000000000..723378685
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/Database/Update.cs
@@ -0,0 +1,13 @@
+using InnovEnergy.App.VrmGrabber.DataTypes;
+
+namespace InnovEnergy.App.VrmGrabber.Database;
+
+
+public static partial class Db 
+{
+
+    public static Boolean Update(Installation installation)
+    {
+        return Connection.Update(installation) > 0;
+    }
+}
\ No newline at end of file
diff --git a/csharp/App_backup/VrmGrabber/Program.cs b/csharp/App_backup/VrmGrabber/Program.cs
new file mode 100644
index 000000000..693f847cc
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/Program.cs
@@ -0,0 +1,36 @@
+using InnovEnergy.App.VrmGrabber.Database;
+using Microsoft.OpenApi.Models;
+
+namespace InnovEnergy.App.VrmGrabber;
+
+public static class Program
+{
+    public static async Task Main(String[] args)
+    {
+        var updateTask = Db.UpdateDetailsAndInstallations();
+        var builder = WebApplication.CreateBuilder(args);
+
+        builder.Services.AddControllers();
+        // builder.Services.AddSwaggerGen(c =>
+        // {
+        //     c.UseAllOfToExtendReferenceSchemas();
+        //     c.SupportNonNullableReferenceTypes();
+        // });
+
+        var app = builder.Build();
+        // app.UseSwagger();
+        // app.UseSwaggerUI();
+        // app.UseHttpsRedirection();
+        app.MapControllers();
+        // app.MapGet("/", () => Controller.Index());
+        var webTask = app.RunAsync();
+        await Task.WhenAll(webTask, updateTask);
+    }
+
+    private static OpenApiInfo OpenApiInfo { get; } = new OpenApiInfo
+    {
+        Title = "InnovEnergy VRM Grabber",
+        Version = "v1"
+    };
+
+}
\ No newline at end of file
diff --git a/csharp/App_backup/VrmGrabber/ShuffleClass.cs b/csharp/App_backup/VrmGrabber/ShuffleClass.cs
new file mode 100644
index 000000000..a52e40cdf
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/ShuffleClass.cs
@@ -0,0 +1,17 @@
+namespace InnovEnergy.App.VrmGrabber;
+
+public static class ShuffleClass
+{
+    private static readonly Random Rng = new Random();  
+
+    public static void Shuffle<T>(this IList<T> list)  
+    {  
+        var n = list.Count;  
+        while (n > 1) {  
+            n--;  
+            var k = Rng.Next(n + 1);  
+            (list[k], list[n]) = (list[n], list[k]);
+        }  
+    }
+    
+}
\ No newline at end of file
diff --git a/csharp/App_backup/VrmGrabber/VrmGrabber.csproj b/csharp/App_backup/VrmGrabber/VrmGrabber.csproj
new file mode 100644
index 000000000..2f42775f2
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/VrmGrabber.csproj
@@ -0,0 +1,50 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+    
+    <Import Project="../InnovEnergy.App.props" />
+
+    <PropertyGroup>
+        <PublishTrimmed>false</PublishTrimmed>
+        <RootNamespace>InnovEnergy.App.VrmGrabber</RootNamespace>
+    </PropertyGroup>
+    
+    <ItemGroup>
+        <PackageReference Include="Flurl.Http" Version="3.2.4" />
+        <PackageReference Include="Handlebars.Net" Version="2.1.4" />
+        <PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.5.1" />
+        <PackageReference Include="MailKit" Version="3.6.0" />
+        <PackageReference Include="Microsoft.AspNet.Identity.Core" Version="2.2.3" />
+        <PackageReference Include="Microsoft.AspNet.Identity.Owin" Version="2.2.3" />
+        <PackageReference Include="Microsoft.AspNet.WebApi.Core" Version="5.2.9" />
+        <PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
+        <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
+        <PackageReference Include="Microsoft.Identity.Web" Version="1.26.0" />
+        <PackageReference Include="Microsoft.Owin.Cors" Version="4.2.2" />
+        <PackageReference Include="Microsoft.Owin.Host.SystemWeb" Version="4.2.2" />
+        <PackageReference Include="Microsoft.Owin.Security" Version="4.2.2" />
+        <PackageReference Include="Microsoft.Owin.Security.Cookies" Version="4.2.2" />
+        <PackageReference Include="Microsoft.Owin.Security.OAuth" Version="4.2.2" />
+        <PackageReference Include="sqlite-net-pcl" Version="1.8.116" />
+        <PackageReference Include="sqlite-net-sqlcipher" Version="1.9.141-beta" />
+        <PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
+        <PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="7.0.6" />
+        <PackageReference Include="Swashbuckle.AspNetCore.Filters.Abstractions" Version="7.0.6" />
+    </ItemGroup>
+
+  
+    <ItemGroup>
+      <ProjectReference Include="../../Lib/Utils/Utils.csproj" />
+      <ProjectReference Include="../../Lib/Victron/VictronVRM/VictronVRM.csproj" />
+      <ProjectReference Include="../RemoteSupportConsole/RemoteSupportConsole.csproj" />
+    </ItemGroup>
+    
+    <ItemGroup>
+      <_ContentIncludedByDefault Remove="wwwroot/index.html" />
+    </ItemGroup>
+    
+    <ItemGroup>
+      <None Update="db.sqlite">
+        <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+      </None>
+    </ItemGroup>
+
+</Project>
diff --git a/csharp/App_backup/VrmGrabber/VrmGrabber.sln b/csharp/App_backup/VrmGrabber/VrmGrabber.sln
new file mode 100644
index 000000000..3e0482092
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/VrmGrabber.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}") = "VrmGrabber", "VrmGrabber.csproj", "{A3BDD9AD-F065-444E-9C2E-F777810E3BF9}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{A3BDD9AD-F065-444E-9C2E-F777810E3BF9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A3BDD9AD-F065-444E-9C2E-F777810E3BF9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A3BDD9AD-F065-444E-9C2E-F777810E3BF9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A3BDD9AD-F065-444E-9C2E-F777810E3BF9}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {274A19A1-A0B6-4EAF-BCF6-475F7C511EF3}
+	EndGlobalSection
+EndGlobal
diff --git a/csharp/App_backup/VrmGrabber/alarm.json b/csharp/App_backup/VrmGrabber/alarm.json
new file mode 100644
index 000000000..a99f75c16
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/alarm.json
@@ -0,0 +1,10 @@
+{
+  "idDataAttribute":115,
+  "instance":1,
+  "lowAlarm":245,
+  "lowAlarmHysteresis":250,
+  "highAlarm":315,
+  "highAlarmHysteresis":313,
+  "AlarmEnabled":1,
+  "NotifyAfterSeconds":60,
+  "meta_info": {"icon":"device-battery-monitor","name":"Battery Monitor [1]","idDeviceType":2,"dataAttribute":"Battery temperature"}}
diff --git a/csharp/App_backup/VrmGrabber/bms-firmware/AF09.bin b/csharp/App_backup/VrmGrabber/bms-firmware/AF09.bin
new file mode 100644
index 000000000..d714f466b
Binary files /dev/null and b/csharp/App_backup/VrmGrabber/bms-firmware/AF09.bin differ
diff --git a/csharp/App_backup/VrmGrabber/db.sqlite b/csharp/App_backup/VrmGrabber/db.sqlite
new file mode 100644
index 000000000..29961ffce
Binary files /dev/null and b/csharp/App_backup/VrmGrabber/db.sqlite differ
diff --git a/csharp/App_backup/VrmGrabber/server.py b/csharp/App_backup/VrmGrabber/server.py
new file mode 100644
index 000000000..5d5c350ec
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/server.py
@@ -0,0 +1,14 @@
+import requests as requests
+from flask import Flask
+from json2html import json2html
+
+app = Flask(__name__)
+serverUrl = "https://127.0.0.1:8000/api"  #todo change me
+
+@app.route('/')
+def hello():
+    json = requests.get(serverUrl + '/GetInstallationList', verify=False)   #TODO VERIFY
+
+    if json.content == b'0':
+        return "Still loading Installations"
+    return json2html.convert(json = json.json(), escape=False)
diff --git a/csharp/App_backup/VrmGrabber/token.json b/csharp/App_backup/VrmGrabber/token.json
new file mode 100644
index 000000000..9a0fbea24
--- /dev/null
+++ b/csharp/App_backup/VrmGrabber/token.json
@@ -0,0 +1 @@
+{"idUser":55450,"token":"909379784fa601a52ab4756c1cc9690ecc769db4b661214f9b165eda20be9913"}
\ No newline at end of file
diff --git a/firmware/2025-02-24.json b/firmware/2025-02-24.json
new file mode 100644
index 000000000..1309e125b
--- /dev/null
+++ b/firmware/2025-02-24.json
@@ -0,0 +1 @@
+UEsDBBQAAAAIAGZRWVqEoBx0dAAAAJUAAAAJAAAAZGF0YS5qc29ubcqxCsIwEADQf7k5HrlL7pJ0VAeXguAXhBJqlhbSgIr474o4Oj54TxjrclknGJiRDIz5/pUntAaOdZuuuc11mfe599Ie5/VWGgw71oDRBSIXOYmqioHD3yqaUNSKEFGyEryBU8n9836BRB1qDJocO2/Zv95QSwECFAMUAAAACABmUVlahKAcdHQAAACVAAAACQAAAAAAAAAAAAAAgAEAAAAAZGF0YS5qc29uUEsFBgAAAAABAAEANwAAAJsAAAAAAA==
\ No newline at end of file
diff --git a/firmware/2025-02-25.json b/firmware/2025-02-25.json
new file mode 100644
index 000000000..1309e125b
--- /dev/null
+++ b/firmware/2025-02-25.json
@@ -0,0 +1 @@
+UEsDBBQAAAAIAGZRWVqEoBx0dAAAAJUAAAAJAAAAZGF0YS5qc29ubcqxCsIwEADQf7k5HrlL7pJ0VAeXguAXhBJqlhbSgIr474o4Oj54TxjrclknGJiRDIz5/pUntAaOdZuuuc11mfe599Ie5/VWGgw71oDRBSIXOYmqioHD3yqaUNSKEFGyEryBU8n9836BRB1qDJocO2/Zv95QSwECFAMUAAAACABmUVlahKAcdHQAAACVAAAACQAAAAAAAAAAAAAAgAEAAAAAZGF0YS5qc29uUEsFBgAAAAABAAEANwAAAJsAAAAAAA==
\ No newline at end of file
diff --git a/firmware/Cerbo_Release/CerboReleaseFiles/dbus-fzsonick-48tl/aggregator.py b/firmware/Cerbo_Release/CerboReleaseFiles/dbus-fzsonick-48tl/aggregator.py
index 1e6c2511d..fdcbc7ed7 100755
--- a/firmware/Cerbo_Release/CerboReleaseFiles/dbus-fzsonick-48tl/aggregator.py
+++ b/firmware/Cerbo_Release/CerboReleaseFiles/dbus-fzsonick-48tl/aggregator.py
@@ -212,16 +212,16 @@ class Aggregator:
                         if node_number not in node_data:
                             node_data[node_number] = {'soc': [], 'discharge': [], 'charge': [], 'heating': []}
 
-                        value = device_data.get("Soc", {}).get("value", "N/A")
+                        value = device_data.get("Soc", {})
                         node_data[node_number]['soc'].append(float(value))
 
-                        value = device_data.get("Dc", {}).get("Power", "N/A").get("value", "N/A")
+                        value = device_data.get("Dc", {}).get("Power", "N/A")
                         value = float(value)
                         if value < 0:
                             node_data[node_number]['discharge'].append(value)
                         else:
                             node_data[node_number]['charge'].append(value)
-                        value = device_data.get("HeatingPower", "N/A").get("value", "N/A")
+                        value = device_data.get("HeatingPower", "N/A")
                         value = float(value)
                         node_data[node_number]['heating'].append(value)
 
diff --git a/firmware/Cerbo_Release/CerboReleaseFiles/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py b/firmware/Cerbo_Release/CerboReleaseFiles/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py
index 6e5185fcf..9b1f2b68c 100755
--- a/firmware/Cerbo_Release/CerboReleaseFiles/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py
+++ b/firmware/Cerbo_Release/CerboReleaseFiles/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py
@@ -1073,10 +1073,7 @@ def insert_nested_data(data, split_list, value, symbol):
     key = split_list[0]  # Get the first key in the list
 
     if len(split_list) == 1:
-        data[key] = {
-                     "value": round(value, 2) if isinstance(value, float) else value,
-                     #"symbol": str(symbol)
-                }
+        data[key] =  round(value, 2) if isinstance(value, float) else value
     else:
         if key not in data:
             data[key] = {}  # Create a new dictionary if key doesn't exist
@@ -1134,37 +1131,6 @@ def create_json_files(signals, statuses, node_numbers, alarms_number_list, warni
     with open(json_path, 'w') as jsonfile:
         # json.dump(data, jsonfile, indent=4)
         json.dump(data, jsonfile, separators=(',', ':'))
-#
-# def create_csv_files(signals, statuses, node_numbers, alarms_number_list, warnings_number_list):
-#     global s3_config, num_of_csv_files_saved
-#     timestamp = int(time.time())
-#     if timestamp % 2 != 0:
-#         timestamp -= 1
-#     # Create CSV directory if it doesn't exist
-#     if not os.path.exists(JSON_DIR):
-#         os.makedirs(JSON_DIR)
-#     csv_filename = f"{timestamp}.csv"
-#     csv_path = os.path.join(JSON_DIR, csv_filename)
-#     num_of_csv_files_saved+=1
-#
-#     # Append values to the CSV file
-#     if not os.path.exists(csv_path):
-#         with open(csv_path, 'a', newline='') as csvfile:
-#             csv_writer = csv.writer(csvfile, delimiter=';')
-#             # Add a special row for the nodes configuration
-#             nodes_config_path = "/Config/Devices/BatteryNodes"
-#             nodes_list = ",".join(str(node) for node in node_numbers)
-#             config_row = [nodes_config_path, nodes_list, ""]
-#             csv_writer.writerow(config_row)
-#             # Iterate over each node and signal to create rows in the new format
-#             for i, node in enumerate(node_numbers):
-#                 csv_writer.writerow([f"/Battery/Devices/{str(i+1)}/Alarms", alarms_number_list[i], ""])
-#                 csv_writer.writerow([f"/Battery/Devices/{str(i+1)}/Warnings", warnings_number_list[i], ""])
-#                 for s in signals:
-#                     signal_name = insert_id(s.name, i+1)
-#                     value = s.get_value(statuses[i])
-#                     row_values = [signal_name, value, s.get_text]
-#                     csv_writer.writerow(row_values)
 
 BATTERY_COUNTS_FILE = '/data/battery_count.csv'
 def load_battery_counts():
diff --git a/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/aggregator.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/aggregator.py
index 548437fb2..ae9620bd1 100755
--- a/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/aggregator.py
+++ b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/aggregator.py
@@ -215,16 +215,16 @@ class Aggregator:
                         if node_number not in node_data:
                             node_data[node_number] = {'soc': [], 'discharge': [], 'charge': [], 'heating': []}
 
-                        value = device_data.get("Soc", {}).get("value", "N/A")
+                        value = device_data.get("Soc", {})
                         node_data[node_number]['soc'].append(float(value))
 
-                        value = device_data.get("Dc", {}).get("Power", "N/A").get("value", "N/A")
+                        value = device_data.get("Dc", {}).get("Power", "N/A")
                         value=float(value)
                         if value < 0:
                             node_data[node_number]['discharge'].append(value)
                         else:
                             node_data[node_number]['charge'].append(value)
-                        value = device_data.get("HeatingPower", "N/A").get("value", "N/A")
+                        value = device_data.get("HeatingPower", "N/A")
                         value = float(value)
                         node_data[node_number]['heating'].append(value)
 
diff --git a/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py
index 68c986cc5..a7bc071e3 100755
--- a/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py
+++ b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/dbus-fzsonick-48tl.py
@@ -712,10 +712,8 @@ def insert_nested_data(data, split_list, value, symbol):
     key = split_list[0]  # Get the first key in the list
 
     if len(split_list) == 1:
-        data[key] = {
-                     "value": round(value, 2) if isinstance(value, float) else value,
-                     #"symbol": str(symbol)
-                }
+        data[key] =  round(value, 2) if isinstance(value, float) else value
+                
     else:
         if key not in data:
             data[key] = {}  # Create a new dictionary if key doesn't exist
@@ -748,11 +746,13 @@ def create_json_files(signals, statuses, node_numbers, alarms_number_list, warni
         device_data["Alarms"] = alarms_number_list[i]
         device_data["Warnings"] = warnings_number_list[i]
 
+
         # Iterate over the signals and add their values
         for s in signals:
+            #print(s.name)
             split_list = s.name.split("/")[3:]
-            #print(split_list)
             value = s.get_value(statuses[i])
+            #print(value)
             symbol=s.get_text
             insert_nested_data(device_data, split_list, value, symbol)
 
diff --git a/firmware/update_all_installations.sh b/firmware/update_all_installations.sh
index 1ea7c0a92..53e9bf9ce 100755
--- a/firmware/update_all_installations.sh
+++ b/firmware/update_all_installations.sh
@@ -11,8 +11,516 @@ set -e
 venus_release_file_path="./Venus_Release/VenusReleaseFiles"
 cerbo_release_file_path="./Cerbo_Release/CerboReleaseFiles"
 
-venus_ip_addresses=("10.2.0.191" "10.2.1.36" "10.2.1.108")  
-cerbo_ip_addresses=("10.2.2.212" "10.2.4.181" "10.2.3.198") 
+venus_ip_addresses=("10.2.4.155" "10.2.3.178" "10.2.3.164" "10.2.4.114" "10.2.3.133" "10.2.2.72" "10.2.2.11"  "10.2.2.128" "10.2.2.100" "10.2.2.243")
+#
+#venus_ip_addresses=(
+#    "10.2.1.35" "10.2.0.104" "10.2.1.159" "10.2.0.224" "10.2.0.155" "10.2.0.209" "10.2.0.227" "10.2.0.211"
+#    "10.2.1.134" "10.2.0.130" "10.2.1.169" "10.2.0.105" "10.2.0.220" "10.2.1.124" "10.2.1.2" "10.2.1.158"
+#    "10.2.0.195" "10.2.1.171" "10.2.0.225" "10.2.1.53" "10.2.0.107" "10.2.0.153" "10.2.1.106" "10.2.1.117"
+#    "10.2.0.145" "10.2.0.110" "10.2.1.177" "10.2.1.247" "10.2.0.101" "10.2.0.108" "10.2.1.120" "10.2.1.160"
+#    "10.2.0.98" "10.2.0.94" "10.2.1.173" "10.2.0.113" "10.2.0.150" "10.2.0.233" "10.2.1.162" "10.2.1.138"
+#    "10.2.1.165" "10.2.1.163" "10.2.1.32" "10.2.1.110" "10.2.0.103" "10.2.1.113" "10.2.0.216" "10.2.0.184"
+#    "10.2.0.191" "10.2.1.60" "10.2.1.219" "10.2.0.214" "10.2.1.83" "10.2.1.39" "10.2.0.194" "10.2.1.81"
+#    "10.2.1.125" "10.2.0.154" "10.2.0.196" "10.2.0.99" "10.2.1.130" "10.2.1.33" "10.2.0.249" "10.2.0.133"
+#    "10.2.1.118" "10.2.0.138" "10.2.0.144" "10.2.0.141" "10.2.0.188" "10.2.1.74" "10.2.1.141" "10.2.1.142"
+#    "10.2.0.193" "10.2.1.145" "10.2.1.15" "10.2.1.70" "10.2.0.135" "10.2.0.247" "10.2.0.134" "10.2.1.21"
+#    "10.2.0.131" "10.2.1.1" "10.2.1.73" "10.2.0.243" "10.2.1.19" "10.2.1.166" "10.2.0.192" "10.2.1.12"
+#    "10.2.2.188" "10.2.0.158" "10.2.1.146" "10.2.1.27" "10.2.0.202" "10.2.0.157" "10.2.1.55" "10.2.1.16"
+#    "10.2.1.28" "10.2.4.242" "10.2.0.254" "10.2.1.128" "10.2.1.58" "10.2.1.156" "10.2.1.137" "10.2.1.135"
+#    "10.2.1.24" "10.2.0.128" "10.2.1.90" "10.2.1.153" "10.2.1.93" "10.2.1.111" "10.2.1.71" "10.2.1.37"
+#    "10.2.0.127" "10.2.0.126" "10.2.1.22" "10.2.1.91" "10.2.1.123" "10.2.1.220" "10.2.1.114" "10.2.1.41"
+#    "10.2.0.125" "10.2.1.62" "10.2.0.161" "10.2.1.121" "10.2.1.10" "10.2.0.112" "10.2.0.111" "10.2.0.218"
+#    "10.2.1.49" "10.2.0.230" "10.2.1.170" "10.2.0.114" "10.2.0.140" "10.2.4.241" "10.2.1.107" "10.2.0.185"
+#    "10.2.0.124" "10.2.1.85" "10.2.0.123" "10.2.1.23" "10.2.1.44" "10.2.1.172" "10.2.1.129" "10.2.1.14"
+#    "10.2.1.59" "10.2.1.56" "10.2.1.149" "10.2.1.140" "10.2.1.116" "10.2.1.57" "10.2.1.26" "10.2.0.210"
+#    "10.2.0.237" "10.2.1.47" "10.2.1.127" "10.2.0.199" "10.2.0.252" "10.2.1.119" "10.2.0.122" "10.2.0.251"
+#    "10.2.1.7" "10.2.1.45" "10.2.1.157" "10.2.1.139" "10.2.0.119" "10.2.0.250" "10.2.1.148" "10.2.1.92"
+#    "10.2.0.245" "10.2.0.246" "10.2.1.80" "10.2.0.204" "10.2.0.203" "10.2.0.205" "10.2.1.78" "10.2.4.210"
+#    "10.2.0.222" "10.2.1.36" "10.2.1.3" "10.2.0.244" "10.2.1.176" "10.2.0.242" "10.2.1.30" "10.2.1.61"
+#    "10.2.1.168" "10.2.0.215" "10.2.1.76" "10.2.1.43" "10.2.0.200" "10.2.0.115" "10.2.1.115" "10.2.0.238"
+#    "10.2.0.189" "10.2.0.160" "10.2.0.149" "10.2.1.161" "10.2.1.101" "10.2.1.40" "10.2.0.229" "10.2.1.52"
+#    "10.2.0.239" "10.2.0.132" "10.2.0.162" "10.2.1.25" "10.2.0.118" "10.2.1.151" "10.2.0.190" "10.2.1.108"
+#    "10.2.0.219" "10.2.1.100" "10.2.1.109"
+#)
+
+
+#cerbo_ip_addresses=("10.2.1.150") 
+#
+cerbo_ip_addresses=( 
+
+10.2.5.5
+10.2.4.98
+10.2.4.97
+10.2.4.96
+10.2.4.95
+10.2.4.94
+10.2.4.92
+10.2.4.91
+10.2.4.90
+10.2.4.9
+10.2.4.88
+10.2.4.87
+10.2.4.84
+10.2.4.83
+10.2.4.82
+10.2.4.81
+10.2.4.80
+10.2.4.78
+10.2.4.77
+10.2.4.75
+10.2.4.73
+10.2.4.71
+10.2.4.70
+10.2.4.69
+10.2.4.68
+10.2.4.66
+10.2.4.63
+10.2.4.61
+10.2.4.60
+10.2.4.59
+10.2.4.57
+10.2.4.55
+10.2.4.51
+10.2.4.48
+10.2.4.47
+10.2.4.45
+10.2.4.44
+10.2.4.42
+10.2.4.4
+10.2.4.37
+10.2.4.34
+10.2.4.31
+10.2.4.30
+10.2.4.28
+10.2.4.27
+10.2.4.25
+10.2.4.24
+10.2.4.24
+10.2.4.232
+10.2.4.223
+10.2.4.213
+10.2.4.206
+10.2.4.205
+10.2.4.201
+10.2.4.20
+10.2.4.193
+10.2.4.191
+10.2.4.190
+10.2.4.188
+10.2.4.183
+10.2.4.181
+10.2.4.180
+10.2.4.179
+10.2.4.178
+10.2.4.177
+10.2.4.175
+10.2.4.17
+10.2.4.169
+10.2.4.166 
+10.2.4.160
+10.2.4.158
+10.2.4.157
+10.2.4.155
+10.2.4.15
+10.2.4.149
+10.2.4.148
+10.2.4.146
+10.2.4.143
+10.2.4.141
+10.2.4.140
+10.2.4.14
+10.2.4.137
+10.2.4.136
+10.2.4.135
+10.2.4.134
+10.2.4.129
+10.2.4.127
+10.2.4.126
+10.2.4.125
+10.2.4.123
+10.2.4.122
+10.2.4.120
+10.2.4.12
+10.2.4.119 
+10.2.4.118
+10.2.4.116
+10.2.4.115
+10.2.4.114
+10.2.4.111
+10.2.4.11
+10.2.4.109
+10.2.4.108
+10.2.4.103
+10.2.4.102
+10.2.4.100
+10.2.4.10
+10.2.3.98
+10.2.3.97
+10.2.3.91
+10.2.3.90
+10.2.3.9
+10.2.3.83
+10.2.3.8
+10.2.3.79
+10.2.3.76
+10.2.3.75
+10.2.3.74
+10.2.3.73
+10.2.3.72
+10.2.3.70
+10.2.3.7
+10.2.3.69
+10.2.3.68
+10.2.3.67
+10.2.3.66
+10.2.3.64
+10.2.3.61
+10.2.3.6
+10.2.3.54
+10.2.3.51
+10.2.3.5
+10.2.3.48
+10.2.3.46
+10.2.3.45
+10.2.3.44
+10.2.3.42
+10.2.3.41
+10.2.3.40
+10.2.3.4
+10.2.3.38
+10.2.3.37
+10.2.3.36
+10.2.3.35
+10.2.3.34
+10.2.3.30
+10.2.3.3
+10.2.3.28
+10.2.3.26
+10.2.3.253
+10.2.3.252
+10.2.3.251
+10.2.3.249
+10.2.3.247
+10.2.3.244
+10.2.3.243
+10.2.3.241
+10.2.3.240
+10.2.3.24
+10.2.3.238
+10.2.3.237
+10.2.3.236
+10.2.3.231
+10.2.3.23
+10.2.3.226
+10.2.3.225
+10.2.3.224
+10.2.3.223
+10.2.3.222
+10.2.3.221
+10.2.3.220
+10.2.3.218
+10.2.3.216
+10.2.3.215
+10.2.3.214
+10.2.3.21
+10.2.3.209
+10.2.3.205
+10.2.3.203
+10.2.3.202
+10.2.3.201
+10.2.3.200
+10.2.3.2
+10.2.3.198
+10.2.3.197
+10.2.3.195
+10.2.3.194
+10.2.3.190
+10.2.3.19
+10.2.3.188
+10.2.3.186
+10.2.3.185
+10.2.3.184
+10.2.3.183
+10.2.3.182
+10.2.3.181
+10.2.3.180
+10.2.3.18
+10.2.3.178
+10.2.3.177
+10.2.3.176
+10.2.3.175
+10.2.3.174
+10.2.3.173
+10.2.3.170
+10.2.3.169
+10.2.3.168
+10.2.3.166
+10.2.3.165
+10.2.3.164
+10.2.3.162
+10.2.3.161
+10.2.3.160
+10.2.3.159
+10.2.3.158
+10.2.3.157
+10.2.3.156
+10.2.3.155
+10.2.3.154
+10.2.3.152
+10.2.3.150
+10.2.3.15
+10.2.3.148
+10.2.3.147
+10.2.3.146
+10.2.3.144
+10.2.3.143
+10.2.3.141
+10.2.3.14
+10.2.3.135
+10.2.3.134
+10.2.3.133
+10.2.3.132
+10.2.3.131
+10.2.3.130
+10.2.3.13
+10.2.3.129
+10.2.3.126
+10.2.3.125
+10.2.3.124
+10.2.3.122
+10.2.3.12
+10.2.3.119
+10.2.3.118
+10.2.3.117
+10.2.3.116
+10.2.3.113
+10.2.3.112
+10.2.3.111
+10.2.3.108
+10.2.3.107
+10.2.3.102
+10.2.3.10
+10.2.3.1
+10.2.2.99
+10.2.2.98
+10.2.2.96
+10.2.2.91
+10.2.2.90
+10.2.2.89
+10.2.2.87
+10.2.2.86
+10.2.2.85
+10.2.2.83
+10.2.2.82
+10.2.2.81
+10.2.2.8
+10.2.2.79
+10.2.2.78
+10.2.2.77
+10.2.2.76
+10.2.2.74
+10.2.2.73
+10.2.2.72
+10.2.2.71
+10.2.2.70
+10.2.2.69
+10.2.2.66
+10.2.2.65
+10.2.2.64
+10.2.2.61
+10.2.2.59
+10.2.2.58
+10.2.2.57
+10.2.2.56
+10.2.2.52
+10.2.2.5
+10.2.2.47
+10.2.2.46
+10.2.2.45
+10.2.2.44
+10.2.2.43
+10.2.2.42
+10.2.2.39
+10.2.2.38
+10.2.2.36
+10.2.2.35
+10.2.2.34
+10.2.2.33
+10.2.2.32
+10.2.2.30
+10.2.2.3
+10.2.2.29
+10.2.2.26
+10.2.2.254
+10.2.2.250
+10.2.2.25
+10.2.2.249
+10.2.2.248
+10.2.2.245
+10.2.2.244
+10.2.2.243
+10.2.2.242
+10.2.2.240
+10.2.2.24
+10.2.2.239
+10.2.2.238
+10.2.2.235
+10.2.2.234
+10.2.2.233
+10.2.2.232
+10.2.2.231
+10.2.2.23
+10.2.2.229
+10.2.2.228
+10.2.2.227
+10.2.2.225
+10.2.2.224
+10.2.2.223
+10.2.2.222
+10.2.2.221
+10.2.2.220
+10.2.2.22
+10.2.2.219
+10.2.2.216
+10.2.2.215
+10.2.2.214
+10.2.2.213
+10.2.2.212
+10.2.2.211
+10.2.2.21
+10.2.2.208
+10.2.2.207
+10.2.2.205
+10.2.2.204
+10.2.2.203
+10.2.2.202
+10.2.2.201
+10.2.2.2
+10.2.2.198
+10.2.2.197
+10.2.2.196
+10.2.2.194
+10.2.2.193
+10.2.2.192
+10.2.2.190
+10.2.2.19
+10.2.2.189
+10.2.2.184
+10.2.2.183
+10.2.2.182
+10.2.2.181
+10.2.2.180
+10.2.2.178
+10.2.2.176
+10.2.2.174
+10.2.2.173
+10.2.2.172
+10.2.2.171
+10.2.2.170
+10.2.2.169
+10.2.2.166
+10.2.2.164
+10.2.2.163
+10.2.2.162
+10.2.2.161
+10.2.2.16
+10.2.2.159
+10.2.2.158
+10.2.2.156
+10.2.2.153
+10.2.2.152
+10.2.2.151
+10.2.2.150
+10.2.2.15
+10.2.2.146
+10.2.2.145
+10.2.2.144
+10.2.2.143
+10.2.2.142
+10.2.2.141
+10.2.2.140
+10.2.2.14
+10.2.2.137
+10.2.2.133
+10.2.2.131
+10.2.2.13
+10.2.2.129
+10.2.2.128
+10.2.2.127
+10.2.2.126
+10.2.2.124
+10.2.2.122
+10.2.2.121
+10.2.2.120
+10.2.2.12
+10.2.2.118
+10.2.2.117
+10.2.2.116
+10.2.2.115
+10.2.2.114
+10.2.2.111
+10.2.2.11
+10.2.2.109
+10.2.2.107
+10.2.2.105
+10.2.2.104
+10.2.2.103
+10.2.2.101
+10.2.2.100
+10.2.2.10
+10.2.2.1
+10.2.1.86
+10.2.1.79
+10.2.1.254
+10.2.1.253
+10.2.1.252
+10.2.1.251
+10.2.1.244
+10.2.1.243
+10.2.1.240
+10.2.1.237
+10.2.1.234
+10.2.1.231
+10.2.1.230
+10.2.1.229
+10.2.1.228
+10.2.1.227
+10.2.1.226
+10.2.1.225
+10.2.1.224
+10.2.1.223
+10.2.1.216
+10.2.1.215
+10.2.1.213
+10.2.1.212
+10.2.1.211
+10.2.1.209
+10.2.1.208
+10.2.1.207
+10.2.1.206
+10.2.1.205
+10.2.1.204
+10.2.1.203
+10.2.1.200
+10.2.1.199
+10.2.1.196
+10.2.1.195
+10.2.1.194
+10.2.1.193
+10.2.1.192
+10.2.1.191
+10.2.1.190
+10.2.1.189
+10.2.1.187
+10.2.1.186
+10.2.1.185
+10.2.1.180
+10.2.1.179
+10.2.1.150
+10.2.1.133
+10.2.1.104
+10.2.0.179
+)
 
 deploy() {
     local device_type=$1
@@ -62,6 +570,8 @@ deploy() {
             echo "Warning: Failed to copy file to /data on $ip_address"
         fi
         
+       
+        
         # Copy files
         if scp -o ConnectTimeout=10 "$release_file_path/dbus-fzsonick-48tl/aggregator.py" "root@$ip_address:/opt/victronenergy/dbus-fzsonick-48tl"; then
             echo "Copied file to /opt on $ip_address"
diff --git a/typescript/frontend-marios2/deploy.sh b/typescript/frontend-marios2/deploy.sh
index 13a10bf39..0c030cdff 100755
--- a/typescript/frontend-marios2/deploy.sh
+++ b/typescript/frontend-marios2/deploy.sh
@@ -1,4 +1,4 @@
-#npm run build && rsync -rv .*  ubuntu@194.182.190.208:~/frontend/ && ssh ubuntu@194.182.190.208 'sudo cp -rf ~/frontend/build/* /var/www/html/monitor.innov.energy/html/' && ssh ubuntu@194.182.190.208 'sudo npm install -g serve'
+npm run build && rsync -rv .*  ubuntu@194.182.190.208:~/frontend/ && ssh ubuntu@194.182.190.208 'sudo cp -rf ~/frontend/build/* /var/www/html/monitor.innov.energy/html/' && ssh ubuntu@194.182.190.208 'sudo npm install -g serve'
 
 
-npm run build && rsync -rv .*  ubuntu@91.92.154.141:~/frontend/ && ssh ubuntu@91.92.154.141 'sudo cp -rf ~/frontend/build/* /var/www/html/monitor.innov.energy/html/' && ssh ubuntu@91.92.154.141 'sudo npm install -g serve'
+#npm run build && rsync -rv .*  ubuntu@91.92.154.141:~/frontend/ && ssh ubuntu@91.92.154.141 'sudo cp -rf ~/frontend/build/* /var/www/html/monitor.innov.energy/html/' && ssh ubuntu@91.92.154.141 'sudo npm install -g serve'
diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx
index 4927b0e14..1b4cd22a9 100644
--- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx
@@ -11,7 +11,7 @@ import {
   TableRow,
   Typography
 } from '@mui/material';
-import { TopologyValues } from '../Log/graph.util';
+import { JSONRecordData } from '../Log/graph.util';
 import {
   Link,
   Route,
@@ -24,11 +24,11 @@ import { FormattedMessage } from 'react-intl';
 import { I_S3Credentials } from '../../../interfaces/S3Types';
 import routes from '../../../Resources/routes.json';
 import CircularProgress from '@mui/material/CircularProgress';
-import DetailedBatteryView from './DetailedBatteryView';
 import MainStats from './MainStats';
+import DetailedBatteryView from './DetailedBatteryView';
 
 interface BatteryViewProps {
-  values: TopologyValues;
+  values: JSONRecordData;
   s3Credentials: I_S3Credentials;
   installationId: number;
   productNum: number;
@@ -42,8 +42,12 @@ function BatteryView(props: BatteryViewProps) {
   const currentLocation = useLocation();
   const navigate = useNavigate();
   const sortedBatteryView =
-    props.values != null
-      ? [...props.values.batteryView].sort((a, b) => b.BatteryId - a.BatteryId)
+    props.values != null && props.values?.Battery?.Devices
+      ? Object.entries(props.values.Battery.Devices)
+          .map(([BatteryId, battery]) => {
+            return { BatteryId, battery }; // Here we return an object with the id and device
+          })
+          .sort((a, b) => parseInt(b.BatteryId) - parseInt(a.BatteryId))
       : [];
 
   const [loading, setLoading] = useState(sortedBatteryView.length == 0);
@@ -52,13 +56,13 @@ function BatteryView(props: BatteryViewProps) {
     navigate(routes.mainstats);
   };
 
-  const findBatteryData = (batteryId: number) => {
-    for (let i = 0; i < props.values.batteryView.length; i++) {
-      if (props.values.batteryView[i].BatteryId == batteryId) {
-        return props.values.batteryView[i];
-      }
-    }
-  };
+  // const findBatteryData = (batteryId: number) => {
+  //   for (let i = 0; i < props.values.batteryView.length; i++) {
+  //     if (props.values.batteryView[i].BatteryId == batteryId) {
+  //       return props.values.batteryView[i];
+  //     }
+  //   }
+  // };
 
   useEffect(() => {
     if (sortedBatteryView.length == 0) {
@@ -176,20 +180,37 @@ function BatteryView(props: BatteryViewProps) {
                   ></MainStats>
                 }
               />
-              {props.values.batteryView.map((battery) => (
-                <Route
-                  key={routes.detailed_view + battery.BatteryId}
-                  path={routes.detailed_view + battery.BatteryId}
-                  element={
-                    <DetailedBatteryView
-                      s3Credentials={props.s3Credentials}
-                      batteryData={findBatteryData(battery.BatteryId)}
-                      installationId={props.installationId}
-                      productNum={props.productNum}
-                    ></DetailedBatteryView>
-                  }
-                />
-              ))}
+              {Object.entries(props.values.Battery.Devices).map(
+                ([BatteryId, battery]) => (
+                  <Route
+                    key={routes.detailed_view + BatteryId}
+                    path={routes.detailed_view + BatteryId}
+                    element={
+                      <DetailedBatteryView
+                        batteryId={Number(BatteryId)}
+                        s3Credentials={props.s3Credentials}
+                        batteryData={battery}
+                        installationId={props.installationId}
+                        productNum={props.productNum}
+                      ></DetailedBatteryView>
+                    }
+                  />
+                )
+              )}
+              {/*{props.values.batteryView.map((battery) => (*/}
+              {/*  <Route*/}
+              {/*    key={routes.detailed_view + battery.BatteryId}*/}
+              {/*    path={routes.detailed_view + battery.BatteryId}*/}
+              {/*    element={*/}
+              {/*      <DetailedBatteryView*/}
+              {/*        s3Credentials={props.s3Credentials}*/}
+              {/*        batteryData={findBatteryData(battery.BatteryId)}*/}
+              {/*        installationId={props.installationId}*/}
+              {/*        productNum={props.productNum}*/}
+              {/*      ></DetailedBatteryView>*/}
+              {/*    }*/}
+              {/*  />*/}
+              {/*))}*/}
             </Routes>
           </Grid>
 
@@ -219,9 +240,9 @@ function BatteryView(props: BatteryViewProps) {
                 </TableRow>
               </TableHead>
               <TableBody>
-                {sortedBatteryView.map((battery) => (
+                {sortedBatteryView.map(({ BatteryId, battery }) => (
                   <TableRow
-                    key={battery.BatteryId}
+                    key={BatteryId}
                     style={{
                       height: '10px'
                     }}
@@ -234,9 +255,9 @@ function BatteryView(props: BatteryViewProps) {
                     >
                       <Link
                         style={{ color: 'black' }}
-                        to={routes.detailed_view + battery.BatteryId.toString()}
+                        to={routes.detailed_view + BatteryId}
                       >
-                        {'Node ' + battery.BatteryId}
+                        {'Node ' + BatteryId}
                       </Link>
                     </TableCell>
                     <TableCell
@@ -245,7 +266,7 @@ function BatteryView(props: BatteryViewProps) {
                         textAlign: 'center'
                       }}
                     >
-                      {battery.FwVersion.value}
+                      {battery.FwVersion}
                     </TableCell>
                     <TableCell
                       sx={{
@@ -253,7 +274,7 @@ function BatteryView(props: BatteryViewProps) {
                         textAlign: 'center'
                       }}
                     >
-                      {battery.Power.value + ' ' + battery.Power.unit}
+                      {battery.Dc.Power + ' ' + 'W'}
                     </TableCell>
                     <TableCell
                       sx={{
@@ -261,48 +282,44 @@ function BatteryView(props: BatteryViewProps) {
                         textAlign: 'center',
 
                         backgroundColor:
-                          battery.Voltage.value < 44 ||
-                          battery.Voltage.value > 57
+                          battery.Dc.Voltage < 44 || battery.Dc.Voltage > 57
                             ? '#FF033E'
                             : '#32CD32',
-                        color:
-                          battery.Voltage.value === '' ? 'white' : 'inherit'
+                        color: battery.Dc.Voltage ? 'black' : 'white'
                       }}
                     >
-                      {battery.Voltage.value + ' ' + battery.Voltage.unit}
+                      {battery.Dc.Voltage + ' ' + 'V'}
                     </TableCell>
                     <TableCell
                       sx={{
                         width: '10%',
                         textAlign: 'center',
                         backgroundColor:
-                          battery.Soc.value < 20
+                          battery.Soc < 20
                             ? '#FF033E'
-                            : battery.Soc.value < 50
+                            : battery.Soc < 50
                             ? '#ffbf00'
                             : '#32CD32',
-                        color: battery.Soc.value === '' ? 'white' : 'inherit'
+                        color: battery.Soc ? 'inherit' : 'white'
                       }}
                     >
-                      {battery.Soc.value + ' ' + battery.Soc.unit}
+                      {battery.Soc + ' %'}
                     </TableCell>
                     <TableCell
                       sx={{
                         width: '10%',
                         textAlign: 'center',
                         backgroundColor:
-                          battery.AverageTemperature.value > 300
+                          battery.Temperatures.Cells.Average > 300
                             ? '#FF033E'
-                            : battery.AverageTemperature.value > 280
+                            : battery.Temperatures.Cells.Average > 280
                             ? '#ffbf00'
-                            : battery.AverageTemperature.value < 245
+                            : battery.Temperatures.Cells.Average < 245
                             ? '#008FFB'
                             : '#32CD32'
                       }}
                     >
-                      {battery.AverageTemperature.value +
-                        ' ' +
-                        battery.AverageTemperature.unit}
+                      {battery.Temperatures.Cells.Average + ' °C'}
                     </TableCell>
 
                     <TableCell
@@ -311,17 +328,15 @@ function BatteryView(props: BatteryViewProps) {
                         textAlign: 'center',
                         padding: '8px',
                         fontWeight:
-                          battery.Warnings.value !== '' ? 'bold' : 'inherit',
+                          battery.Warnings !== '' ? 'bold' : 'inherit',
                         backgroundColor:
-                          battery.Warnings.value === '' ? 'inherit' : '#ff9900',
-                        color:
-                          battery.Warnings.value != '' ? 'black' : 'inherit'
+                          battery.Warnings === '' ? 'inherit' : '#ff9900',
+                        color: battery.Warnings != '' ? 'black' : 'inherit'
                       }}
                     >
-                      {battery.Warnings.value === '' ? (
+                      {battery.Warnings === '' ? (
                         'None'
-                      ) : battery.Warnings.value.toString().split('-').length >
-                        1 ? (
+                      ) : battery.Warnings.toString().split('-').length > 1 ? (
                         <Link
                           style={{ color: 'black' }}
                           to={
@@ -336,24 +351,22 @@ function BatteryView(props: BatteryViewProps) {
                           Multiple Warnings
                         </Link>
                       ) : (
-                        battery.Warnings.value
+                        battery.Warnings
                       )}
                     </TableCell>
                     <TableCell
                       sx={{
                         width: '20%',
                         textAlign: 'center',
-                        fontWeight:
-                          battery.Alarms.value !== '' ? 'bold' : 'inherit',
+                        fontWeight: battery.Alarms !== '' ? 'bold' : 'inherit',
                         backgroundColor:
-                          battery.Alarms.value === '' ? 'inherit' : '#FF033E',
-                        color: battery.Alarms.value != '' ? 'black' : 'inherit'
+                          battery.Alarms === '' ? 'inherit' : '#FF033E',
+                        color: battery.Alarms != '' ? 'black' : 'inherit'
                       }}
                     >
-                      {battery.Alarms.value === '' ? (
+                      {battery.Alarms === '' ? (
                         'None'
-                      ) : battery.Alarms.value.toString().split('-').length >
-                        1 ? (
+                      ) : battery.Alarms.toString().split('-').length > 1 ? (
                         <Link
                           style={{ color: 'black' }}
                           to={
@@ -368,7 +381,7 @@ function BatteryView(props: BatteryViewProps) {
                           Multiple Alarms
                         </Link>
                       ) : (
-                        battery.Alarms.value
+                        battery.Alarms
                       )}
                     </TableCell>
                   </TableRow>
diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryViewSalidomo.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryViewSalidomo.tsx
index 9dad16594..8b887ec25 100644
--- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryViewSalidomo.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryViewSalidomo.tsx
@@ -43,11 +43,14 @@ function BatteryViewSalidomo(props: BatteryViewProps) {
   const currentLocation = useLocation();
   const navigate = useNavigate();
 
-  const sortedBatteryView = Object.entries(props.values.Battery.Devices)
-    .map(([BatteryId, battery]) => {
-      return { BatteryId, battery }; // Here we return an object with the id and device
-    })
-    .sort((a, b) => parseInt(b.BatteryId) - parseInt(a.BatteryId));
+  const sortedBatteryView =
+    props.values != null
+      ? Object.entries(props.values.Battery.Devices)
+          .map(([BatteryId, battery]) => {
+            return { BatteryId, battery }; // Here we return an object with the id and device
+          })
+          .sort((a, b) => parseInt(b.BatteryId) - parseInt(a.BatteryId))
+      : [];
 
   const [loading, setLoading] = useState(sortedBatteryView.length == 0);
 
@@ -243,7 +246,7 @@ function BatteryViewSalidomo(props: BatteryViewProps) {
                         textAlign: 'center'
                       }}
                     >
-                      {battery.FwVersion.value}
+                      {battery.FwVersion}
                     </TableCell>
                     <TableCell
                       sx={{
@@ -251,7 +254,7 @@ function BatteryViewSalidomo(props: BatteryViewProps) {
                         textAlign: 'center'
                       }}
                     >
-                      {battery.Dc.Power.value + ' W'}
+                      {battery.Dc.Power + ' W'}
                     </TableCell>
                     <TableCell
                       sx={{
@@ -259,45 +262,44 @@ function BatteryViewSalidomo(props: BatteryViewProps) {
                         textAlign: 'center',
 
                         backgroundColor:
-                          battery.Dc.Voltage.value < 44 ||
-                          battery.Dc.Voltage.value > 57
+                          battery.Dc.Voltage < 44 || battery.Dc.Voltage > 57
                             ? '#FF033E'
                             : '#32CD32',
-                        color: battery.Dc.Voltage.value ? 'inherit' : 'white'
+                        color: battery.Dc.Voltage ? 'inherit' : 'white'
                       }}
                     >
-                      {battery.Dc.Voltage.value + ' V'}
+                      {battery.Dc.Voltage + ' V'}
                     </TableCell>
                     <TableCell
                       sx={{
                         width: '10%',
                         textAlign: 'center',
                         backgroundColor:
-                          battery.Soc.value < 20
+                          battery.Soc < 20
                             ? '#FF033E'
-                            : battery.Soc.value < 50
+                            : battery.Soc < 50
                             ? '#ffbf00'
                             : '#32CD32',
-                        color: battery.Soc.value ? 'inherit' : 'white'
+                        color: battery.Soc ? 'inherit' : 'white'
                       }}
                     >
-                      {battery.Soc.value + ' %'}
+                      {battery.Soc + ' %'}
                     </TableCell>
                     <TableCell
                       sx={{
                         width: '10%',
                         textAlign: 'center',
                         backgroundColor:
-                          battery.Temperatures.Cells.Average.value > 300
+                          battery.Temperatures.Cells.Average > 300
                             ? '#FF033E'
-                            : battery.Temperatures.Cells.Average.value > 280
+                            : battery.Temperatures.Cells.Average > 280
                             ? '#ffbf00'
-                            : battery.Temperatures.Cells.Average.value < 245
+                            : battery.Temperatures.Cells.Average < 245
                             ? '#008FFB'
                             : '#32CD32'
                       }}
                     >
-                      {battery.Temperatures.Cells.Average.value + ' C'}
+                      {battery.Temperatures.Cells.Average + ' C'}
                     </TableCell>
 
                     {/*{props.productNum === 0 && (*/}
diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryView.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryView.tsx
index f5edd711c..34f8d9954 100644
--- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryView.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryView.tsx
@@ -16,7 +16,7 @@ import {
   TableRow,
   Typography
 } from '@mui/material';
-import { Battery } from '../Log/graph.util';
+import { Device } from '../Log/graph.util';
 import { useNavigate } from 'react-router-dom';
 import ArrowBackIcon from '@mui/icons-material/ArrowBack';
 import Button from '@mui/material/Button';
@@ -25,8 +25,9 @@ import { UserType } from '../../../interfaces/UserTypes';
 import { UserContext } from '../../../contexts/userContext';
 
 interface DetailedBatteryViewProps {
+  batteryId: number;
   s3Credentials: I_S3Credentials;
-  batteryData: Battery;
+  batteryData: Device;
   installationId: number;
   productNum: number;
 }
@@ -49,7 +50,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
   const [errorMessage, setErrorMessage] = useState('');
 
   const [selectedVersion, setSelectedVersion] = useState(
-    props.batteryData.FwVersion.value
+    props.batteryData.FwVersion
   );
 
   const handleBatteryViewButton = () => {
@@ -65,35 +66,35 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
   };
 
   const [GreenisBlinking, setGreenisBlinking] = useState(
-    props.batteryData.GreenLeds.value === 'Blinking'
+    props.batteryData.Leds.Green === 'Blinking'
   );
 
   const [AmberisBlinking, setAmberisBlinking] = useState(
-    props.batteryData.AmberLeds.value === 'Blinking'
+    props.batteryData.Leds.Amber === 'Blinking'
   );
   const [RedisBlinking, setRedisBlinking] = useState(
-    props.batteryData.RedLeds.value === 'Blinking'
+    props.batteryData.Leds.Red === 'Blinking'
   );
 
   const [BlueisBlinking, setBlueisBlinking] = useState(
-    props.batteryData.BlueLeds.value === 'Blinking'
+    props.batteryData.Leds.Blue === 'Blinking'
   );
 
   useEffect(() => {
     const intervalId = setInterval(() => {
-      if (props.batteryData.AmberLeds.value === 'Blinking') {
+      if (props.batteryData.Leds.Amber === 'Blinking') {
         setAmberisBlinking((prevIsBlinking) => !prevIsBlinking);
       }
 
-      if (props.batteryData.RedLeds.value === 'Blinking') {
+      if (props.batteryData.Leds.Red === 'Blinking') {
         setRedisBlinking((prevIsBlinking) => !prevIsBlinking);
       }
 
-      if (props.batteryData.BlueLeds.value === 'Blinking') {
+      if (props.batteryData.Leds.Blue === 'Blinking') {
         setBlueisBlinking((prevIsBlinking) => !prevIsBlinking);
       }
 
-      if (props.batteryData.GreenLeds.value === 'Blinking') {
+      if (props.batteryData.Leds.Green === 'Blinking') {
         setGreenisBlinking((prevIsBlinking) => !prevIsBlinking);
       }
     }, 500); // Blink every 500 milliseconds
@@ -129,7 +130,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
 
     const res = await axiosConfig
       .post(
-        `/UpdateFirmware?batteryNode=${props.batteryData.BatteryId.toString()}&installationId=${
+        `/UpdateFirmware?batteryNode=${props.batteryId.toString()}&installationId=${
           props.installationId
         }&version=${selectedVersion}`
       )
@@ -169,7 +170,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
     try {
       // Start the job to generate the battery log
       const startRes = await axiosConfig.post(
-        `/StartDownloadBatteryLog?batteryNode=${props.batteryData.BatteryId.toString()}&installationId=${
+        `/StartDownloadBatteryLog?batteryNode=${props.batteryId.toString()}&installationId=${
           props.installationId
         }`
       );
@@ -635,7 +636,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
               fontWeight: 'bold'
             }}
           >
-            {'Node ' + props.batteryData.BatteryId}
+            {'Node ' + props.batteryId}
           </Typography>
 
           <div style={batteryStyle}>
@@ -643,8 +644,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
               style={{
                 ...batteryStringStyle,
                 backgroundColor:
-                  props.batteryData.String1Active.value == 'True' ||
-                  Number(props.batteryData.String1Active.value) == 0
+                  props.batteryData.BatteryStrings.String1Active == 'True' ||
+                  Number(props.batteryData.BatteryStrings) == 0
                     ? '#32CD32'
                     : '#FF033E'
               }}
@@ -653,8 +654,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
               style={{
                 ...batteryStringStyle,
                 backgroundColor:
-                  props.batteryData.String2Active.value == 'True' ||
-                  Number(props.batteryData.String2Active.value) == 0
+                  props.batteryData.BatteryStrings.String2Active == 'True' ||
+                  Number(props.batteryData.BatteryStrings.String2Active) == 0
                     ? '#32CD32'
                     : '#FF033E'
               }}
@@ -663,8 +664,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
               style={{
                 ...batteryStringStyle,
                 backgroundColor:
-                  props.batteryData.String3Active.value == 'True' ||
-                  Number(props.batteryData.String3Active.value) == 0
+                  props.batteryData.BatteryStrings.String3Active == 'True' ||
+                  Number(props.batteryData.BatteryStrings.String3Active) == 0
                     ? '#32CD32'
                     : '#FF033E'
               }}
@@ -673,8 +674,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
               style={{
                 ...batteryStringStyle,
                 backgroundColor:
-                  props.batteryData.String4Active.value == 'True' ||
-                  Number(props.batteryData.String4Active.value) == 0
+                  props.batteryData.BatteryStrings.String4Active == 'True' ||
+                  Number(props.batteryData.BatteryStrings.String4Active) == 0
                     ? '#32CD32'
                     : '#FF033E'
               }}
@@ -683,8 +684,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
               style={{
                 ...batteryStringStyle,
                 backgroundColor:
-                  props.batteryData.String5Active.value == 'True' ||
-                  Number(props.batteryData.String5Active.value) == 0
+                  props.batteryData.BatteryStrings.String5Active == 'True' ||
+                  Number(props.batteryData.BatteryStrings.String5Active) == 0
                     ? '#32CD32'
                     : '#FF033E'
               }}
@@ -699,8 +700,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                   marginTop: '-10px',
                   borderRadius: '50%',
                   backgroundColor:
-                    props.batteryData.GreenLeds.value === 'On' ||
-                    GreenisBlinking
+                    props.batteryData.Leds.Green === 'On' || GreenisBlinking
                       ? 'green'
                       : 'transparent'
                 }}
@@ -714,8 +714,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                   marginTop: '10px',
                   borderRadius: '50%',
                   backgroundColor:
-                    props.batteryData.AmberLeds.value === 'On' ||
-                    AmberisBlinking
+                    props.batteryData.Leds.Amber === 'On' || AmberisBlinking
                       ? 'orange'
                       : 'transparent'
                 }}
@@ -729,7 +728,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                   marginTop: '10px',
                   borderRadius: '50%',
                   backgroundColor:
-                    props.batteryData.BlueLeds.value === 'On' || BlueisBlinking
+                    props.batteryData.Leds.Blue === 'On' || BlueisBlinking
                       ? '#00ccff'
                       : 'transparent'
                 }}
@@ -743,7 +742,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                   marginTop: '10px',
                   borderRadius: '50%',
                   backgroundColor:
-                    props.batteryData.RedLeds.value === 'On' || RedisBlinking
+                    props.batteryData.Leds.Red === 'On' || RedisBlinking
                       ? 'red'
                       : 'transparent'
                 }}
@@ -803,9 +802,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.Voltage.value +
-                      ' ' +
-                      props.batteryData.Voltage.unit}
+                    {props.batteryData.Dc.Voltage + ' V'}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -825,9 +822,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.Current.value +
-                      ' ' +
-                      props.batteryData.Current.unit}
+                    {props.batteryData.Dc.Current + ' A'}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -847,9 +842,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.Power.value +
-                      ' ' +
-                      props.batteryData.Power.unit}
+                    {props.batteryData.Dc.Power + ' W'}
                   </TableCell>
                 </TableRow>
 
@@ -870,9 +863,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.BusCurrent.value +
-                      ' ' +
-                      props.batteryData.BusCurrent.unit}
+                    {props.batteryData.BusCurrent + ' A'}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -892,9 +883,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.CellsCurrent.value +
-                      ' ' +
-                      props.batteryData.CellsCurrent.unit}
+                    {props.batteryData.CellsCurrent + '  °C'}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -914,9 +903,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.HeatingCurrent.value +
-                      ' ' +
-                      props.batteryData.HeatingCurrent.unit}
+                    {props.batteryData.HeatingCurrent + ' A'}
                   </TableCell>
                 </TableRow>
 
@@ -937,9 +924,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.Soc.value +
-                      ' ' +
-                      props.batteryData.Soc.unit}
+                    {props.batteryData.Soc + ' %'}
                   </TableCell>
                 </TableRow>
               </TableBody>
@@ -999,9 +984,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                           paddingRight: '12px'
                         }}
                       >
-                        {props.batteryData.HeatingTemperature.value +
-                          ' ' +
-                          props.batteryData.HeatingTemperature.unit}
+                        {props.batteryData.Temperatures.Heating + ' °C'}
                       </TableCell>
                     </TableRow>
                     <TableRow>
@@ -1021,9 +1004,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                           paddingRight: '12px'
                         }}
                       >
-                        {props.batteryData.BoardTemperature.value +
-                          ' ' +
-                          props.batteryData.BoardTemperature.unit}
+                        {props.batteryData.Temperatures.Board + ' °C'}
                       </TableCell>
                     </TableRow>
                     <TableRow>
@@ -1043,9 +1024,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                           paddingRight: '12px'
                         }}
                       >
-                        {props.batteryData.AverageTemperature.value +
-                          ' ' +
-                          props.batteryData.AverageTemperature.unit}
+                        {props.batteryData.Temperatures.Cells.Average + ' °C'}
                       </TableCell>
                     </TableRow>
                     <TableRow>
@@ -1065,9 +1044,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                           paddingRight: '12px'
                         }}
                       >
-                        {props.batteryData.LeftCellsTemperature.value +
-                          ' ' +
-                          props.batteryData.LeftCellsTemperature.unit}
+                        {props.batteryData.Temperatures.Cells.Left + ' °C'}
                       </TableCell>
                     </TableRow>
                     <TableRow>
@@ -1087,9 +1064,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                           paddingRight: '12px'
                         }}
                       >
-                        {props.batteryData.RightCellsTemperature.value +
-                          ' ' +
-                          props.batteryData.RightCellsTemperature.unit}
+                        {props.batteryData.Temperatures.Cells.Right + ' °C'}
                       </TableCell>
                     </TableRow>
                     <TableRow>
@@ -1109,9 +1084,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                           paddingRight: '12px'
                         }}
                       >
-                        {props.batteryData.AverageTemperature.value +
-                          ' ' +
-                          props.batteryData.AverageTemperature.unit}
+                        {props.batteryData.Temperatures.Cells.Average + '  °C'}
                       </TableCell>
                     </TableRow>
                     <TableRow>
@@ -1131,9 +1104,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                           paddingRight: '12px'
                         }}
                       >
-                        {props.batteryData.StateTemperature.value +
-                          ' ' +
-                          props.batteryData.StateTemperature.unit}
+                        {props.batteryData.Temperatures.State + '  °C'}
                       </TableCell>
                     </TableRow>
                   </TableBody>
@@ -1193,9 +1164,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.ConnectedToDcBus.value +
-                      ' ' +
-                      props.batteryData.ConnectedToDcBus.unit}
+                    {props.batteryData.IoStatus.ConnectedToDcBus}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -1215,9 +1184,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.AlarmOutActive.value +
-                      ' ' +
-                      props.batteryData.AlarmOutActive.unit}
+                    {props.batteryData.IoStatus.AlarmOutActive}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -1237,9 +1204,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.InternalFanActive.value +
-                      ' ' +
-                      props.batteryData.InternalFanActive.unit}
+                    {props.batteryData.IoStatus.InternalFanActive}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -1259,9 +1224,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.VoltMeasurementAllowed.value +
-                      ' ' +
-                      props.batteryData.VoltMeasurementAllowed.unit}
+                    {props.batteryData.IoStatus.VoltMeasurementAllowed}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -1281,9 +1244,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.AuxRelayBus.value +
-                      ' ' +
-                      props.batteryData.AuxRelayBus.unit}
+                    {props.batteryData.IoStatus.AuxRelayBus}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -1303,9 +1264,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.RemoteStateActive.value +
-                      ' ' +
-                      props.batteryData.RemoteStateActive.unit}
+                    {props.batteryData.IoStatus.RemoteStateActive}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -1325,9 +1284,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.RiscActive.value +
-                      ' ' +
-                      props.batteryData.RiscActive.unit}
+                    {props.batteryData.IoStatus.RiscActive}
                   </TableCell>
                 </TableRow>
               </TableBody>
@@ -1385,9 +1342,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.Eoc.value +
-                      ' ' +
-                      props.batteryData.Eoc.unit}
+                    {props.batteryData.Eoc}
                   </TableCell>
                 </TableRow>
 
@@ -1408,9 +1363,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.SerialNumber.value +
-                      ' ' +
-                      props.batteryData.SerialNumber.unit}
+                    {props.batteryData.SerialNumber}
                   </TableCell>
                 </TableRow>
 
@@ -1431,9 +1384,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.FwVersion.value +
-                      ' ' +
-                      props.batteryData.FwVersion.unit}
+                    {props.batteryData.FwVersion}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -1453,9 +1404,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.TimeSinceTOC.value +
-                      ' ' +
-                      props.batteryData.TimeSinceTOC.unit}
+                    {props.batteryData.TimeSinceTOC}
                   </TableCell>
                 </TableRow>
                 {props.productNum === 0 && (
@@ -1476,9 +1425,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                         paddingRight: '12px'
                       }}
                     >
-                      {props.batteryData.CalibrationChargeRequested.value +
-                        ' ' +
-                        props.batteryData.CalibrationChargeRequested.unit}
+                      {props.batteryData.CalibrationChargeRequested}
                     </TableCell>
                   </TableRow>
                 )}
@@ -1499,9 +1446,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.MaxChargePower.value +
-                      ' ' +
-                      props.batteryData.MaxChargePower.unit}
+                    {props.batteryData.MaxChargePower + ' W'}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -1521,9 +1466,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.MaxDischargePower.value +
-                      ' ' +
-                      props.batteryData.MaxDischargePower.unit}
+                    {props.batteryData.MaxDischargePower + ' W'}
                   </TableCell>
                 </TableRow>
               </TableBody>
diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryViewSalidomo.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryViewSalidomo.tsx
index e428a83e9..e62529324 100644
--- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryViewSalidomo.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryViewSalidomo.tsx
@@ -51,7 +51,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
   const [errorMessage, setErrorMessage] = useState('');
 
   const [selectedVersion, setSelectedVersion] = useState(
-    props.batteryData.FwVersion.value
+    props.batteryData.FwVersion
   );
 
   const handleBatteryViewButton = () => {
@@ -67,35 +67,35 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
   };
 
   const [GreenisBlinking, setGreenisBlinking] = useState(
-    props.batteryData.Leds.Green.value === 'Blinking'
+    props.batteryData.Leds.Green === 'Blinking'
   );
 
   const [AmberisBlinking, setAmberisBlinking] = useState(
-    props.batteryData.Leds.Amber.value === 'Blinking'
+    props.batteryData.Leds.Amber === 'Blinking'
   );
   const [RedisBlinking, setRedisBlinking] = useState(
-    props.batteryData.Leds.Red.value === 'Blinking'
+    props.batteryData.Leds.Red === 'Blinking'
   );
 
   const [BlueisBlinking, setBlueisBlinking] = useState(
-    props.batteryData.Leds.Blue.value === 'Blinking'
+    props.batteryData.Leds.Blue === 'Blinking'
   );
 
   useEffect(() => {
     const intervalId = setInterval(() => {
-      if (props.batteryData.Leds.Amber.value === 'Blinking') {
+      if (props.batteryData.Leds.Amber === 'Blinking') {
         setAmberisBlinking((prevIsBlinking) => !prevIsBlinking);
       }
 
-      if (props.batteryData.Leds.Red.value === 'Blinking') {
+      if (props.batteryData.Leds.Red === 'Blinking') {
         setRedisBlinking((prevIsBlinking) => !prevIsBlinking);
       }
 
-      if (props.batteryData.Leds.Blue.value === 'Blinking') {
+      if (props.batteryData.Leds.Blue === 'Blinking') {
         setBlueisBlinking((prevIsBlinking) => !prevIsBlinking);
       }
 
-      if (props.batteryData.Leds.Green.value === 'Blinking') {
+      if (props.batteryData.Leds.Green === 'Blinking') {
         setGreenisBlinking((prevIsBlinking) => !prevIsBlinking);
       }
     }, 500); // Blink every 500 milliseconds
@@ -645,11 +645,8 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
               style={{
                 ...batteryStringStyle,
                 backgroundColor:
-                  props.batteryData.BatteryStrings.String1Active.value ==
-                    'True' ||
-                  Number(
-                    props.batteryData.BatteryStrings.String1Active.value
-                  ) == 0
+                  props.batteryData.BatteryStrings.String1Active == 'True' ||
+                  Number(props.batteryData.BatteryStrings.String1Active) == 0
                     ? '#32CD32'
                     : '#FF033E'
               }}
@@ -658,11 +655,8 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
               style={{
                 ...batteryStringStyle,
                 backgroundColor:
-                  props.batteryData.BatteryStrings.String2Active.value ==
-                    'True' ||
-                  Number(
-                    props.batteryData.BatteryStrings.String2Active.value
-                  ) == 0
+                  props.batteryData.BatteryStrings.String2Active == 'True' ||
+                  Number(props.batteryData.BatteryStrings.String2Active) == 0
                     ? '#32CD32'
                     : '#FF033E'
               }}
@@ -671,11 +665,8 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
               style={{
                 ...batteryStringStyle,
                 backgroundColor:
-                  props.batteryData.BatteryStrings.String3Active.value ==
-                    'True' ||
-                  Number(
-                    props.batteryData.BatteryStrings.String3Active.value
-                  ) == 0
+                  props.batteryData.BatteryStrings.String3Active == 'True' ||
+                  Number(props.batteryData.BatteryStrings.String3Active) == 0
                     ? '#32CD32'
                     : '#FF033E'
               }}
@@ -684,11 +675,8 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
               style={{
                 ...batteryStringStyle,
                 backgroundColor:
-                  props.batteryData.BatteryStrings.String4Active.value ==
-                    'True' ||
-                  Number(
-                    props.batteryData.BatteryStrings.String4Active.value
-                  ) == 0
+                  props.batteryData.BatteryStrings.String4Active == 'True' ||
+                  Number(props.batteryData.BatteryStrings.String4Active) == 0
                     ? '#32CD32'
                     : '#FF033E'
               }}
@@ -697,11 +685,8 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
               style={{
                 ...batteryStringStyle,
                 backgroundColor:
-                  props.batteryData.BatteryStrings.String5Active.value ==
-                    'True' ||
-                  Number(
-                    props.batteryData.BatteryStrings.String5Active.value
-                  ) == 0
+                  props.batteryData.BatteryStrings.String5Active == 'True' ||
+                  Number(props.batteryData.BatteryStrings.String5Active) == 0
                     ? '#32CD32'
                     : '#FF033E'
               }}
@@ -716,8 +701,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                   marginTop: '-10px',
                   borderRadius: '50%',
                   backgroundColor:
-                    props.batteryData.Leds.Green.value === 'On' ||
-                    GreenisBlinking
+                    props.batteryData.Leds.Green === 'On' || GreenisBlinking
                       ? 'green'
                       : 'transparent'
                 }}
@@ -731,8 +715,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                   marginTop: '10px',
                   borderRadius: '50%',
                   backgroundColor:
-                    props.batteryData.Leds.Amber.value === 'On' ||
-                    AmberisBlinking
+                    props.batteryData.Leds.Amber === 'On' || AmberisBlinking
                       ? 'orange'
                       : 'transparent'
                 }}
@@ -746,7 +729,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                   marginTop: '10px',
                   borderRadius: '50%',
                   backgroundColor:
-                    props.batteryData.Leds.Blue.value === 'On' || BlueisBlinking
+                    props.batteryData.Leds.Blue === 'On' || BlueisBlinking
                       ? '#00ccff'
                       : 'transparent'
                 }}
@@ -760,7 +743,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                   marginTop: '10px',
                   borderRadius: '50%',
                   backgroundColor:
-                    props.batteryData.Leds.Red.value === 'On' || RedisBlinking
+                    props.batteryData.Leds.Red === 'On' || RedisBlinking
                       ? 'red'
                       : 'transparent'
                 }}
@@ -820,7 +803,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.Dc.Voltage.value + ' V'}
+                    {props.batteryData.Dc.Voltage + ' V'}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -840,7 +823,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.Dc.Current.value + ' A'}
+                    {props.batteryData.Dc.Current + ' A'}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -860,7 +843,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.Dc.Power.value + ' W'}
+                    {props.batteryData.Dc.Power + ' W'}
                   </TableCell>
                 </TableRow>
 
@@ -881,7 +864,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.BusCurrent.value + ' A'}
+                    {props.batteryData.BusCurrent + ' A'}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -901,7 +884,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.CellsCurrent.value + ' A'}
+                    {props.batteryData.CellsCurrent + ' A'}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -921,7 +904,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.HeatingCurrent.value + ' A'}
+                    {props.batteryData.HeatingCurrent + ' A'}
                   </TableCell>
                 </TableRow>
 
@@ -942,7 +925,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.Soc.value + ' %'}
+                    {props.batteryData.Soc + ' %'}
                   </TableCell>
                 </TableRow>
               </TableBody>
@@ -1000,7 +983,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.IoStatus.ConnectedToDcBus.value
+                    {props.batteryData.IoStatus.ConnectedToDcBus
                       ? 'True'
                       : 'False'}
                   </TableCell>
@@ -1022,7 +1005,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.IoStatus.AlarmOutActive.value
+                    {props.batteryData.IoStatus.AlarmOutActive
                       ? 'True'
                       : 'False'}
                   </TableCell>
@@ -1044,7 +1027,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.IoStatus.InternalFanActive.value
+                    {props.batteryData.IoStatus.InternalFanActive
                       ? 'True'
                       : 'False'}
                   </TableCell>
@@ -1066,7 +1049,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.IoStatus.VoltMeasurementAllowed.value
+                    {props.batteryData.IoStatus.VoltMeasurementAllowed
                       ? 'True'
                       : 'False'}
                   </TableCell>
@@ -1088,9 +1071,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.IoStatus.AuxRelayBus.value
-                      ? 'True'
-                      : 'False'}
+                    {props.batteryData.IoStatus.AuxRelayBus ? 'True' : 'False'}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -1110,7 +1091,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.IoStatus.RemoteStateActive.value
+                    {props.batteryData.IoStatus.RemoteStateActive
                       ? 'True'
                       : 'False'}
                   </TableCell>
@@ -1132,9 +1113,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.IoStatus.RiscActive.value
-                      ? 'True'
-                      : 'False'}
+                    {props.batteryData.IoStatus.RiscActive ? 'True' : 'False'}
                   </TableCell>
                 </TableRow>
               </TableBody>
@@ -1192,7 +1171,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.Eoc.value}
+                    {props.batteryData.Eoc}
                   </TableCell>
                 </TableRow>
 
@@ -1213,7 +1192,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.SerialNumber.value}
+                    {props.batteryData.SerialNumber}
                   </TableCell>
                 </TableRow>
 
@@ -1234,7 +1213,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.FwVersion.value}
+                    {props.batteryData.FwVersion}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -1254,7 +1233,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.TimeSinceTOC.value}
+                    {props.batteryData.TimeSinceTOC}
                   </TableCell>
                 </TableRow>
 
@@ -1275,7 +1254,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.MaxChargePower.value + ' W'}
+                    {props.batteryData.MaxChargePower + ' W'}
                   </TableCell>
                 </TableRow>
                 <TableRow>
@@ -1295,7 +1274,7 @@ function DetailedBatteryViewSalidomo(props: DetailedBatteryViewProps) {
                       paddingRight: '12px'
                     }}
                   >
-                    {props.batteryData.MaxDischargePower.value + ' W'}
+                    {props.batteryData.MaxDischargePower + ' W'}
                   </TableCell>
                 </TableRow>
               </TableBody>
diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx
index d771d967c..75c60f8fa 100644
--- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx
@@ -16,7 +16,7 @@ import { getChartOptions } from '../Overview/chartOptions';
 import {
   BatteryDataInterface,
   BatteryOverviewInterface,
-  transformInputToBatteryViewData
+  transformInputToBatteryViewDataJson
 } from '../../../interfaces/Chart';
 import dayjs, { Dayjs } from 'dayjs';
 import { TimeSpan, UnixTime } from '../../../dataCache/time';
@@ -96,7 +96,7 @@ function MainStats(props: MainStatsProps) {
     const resultPromise: Promise<{
       chartData: BatteryDataInterface;
       chartOverview: BatteryOverviewInterface;
-    }> = transformInputToBatteryViewData(
+    }> = transformInputToBatteryViewDataJson(
       props.s3Credentials,
       props.id,
       product,
@@ -192,7 +192,7 @@ function MainStats(props: MainStatsProps) {
     const resultPromise: Promise<{
       chartData: BatteryDataInterface;
       chartOverview: BatteryOverviewInterface;
-    }> = transformInputToBatteryViewData(
+    }> = transformInputToBatteryViewDataJson(
       props.s3Credentials,
       props.id,
       product,
@@ -254,7 +254,7 @@ function MainStats(props: MainStatsProps) {
     const resultPromise: Promise<{
       chartData: BatteryDataInterface;
       chartOverview: BatteryOverviewInterface;
-    }> = transformInputToBatteryViewData(
+    }> = transformInputToBatteryViewDataJson(
       props.s3Credentials,
       props.id,
       product,
diff --git a/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx b/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx
index 5e1a5e5f6..829ba84a8 100644
--- a/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx
@@ -1,4 +1,4 @@
-import { ConfigurationValues, TopologyValues } from '../Log/graph.util';
+import { ConfigurationValues, JSONRecordData } from '../Log/graph.util';
 import {
   Alert,
   Box,
@@ -31,7 +31,7 @@ import { DateTimePicker } from '@mui/x-date-pickers/DateTimePicker';
 import { TimePicker } from '@mui/lab';
 
 interface ConfigurationProps {
-  values: TopologyValues;
+  values: JSONRecordData;
   id: number;
 }
 
@@ -40,6 +40,8 @@ function Configuration(props: ConfigurationProps) {
     return null;
   }
 
+  console.log('111111111111111111111111111111111111111111111111');
+
   const CalibrationChargeOptions = [
     'Repetitive Calibration',
     'Additional Calibration',
@@ -86,19 +88,19 @@ function Configuration(props: ConfigurationProps) {
   const { currentUser, setUser } = context;
 
   const [formValues, setFormValues] = useState<ConfigurationValues>({
-    minimumSoC: props.values.minimumSoC[0].value,
-    gridSetPoint: (props.values.gridSetPoint[0].value as number) / 1000,
+    minimumSoC: props.values.Config.MinSoc,
+    gridSetPoint: (props.values.Config.GridSetPoint as number) / 1000,
     CalibrationChargeState: CalibrationChargeOptionsController.indexOf(
-      props.values.calibrationChargeState[0].value.toString()
+      props.values.Config.ForceCalibrationChargeState.toString()
     ),
     calibrationChargeDate:
       CalibrationChargeOptionsController.indexOf(
-        props.values.calibrationChargeState[0].value.toString()
+        props.values.Config.ForceCalibrationChargeState.toString()
       ) == 0
-        ? dayjs(props.values.repetitiveCalibrationChargeDate[0].value)
+        ? dayjs(props.values.Config.DayAndTimeForRepetitiveCalibration)
             // .add(localOffset, 'minute')
             .toDate()
-        : dayjs(props.values.additionalCalibrationChargeDate[0].value)
+        : dayjs(props.values.Config.DayAndTimeForAdditionalCalibration)
             // .add(localOffset, 'minute')
             .toDate()
   });
@@ -106,9 +108,9 @@ function Configuration(props: ConfigurationProps) {
   const handleSubmit = async (e) => {
     if (
       CalibrationChargeOptionsController.indexOf(
-        props.values.calibrationChargeState[0].value.toString()
+        props.values.Config.ForceCalibrationChargeState.toString()
       ) != 2 &&
-      props.values.mode[0].value === 'CalibrationCharge'
+      props.values.EssControl.Mode === 'CalibrationCharge'
     ) {
       setDateSelectionError(
         'You cannot change the date while the installation is in Calibration Charge Mode'
@@ -192,10 +194,10 @@ function Configuration(props: ConfigurationProps) {
       ),
       ['calibrationChargeDate']:
         CalibrationChargeOptions.indexOf(event.target.value) == 0
-          ? dayjs(props.values.repetitiveCalibrationChargeDate[0].value)
+          ? dayjs(props.values.Config.DayAndTimeForRepetitiveCalibration)
               // .add(localOffset, 'minute')
               .toDate()
-          : dayjs(props.values.additionalCalibrationChargeDate[0].value)
+          : dayjs(props.values.Config.DayAndTimeForAdditionalCalibration)
               // .add(localOffset, 'minute')
               .toDate()
     });
@@ -466,13 +468,14 @@ function Configuration(props: ConfigurationProps) {
                     />
                   }
                   value={
-                    (props.values.installedDcDcPower[0].value as number) * 10
+                    (props.values.DcDc.SystemControl
+                      .NumberOfConnectedSlaves as number) * 10
                   }
                   fullWidth
                 />
               </div>
 
-              {props.values.maximumDischargePower && (
+              {props.values.Config.MaxBatteryDischargingCurrent && (
                 <div style={{ marginBottom: '5px' }}>
                   <TextField
                     label={
@@ -482,26 +485,28 @@ function Configuration(props: ConfigurationProps) {
                       />
                     }
                     value={
-                      (props.values.maximumDischargePower[0].value as number) *
+                      (props.values.Config
+                        .MaxBatteryDischargingCurrent as number) *
                       48 *
-                      (props.values.DcDcNum[0].value as number)
+                      (props.values.DcDc.SystemControl
+                        .NumberOfConnectedSlaves as number)
                     }
                     fullWidth
                   />
                 </div>
               )}
-              <div>
-                <TextField
-                  label={
-                    <FormattedMessage
-                      id="Number_of_Batteries"
-                      defaultMessage="Number of Batteries"
-                    />
-                  }
-                  value={props.values.battery.length - 4}
-                  fullWidth
-                />
-              </div>
+              {/*<div>*/}
+              {/*  <TextField*/}
+              {/*    label={*/}
+              {/*      <FormattedMessage*/}
+              {/*        id="Number_of_Batteries"*/}
+              {/*        defaultMessage="Number of Batteries"*/}
+              {/*      />*/}
+              {/*    }*/}
+              {/*    value={Object.keys(props.values.Battery.Devices).length}*/}
+              {/*    fullWidth*/}
+              {/*  />*/}
+              {/*</div>*/}
 
               <div
                 style={{
diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx
index b760cf5be..0f0eff4e0 100644
--- a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx
@@ -13,15 +13,10 @@ import Access from '../ManageAccess/Access';
 import Log from 'src/content/dashboards/Log/Log';
 import { TimeSpan, UnixTime } from 'src/dataCache/time';
 import { FetchResult } from 'src/dataCache/dataCache';
-import {
-  extractValues,
-  TopologyValues
-} from 'src/content/dashboards/Log/graph.util';
-import Topology from '../Topology/Topology';
+import { JSONRecordData } from 'src/content/dashboards/Log/graph.util';
 import { FormattedMessage } from 'react-intl';
 import Overview from '../Overview/overview';
-import Configuration from '../Configuration/Configuration';
-import { fetchData } from 'src/content/dashboards/Installations/fetchData';
+import { fetchDataJson } from 'src/content/dashboards/Installations/fetchData';
 import CancelIcon from '@mui/icons-material/Cancel';
 import BuildIcon from '@mui/icons-material/Build';
 import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
@@ -29,8 +24,10 @@ import routes from '../../../Resources/routes.json';
 import Information from '../Information/Information';
 import { UserType } from '../../../interfaces/UserTypes';
 import HistoryOfActions from '../History/History';
-import PvView from '../PvView/PvView';
+import Topology from '../Topology/Topology';
 import BatteryView from '../BatteryView/BatteryView';
+import Configuration from '../Configuration/Configuration';
+import PvView from '../PvView/PvView';
 
 interface singleInstallationProps {
   current_installation?: I_Installation;
@@ -47,7 +44,7 @@ function Installation(props: singleInstallationProps) {
   const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
   const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
   const [currentTab, setCurrentTab] = useState<string>(undefined);
-  const [values, setValues] = useState<TopologyValues | null>(null);
+  const [values, setValues] = useState<JSONRecordData | null>(null);
   const status = props.current_installation.status;
   const [connected, setConnected] = useState(true);
   const [loading, setLoading] = useState(true);
@@ -82,7 +79,7 @@ function Installation(props: singleInstallationProps) {
     for (var i = timeperiodToSearch; i > 0; i -= 2) {
       timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
       try {
-        res = await fetchData(timestampToFetch, s3Credentials);
+        res = await fetchDataJson(timestampToFetch, s3Credentials, false);
         if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
           break;
         }
@@ -101,18 +98,18 @@ function Installation(props: singleInstallationProps) {
     setLoading(false);
 
     const timestamp = Object.keys(res)[Object.keys(res).length - 1];
-
-    setValues(
-      extractValues({
-        time: UnixTime.fromTicks(parseInt(timestamp, 10)),
-        value: res[timestamp]
-      })
-    );
+    setValues(res[timestamp]);
+    // setValues(
+    //   extractValues({
+    //     time: UnixTime.fromTicks(parseInt(timestamp, 10)),
+    //     value: res[timestamp]
+    //   })
+    // );
     return true;
   };
 
   const fetchDataPeriodically = async () => {
-    var timeperiodToSearch = 90;
+    var timeperiodToSearch = 200;
     let res;
     let timestampToFetch;
 
@@ -123,7 +120,7 @@ function Installation(props: singleInstallationProps) {
       timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
 
       try {
-        res = await fetchData(timestampToFetch, s3Credentials);
+        res = await fetchDataJson(timestampToFetch, s3Credentials, false);
         if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
           break;
         }
@@ -150,13 +147,14 @@ function Installation(props: singleInstallationProps) {
         console.log(`Timestamp: ${timestamp}`);
         console.log(res[timestamp]);
 
+        setValues(res[timestamp]);
         // Set values asynchronously with delay
-        setValues(
-          extractValues({
-            time: UnixTime.fromTicks(parseInt(timestamp, 10)),
-            value: res[timestamp]
-          })
-        );
+        // setValues(
+        //   extractValues({
+        //     time: UnixTime.fromTicks(parseInt(timestamp, 10)),
+        //     value: res[timestamp]
+        //   })
+        // );
         // Wait for 2 seconds before processing next timestamp
         await timeout(2000);
       }
@@ -164,14 +162,14 @@ function Installation(props: singleInstallationProps) {
       timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(60));
       console.log('NEW TIMESTAMP TO FETCH IS ' + timestampToFetch);
 
-      for (i = 0; i < 10; i++) {
+      for (i = 0; i < 30; i++) {
         if (!continueFetching.current) {
           return false;
         }
 
         try {
           console.log('Trying to fetch timestamp ' + timestampToFetch);
-          res = await fetchData(timestampToFetch, s3Credentials);
+          res = await fetchDataJson(timestampToFetch, s3Credentials, false);
           if (
             res !== FetchResult.notAvailable &&
             res !== FetchResult.tryLater
@@ -293,7 +291,7 @@ function Installation(props: singleInstallationProps) {
                 fontSize: '14px'
               }}
             >
-              {values.mode[0].value}
+              {values.EssControl.Mode}
             </Typography>
           </div>
         )}
diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx
index 6d14cb5d4..add63dcf9 100644
--- a/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx
@@ -1,14 +1,8 @@
 import { UnixTime } from 'src/dataCache/time';
 import { I_S3Credentials } from 'src/interfaces/S3Types';
 import { FetchResult } from 'src/dataCache/dataCache';
-import { DataRecord } from 'src/dataCache/data';
 import { S3Access } from 'src/dataCache/S3/S3Access';
-import {
-  JSONRecordData,
-  parseChunk,
-  parseChunkJson,
-  parseCsv
-} from '../Log/graph.util';
+import { JSONRecordData, parseChunkJson } from '../Log/graph.util';
 import JSZip from 'jszip';
 
 export const fetchDataJson = (
@@ -34,7 +28,6 @@ export const fetchDataJson = (
         if (r.status === 404) {
           return Promise.resolve(FetchResult.notAvailable);
         } else if (r.status === 200) {
-          console.log('FOUND ITTTTTTTTTTTT');
           const jsontext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text
           const byteArray = Uint8Array.from(atob(jsontext), (c) =>
             c.charCodeAt(0)
@@ -44,7 +37,6 @@ export const fetchDataJson = (
           const zip = await JSZip.loadAsync(byteArray);
           // Assuming the Json file is named "data.json" inside the ZIP archive
           const jsonContent = await zip.file('data.json').async('text');
-          //console.log(jsonContent);
           return parseChunkJson(jsonContent);
         } else {
           return Promise.resolve(FetchResult.notAvailable);
@@ -111,104 +103,104 @@ export const fetchAggregatedDataJson = (
 //--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 //--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 //--------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
-export const fetchData = (
-  timestamp: UnixTime,
-  s3Credentials?: I_S3Credentials,
-  cutdigits?: boolean
-): Promise<FetchResult<Record<string, DataRecord>>> => {
-  const s3Path = cutdigits
-    ? `${timestamp.ticks.toString().slice(0, -2)}.csv`
-    : `${timestamp.ticks}.csv`;
-  if (s3Credentials && s3Credentials.s3Bucket) {
-    const s3Access = new S3Access(
-      s3Credentials.s3Bucket,
-      s3Credentials.s3Region,
-      s3Credentials.s3Provider,
-      s3Credentials.s3Key,
-      s3Credentials.s3Secret
-    );
-
-    return s3Access
-      .get(s3Path)
-      .then(async (r) => {
-        if (r.status === 404) {
-          return Promise.resolve(FetchResult.notAvailable);
-        } else if (r.status === 200) {
-          console.log('FOUND ITTTTTTTTTTTT');
-          const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text
-          const contentEncoding = r.headers.get('content-type');
-
-          //console.log(contentEncoding);
-
-          if (contentEncoding != 'application/base64; charset=utf-8') {
-            // console.log('uncompressed');
-            return parseChunk(csvtext);
-          }
-
-          const byteArray = Uint8Array.from(atob(csvtext), (c) =>
-            c.charCodeAt(0)
-          );
-
-          //Decompress the byte array using JSZip
-          const zip = await JSZip.loadAsync(byteArray);
-          // Assuming the CSV file is named "data.csv" inside the ZIP archive
-          const csvContent = await zip.file('data.csv').async('text');
-
-          //console.log(csvContent);
-
-          return parseChunk(csvContent);
-        } else {
-          return Promise.resolve(FetchResult.notAvailable);
-        }
-      })
-      .catch((e) => {
-        return Promise.resolve(FetchResult.tryLater);
-      });
-  }
-};
-
-export const fetchAggregatedData = (
-  date: string,
-  s3Credentials?: I_S3Credentials
-): Promise<FetchResult<DataRecord>> => {
-  const s3Path = `${date}.csv`;
-  if (s3Credentials && s3Credentials.s3Bucket) {
-    const s3Access = new S3Access(
-      s3Credentials.s3Bucket,
-      s3Credentials.s3Region,
-      s3Credentials.s3Provider,
-      s3Credentials.s3Key,
-      s3Credentials.s3Secret
-    );
-    return s3Access
-      .get(s3Path)
-      .then(async (r) => {
-        if (r.status === 404) {
-          return Promise.resolve(FetchResult.notAvailable);
-        } else if (r.status === 200) {
-          const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text
-          const contentEncoding = r.headers.get('content-type');
-
-          if (contentEncoding != 'application/base64; charset=utf-8') {
-            return parseCsv(csvtext);
-          }
-
-          const byteArray = Uint8Array.from(atob(csvtext), (c) =>
-            c.charCodeAt(0)
-          );
-
-          //Decompress the byte array using JSZip
-          const zip = await JSZip.loadAsync(byteArray);
-          // Assuming the CSV file is named "data.csv" inside the ZIP archive
-          const csvContent = await zip.file('data.csv').async('text');
-          return parseCsv(csvContent);
-        } else {
-          return Promise.resolve(FetchResult.notAvailable);
-        }
-      })
-      .catch((e) => {
-        return Promise.resolve(FetchResult.tryLater);
-      });
-  }
-};
+//
+// export const fetchData = (
+//   timestamp: UnixTime,
+//   s3Credentials?: I_S3Credentials,
+//   cutdigits?: boolean
+// ): Promise<FetchResult<Record<string, DataRecord>>> => {
+//   const s3Path = cutdigits
+//     ? `${timestamp.ticks.toString().slice(0, -2)}.csv`
+//     : `${timestamp.ticks}.csv`;
+//   if (s3Credentials && s3Credentials.s3Bucket) {
+//     const s3Access = new S3Access(
+//       s3Credentials.s3Bucket,
+//       s3Credentials.s3Region,
+//       s3Credentials.s3Provider,
+//       s3Credentials.s3Key,
+//       s3Credentials.s3Secret
+//     );
+//
+//     return s3Access
+//       .get(s3Path)
+//       .then(async (r) => {
+//         if (r.status === 404) {
+//           return Promise.resolve(FetchResult.notAvailable);
+//         } else if (r.status === 200) {
+//           //console.log('FOUND ITTTTTTTTTTTT');
+//           const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text
+//           const contentEncoding = r.headers.get('content-type');
+//
+//           //console.log(contentEncoding);
+//
+//           if (contentEncoding != 'application/base64; charset=utf-8') {
+//             // console.log('uncompressed');
+//             return parseChunk(csvtext);
+//           }
+//
+//           const byteArray = Uint8Array.from(atob(csvtext), (c) =>
+//             c.charCodeAt(0)
+//           );
+//
+//           //Decompress the byte array using JSZip
+//           const zip = await JSZip.loadAsync(byteArray);
+//           // Assuming the CSV file is named "data.csv" inside the ZIP archive
+//           const csvContent = await zip.file('data.csv').async('text');
+//
+//           //console.log(csvContent);
+//
+//           return parseChunk(csvContent);
+//         } else {
+//           return Promise.resolve(FetchResult.notAvailable);
+//         }
+//       })
+//       .catch((e) => {
+//         return Promise.resolve(FetchResult.tryLater);
+//       });
+//   }
+// };
+//
+// export const fetchAggregatedData = (
+//   date: string,
+//   s3Credentials?: I_S3Credentials
+// ): Promise<FetchResult<DataRecord>> => {
+//   const s3Path = `${date}.csv`;
+//   if (s3Credentials && s3Credentials.s3Bucket) {
+//     const s3Access = new S3Access(
+//       s3Credentials.s3Bucket,
+//       s3Credentials.s3Region,
+//       s3Credentials.s3Provider,
+//       s3Credentials.s3Key,
+//       s3Credentials.s3Secret
+//     );
+//     return s3Access
+//       .get(s3Path)
+//       .then(async (r) => {
+//         if (r.status === 404) {
+//           return Promise.resolve(FetchResult.notAvailable);
+//         } else if (r.status === 200) {
+//           const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text
+//           const contentEncoding = r.headers.get('content-type');
+//
+//           if (contentEncoding != 'application/base64; charset=utf-8') {
+//             return parseCsv(csvtext);
+//           }
+//
+//           const byteArray = Uint8Array.from(atob(csvtext), (c) =>
+//             c.charCodeAt(0)
+//           );
+//
+//           //Decompress the byte array using JSZip
+//           const zip = await JSZip.loadAsync(byteArray);
+//           // Assuming the CSV file is named "data.csv" inside the ZIP archive
+//           const csvContent = await zip.file('data.csv').async('text');
+//           return parseCsv(csvContent);
+//         } else {
+//           return Promise.resolve(FetchResult.notAvailable);
+//         }
+//       })
+//       .catch((e) => {
+//         return Promise.resolve(FetchResult.tryLater);
+//       });
+//   }
+// };
diff --git a/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx b/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx
index ba6fe98eb..78333ef2e 100644
--- a/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx
@@ -1,61 +1,87 @@
 // The interface for each device in the Battery object
+
 interface Leds {
-  Blue: { value: string };
-  Amber: { value: string };
-  Green: { value: string };
-  Red: { value: string };
+  Blue: string;
+  Amber: string;
+  Green: string;
+  Red: string;
 }
 
 interface Dc {
-  Current: { value: number };
-  Voltage: { value: number };
-  Power: { value: number };
+  Current: number;
+  Voltage: number;
+  Power: number;
 }
 
 interface Temperatures {
   Cells: {
-    Average: { value: number };
+    Average: number;
+    Right: number;
+    Left: number;
   };
+  State: number;
+  Board: number;
+  Heating: number;
 }
 
 interface IoStatus {
-  ConnectedToDcBus: { value: boolean };
-  AuxRelayBus: { value: boolean };
-  AlarmOutActive: { value: boolean };
-  InternalFanActive: { value: boolean };
-  RemoteStateActive: { value: boolean };
-  VoltMeasurementAllowed: { value: boolean };
-  RiscActive: { value: boolean };
+  ConnectedToDcBus: boolean;
+  AuxRelayBus: boolean;
+  AlarmOutActive: boolean;
+  InternalFanActive: boolean;
+  RemoteStateActive: boolean;
+  VoltMeasurementAllowed: boolean;
+  RiscActive: boolean;
 }
 
 interface BatteryStrings {
-  String1Active: { value: string };
-  String2Active: { value: string };
-  String3Active: { value: string };
-  String4Active: { value: string };
-  String5Active: { value: string };
+  String1Active: string;
+  String2Active: string;
+  String3Active: string;
+  String4Active: string;
+  String5Active: string;
 }
 
 export interface Device {
   Leds: Leds;
-  Eoc: { value: boolean };
-  Soc: { value: number };
-  SerialNumber: { value: string };
-  TimeSinceTOC: { value: string };
-  MaxChargePower: { value: number };
-  CellsCurrent: { value: number };
-  SOCAh: { value: number };
+  Eoc: boolean;
+  Soc: number;
+  SerialNumber: string;
+  TimeSinceTOC: string;
+  MaxChargePower: number;
+  CellsCurrent: number;
+  SOCAh: number;
   Dc: Dc;
-  FwVersion: { value: string };
-  HeatingCurrent: { value: number };
-  MaxDischargePower: { value: number };
+  FwVersion: string;
+  HeatingCurrent: number;
+  MaxDischargePower: number;
   Temperatures: Temperatures;
-  BusCurrent: { value: number };
-  HeatingPower: { value: number };
+  BusCurrent: number;
+  HeatingPower: number;
   IoStatus: IoStatus;
   BatteryStrings: BatteryStrings;
-  Alarms: number;
-  Warnings: number;
+  CalibrationChargeRequested: string;
+  Alarms: string;
+  Warnings: string;
+}
+
+export interface Power {
+  Active: number;
+  Apparent: number;
+  Reactive: number;
+}
+
+export interface PvString {
+  Power: number;
+  Voltage: number;
+  Current: number;
+}
+
+export interface Line {
+  Voltage: number;
+  Current: number;
+  Phi: number;
+  Power: Power;
 }
 
 // The interface for the Battery structure, with dynamic keys (Device IDs)
@@ -64,14 +90,182 @@ export interface JSONRecordData {
     Devices: {
       [deviceId: string]: Device; // Device ID as the key
     };
+
+    Dc: {
+      Voltage: number;
+      Current: number;
+      Power: number;
+    };
+
+    // Power: number;
+    // Current: number;
+    CurrentMinSoc: number;
+    Soc: number;
+    Soh: number;
+    Temperature: number;
+    // Voltage: number;
   };
-  Config: {
-    Devices: {
-      BatteryNodes: {
-        value: string[];
+
+  AcDc: {
+    Ac: {
+      Frequency: number;
+      L1: Line;
+      L2: Line;
+      L3: Line;
+      Power: Power;
+    };
+
+    Dc: {
+      Voltage: number;
+      Current: number;
+      Power: number;
+    };
+
+    Alarms: string[];
+    Warnings: string[];
+  };
+
+  AcDcToDcLink: { Power: number };
+  PvOnAcGrid: { Power: Power };
+  PvOnAcIsland: { Power: Power };
+  AcGridToAcIsland: { Power: Power };
+
+  islandBus: {
+    AcDc: {
+      Ac: {
+        L1: Line;
+        L2: Line;
+        L3: Line;
       };
     };
   };
+
+  Config: {
+    BatterySelfDischargePower: number;
+    CurtailP: number;
+    DayAndTimeForAdditionalCalibration: string;
+    DayAndTimeForRepetitiveCalibration: string;
+    DisplayIndividualBatteries: string;
+    MaxBatteryDischargingCurrent: number;
+    ForceCalibrationChargeState: string;
+    GridSetPoint: number;
+    HoldSocZone: number;
+    MinSoc: number;
+    PConstant: number;
+    PvInstalledPower: number;
+
+    Devices: {
+      BatteryNodes: string;
+      BatteryIp: { DeviceState: string };
+      GridMeterIp: { DeviceState: string };
+      IslandBusLoadMeterIp: { DeviceState: string };
+      LoadOnAcGrid: { DeviceState: string };
+      LoadOnDc: { DeviceState: string };
+      PvOnAcGrid: { DeviceState: string };
+      PvOnAcIsland: { DeviceState: string };
+      PvOnDc: { DeviceState: string };
+      RelaysIp: { DeviceState: string };
+      TruConvertAcIp: { DeviceState: string };
+      TruConvertDcIp: { DeviceState: string };
+      TsRelaysIp: { DeviceState: string };
+    };
+  };
+
+  DcDc: {
+    Dc: {
+      Battery: {
+        Voltage: number;
+        Current: number;
+        Power: number;
+      };
+      Link: {
+        Voltage: number;
+        Current: number;
+        Power: number;
+      };
+    };
+
+    SystemControl: {
+      Alarms: string;
+      CommunicationTimeout: string;
+      DeviceState: string;
+      NumberOfConnectedSlaves: number;
+      NumberOfConnectedSubSlaves: number;
+      PowerSetPointActivation: string;
+      PowerSetPointTrigger: string;
+      ReferenceFrame: string;
+      ResetAlarmsAndWarnings: string;
+      SlaveErrorHandling: string;
+      SubSlaveErrorHandling: string;
+      SystemConfig: string;
+      TargetSlave: number;
+      UseSlaveIdForAddressing: string;
+      Warnings: string;
+    };
+
+    Alarms: string[];
+    Warnings: string[];
+  };
+
+  EssControl: {
+    Mode: string;
+    LimitedBy: string;
+    PowerCorrection: number;
+    PowerSetPoint: number;
+  };
+
+  GridMeter: {
+    Ac: {
+      Frequency: number;
+      L1: Line;
+      L2: Line;
+      L3: Line;
+      Power: Power;
+    };
+    ActivePowerExportT1: number;
+    ActivePowerExportT2: number;
+    ActivePowerExportT3: number;
+    ActivePowerExportT4: number;
+    ActivePowerImportT1: number;
+    ActivePowerImportT2: number;
+    ActivePowerImportT3: number;
+    ActivePowerImportT4: number;
+  };
+
+  LoadOnAcGrid: { Power: Power };
+
+  LoadOnAcIsland: {
+    Ac: {
+      Frequency: number;
+      L1: Line;
+      L2: Line;
+      L3: Line;
+      Power: Power;
+    };
+    ActivePowerExportT1: number;
+    ActivePowerExportT2: number;
+    ActivePowerExportT3: number;
+    ActivePowerExportT4: number;
+    ActivePowerImportT1: number;
+    ActivePowerImportT2: number;
+    ActivePowerImportT3: number;
+    ActivePowerImportT4: number;
+  };
+
+  LoadOnDc: { Power: number };
+
+  PvOnDc: {
+    DcWh: number;
+    NbrOfStrings: number;
+    Dc: {
+      Voltage: number;
+      Current: number;
+      Power: number;
+    };
+    Strings: {
+      [PvId: string]: PvString;
+    };
+  };
 }
 
 export const parseChunkJson = (
@@ -83,8 +277,6 @@ export const parseChunkJson = (
   let currentTimestamp = null;
 
   lines.forEach((line) => {
-    //console.log(line);
-
     const fields = line.split(';');
     if (fields[0] === 'Timestamp') {
       currentTimestamp = fields[1];
@@ -106,61 +298,60 @@ export const parseChunkJson = (
 //--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 //--------------------------------------------------------------------------------------------------------------------------------------------------------------------
 //--------------------------------------------------------------------------------------------------------------------------------------------------------------------
-
-import { DataPoint, DataRecord } from 'src/dataCache/data';
-
+//
 export interface I_CsvEntry {
   value: string | number;
   unit: string;
 }
 
-export const parseCsv = (text: string): DataRecord => {
-  const y = text
-    .split(/\r?\n/)
-    .filter((split) => split.length > 0)
-    .map((l) => {
-      return l.split(';');
-    });
-
-  return y
-    .map((fields) => {
-      if (isNaN(Number(fields[1])) || fields[1] === '') {
-        return { [fields[0]]: { value: fields[1], unit: fields[2] } };
-      }
-      return { [fields[0]]: { value: parseFloat(fields[1]), unit: fields[2] } };
-    })
-    .reduce((acc, current) => ({ ...acc, ...current }), {} as DataRecord);
-};
-
-export const parseChunk = (text: string): Record<string, DataRecord> => {
-  const lines = text.split(/\r?\n/).filter((line) => line.length > 0);
-
-  let result: Record<string, DataRecord> = {};
-  let currentTimestamp = null;
-
-  lines.forEach((line) => {
-    const fields = line.split(';');
-    if (fields[0] === 'Timestamp') {
-      currentTimestamp = fields[1];
-      result[currentTimestamp] = {};
-    } else if (currentTimestamp) {
-      let key = fields[0];
-      let value = fields[1];
-      let unit = fields[2];
-
-      if (isNaN(Number(value)) || value === '') {
-        result[currentTimestamp][key] = { value: value, unit: unit };
-      } else {
-        result[currentTimestamp][key] = {
-          value: parseFloat(value),
-          unit: unit
-        };
-      }
-    }
-  });
-  return result;
-};
-
+//
+// export const parseCsv = (text: string): DataRecord => {
+//   const y = text
+//     .split(/\r?\n/)
+//     .filter((split) => split.length > 0)
+//     .map((l) => {
+//       return l.split(';');
+//     });
+//
+//   return y
+//     .map((fields) => {
+//       if (isNaN(Number(fields[1])) || fields[1] === '') {
+//         return { [fields[0]]: { value: fields[1], unit: fields[2] } };
+//       }
+//       return { [fields[0]]: { value: parseFloat(fields[1]), unit: fields[2] } };
+//     })
+//     .reduce((acc, current) => ({ ...acc, ...current }), {} as DataRecord);
+// };
+//
+// export const parseChunk = (text: string): Record<string, DataRecord> => {
+//   const lines = text.split(/\r?\n/).filter((line) => line.length > 0);
+//
+//   let result: Record<string, DataRecord> = {};
+//   let currentTimestamp = null;
+//
+//   lines.forEach((line) => {
+//     const fields = line.split(';');
+//     if (fields[0] === 'Timestamp') {
+//       currentTimestamp = fields[1];
+//       result[currentTimestamp] = {};
+//     } else if (currentTimestamp) {
+//       let key = fields[0];
+//       let value = fields[1];
+//       let unit = fields[2];
+//
+//       if (isNaN(Number(value)) || value === '') {
+//         result[currentTimestamp][key] = { value: value, unit: unit };
+//       } else {
+//         result[currentTimestamp][key] = {
+//           value: parseFloat(value),
+//           unit: unit
+//         };
+//       }
+//     }
+//   });
+//   return result;
+// };
+//
 export interface I_BoxDataValue {
   unit: string;
   value: string | number;
@@ -172,445 +363,454 @@ export type ConfigurationValues = {
   CalibrationChargeState: number;
   calibrationChargeDate: Date | null;
 };
+//
+// export interface Pv {
+//   PvId: number;
+//   Power: I_BoxDataValue;
+//   Voltage: I_BoxDataValue;
+//   Current: I_BoxDataValue;
+// }
+//
+// export interface Battery {
+//   BatteryId: number;
+//   FwVersion: I_BoxDataValue;
+//   Power: I_BoxDataValue;
+//   Voltage: I_BoxDataValue;
+//   Soc: I_BoxDataValue;
+//   AverageTemperature: I_BoxDataValue;
+//   Warnings: I_BoxDataValue;
+//   Alarms: I_BoxDataValue;
+//   Current: I_BoxDataValue;
+//   BusCurrent: I_BoxDataValue;
+//   CellsCurrent: I_BoxDataValue;
+//   HeatingCurrent: I_BoxDataValue;
+//   HeatingPower: I_BoxDataValue;
+//   SOCAh: I_BoxDataValue;
+//   BlueLeds: I_BoxDataValue;
+//   RedLeds: I_BoxDataValue;
+//   GreenLeds: I_BoxDataValue;
+//   AmberLeds: I_BoxDataValue;
+//   HeatingTemperature: I_BoxDataValue;
+//   BoardTemperature: I_BoxDataValue;
+//   LeftCellsTemperature: I_BoxDataValue;
+//   RightCellsTemperature: I_BoxDataValue;
+//   StateTemperature: I_BoxDataValue;
+//   String1Active: I_BoxDataValue;
+//   String2Active: I_BoxDataValue;
+//   String3Active: I_BoxDataValue;
+//   String4Active: I_BoxDataValue;
+//   String5Active: I_BoxDataValue;
+//   ConnectedToDcBus: I_BoxDataValue;
+//   AlarmOutActive: I_BoxDataValue;
+//   InternalFanActive: I_BoxDataValue;
+//   VoltMeasurementAllowed: I_BoxDataValue;
+//   AuxRelayBus: I_BoxDataValue;
+//   RemoteStateActive: I_BoxDataValue;
+//   RiscActive: I_BoxDataValue;
+//   Eoc: I_BoxDataValue;
+//   SerialNumber: I_BoxDataValue;
+//   TimeSinceTOC: I_BoxDataValue;
+//   CalibrationChargeRequested: I_BoxDataValue;
+//   MaxChargePower: I_BoxDataValue;
+//   MaxDischargePower: I_BoxDataValue;
+// }
+//
+// const PvKeys = ['PvId', 'Power', 'Voltage', 'Current'];
+//
+// const BatteryKeys = [
+//   'BatteryId',
+//   'FwVersion',
+//   'Power',
+//   'Voltage',
+//   'Soc',
+//   'AverageTemperature',
+//   'Warnings',
+//   'Alarms',
+//   'Current',
+//   'BusCurrent',
+//   'CellsCurrent',
+//   'HeatingCurrent',
+//   'HeatingPower',
+//   'SOCAh',
+//   'BlueLeds',
+//   'RedLeds',
+//   'GreenLeds',
+//   'AmberLeds',
+//   'HeatingTemperature',
+//   'BoardTemperature',
+//   'LeftCellsTemperature',
+//   'RightCellsTemperature',
+//   'StateTemperature',
+//   'String1Active',
+//   'String2Active',
+//   'String3Active',
+//   'String4Active',
+//   'String5Active',
+//   'ConnectedToDcBus',
+//   'AlarmOutActive',
+//   'InternalFanActive',
+//   'VoltMeasurementAllowed',
+//   'AuxRelayBus',
+//   'RemoteStateActive',
+//   'RiscActive',
+//   'Eoc',
+//   'SerialNumber',
+//   'TimeSinceTOC',
+//   'CalibrationChargeRequested',
+//   'MaxChargePower',
+//   'MaxDischargePower'
+// ];
+//
+// export type TopologyValues = {
+//   gridBox: I_BoxDataValue[];
+//   pvOnAcGridBox: I_BoxDataValue[];
+//   loadOnAcGridBox: I_BoxDataValue[];
+//   pvOnIslandBusBox: I_BoxDataValue[];
+//   loadOnIslandBusBox: I_BoxDataValue[];
+//   pvOnDcBox: I_BoxDataValue[];
+//   loadOnDcBox: I_BoxDataValue[];
+//   batteryBox: I_BoxDataValue[];
+//
+//   grid: I_BoxDataValue[];
+//   gridToAcInConnection: I_BoxDataValue[];
+//   gridBus: I_BoxDataValue[];
+//   islandBus: I_BoxDataValue[];
+//   dcBus: I_BoxDataValue[];
+//   dcBusToDcDcConnection: I_BoxDataValue[];
+//   dcDCToBatteryConnection: I_BoxDataValue[];
+//   battery: I_BoxDataValue[];
+//   dcBusToLoadOnDcConnection: I_BoxDataValue[];
+//   islandBusToLoadOnIslandBusConnection: I_BoxDataValue[];
+//   gridBusToPvOnGridbusConnection: I_BoxDataValue[];
+//   gridBusToLoadOnGridBusConnection: I_BoxDataValue[];
+//   inverter: I_BoxDataValue[];
+//   dcDc: I_BoxDataValue[];
+//   islandBusToInverter: I_BoxDataValue[];
+//   inverterToDcBus: I_BoxDataValue[];
+//   gridBusToIslandBusConnection: I_BoxDataValue[];
+//   pvOnDcBusToDcBusConnection: I_BoxDataValue[];
+//   pvOnIslandBusToIslandBusConnection: I_BoxDataValue[];
+//   minimumSoC: I_BoxDataValue[];
+//   installedDcDcPower: I_BoxDataValue[];
+//   gridSetPoint: I_BoxDataValue[];
+//   maximumDischargePower: I_BoxDataValue[];
+//   DcDcNum: I_BoxDataValue[];
+//   calibrationChargeState: I_BoxDataValue[];
+//   mode: I_BoxDataValue[];
+//   repetitiveCalibrationChargeDate: I_BoxDataValue[];
+//   additionalCalibrationChargeDate: I_BoxDataValue[];
+//
+//   batteryView: Battery[];
+//
+//   pvView: Pv[];
+// };
+// type TopologyPaths = { [key in keyof TopologyValues]: string[] };
+//
+// const batteryIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+//
+// const pvIds = [
+//   1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
+//   23, 24, 25, 26, 27, 28, 29, 30
+// ];
+//
+// const PvPaths = [
+//   '/PvOnDc/Strings/%id%/Power',
+//   '/PvOnDc/Strings/%id%/Voltage',
+//   '/PvOnDc/Strings/%id%/Current'
+// ];
+//
+// const batteryPaths = [
+//   '/Battery/Devices/%id%/FwVersion',
+//   '/Battery/Devices/%id%/Dc/Power',
+//   '/Battery/Devices/%id%/Dc/Voltage',
+//   '/Battery/Devices/%id%/Soc',
+//   '/Battery/Devices/%id%/Temperatures/Cells/Average',
+//   '/Battery/Devices/%id%/Warnings',
+//   '/Battery/Devices/%id%/Alarms',
+//
+//   '/Battery/Devices/%id%/Dc/Current',
+//   '/Battery/Devices/%id%/BusCurrent',
+//   '/Battery/Devices/%id%/CellsCurrent',
+//   '/Battery/Devices/%id%/HeatingCurrent',
+//   '/Battery/Devices/%id%/HeatingPower',
+//   '/Battery/Devices/%id%/SOCAh',
+//
+//   '/Battery/Devices/%id%/Leds/Blue',
+//   '/Battery/Devices/%id%/Leds/Red',
+//   '/Battery/Devices/%id%/Leds/Green',
+//   '/Battery/Devices/%id%/Leds/Amber',
+//
+//   '/Battery/Devices/%id%/Temperatures/Heating',
+//   '/Battery/Devices/%id%/Temperatures/Board',
+//   '/Battery/Devices/%id%/Temperatures/Cells/Left',
+//   '/Battery/Devices/%id%/Temperatures/Cells/Right',
+//   '/Battery/Devices/%id%/Temperatures/State',
+//
+//   '/Battery/Devices/%id%/BatteryStrings/String1Active',
+//   '/Battery/Devices/%id%/BatteryStrings/String2Active',
+//   '/Battery/Devices/%id%/BatteryStrings/String3Active',
+//   '/Battery/Devices/%id%/BatteryStrings/String4Active',
+//   '/Battery/Devices/%id%/BatteryStrings/String5Active',
+//
+//   '/Battery/Devices/%id%/IoStatus/ConnectedToDcBus',
+//   '/Battery/Devices/%id%/IoStatus/AlarmOutActive',
+//   '/Battery/Devices/%id%/IoStatus/InternalFanActive',
+//   '/Battery/Devices/%id%/IoStatus/VoltMeasurementAllowed',
+//   '/Battery/Devices/%id%/IoStatus/AuxRelayBus',
+//   '/Battery/Devices/%id%/IoStatus/RemoteStateActive',
+//   '/Battery/Devices/%id%/IoStatus/RiscActive',
+//
+//   '/Battery/Devices/%id%/Eoc',
+//   '/Battery/Devices/%id%/SerialNumber',
+//   '/Battery/Devices/%id%/TimeSinceTOC',
+//   '/Battery/Devices/%id%/CalibrationChargeRequested',
+//   '/Battery/Devices/%id%/MaxChargePower',
+//   '/Battery/Devices/%id%/MaxDischargePower'
+// ];
+//
+// export const topologyPaths: TopologyPaths = {
+//   gridBox: ['/Config/Devices/GridMeterIp/DeviceState'],
+//   pvOnAcGridBox: ['/Config/Devices/PvOnAcGrid/DeviceState'],
+//   loadOnAcGridBox: ['/Config/Devices/LoadOnAcGrid/DeviceState'],
+//   pvOnIslandBusBox: ['/Config/Devices/PvOnAcIsland/DeviceState'],
+//   loadOnIslandBusBox: ['/Config/Devices/IslandBusLoadMeterIp/DeviceState'],
+//   pvOnDcBox: ['/Config/Devices/PvOnDc/DeviceState'],
+//   loadOnDcBox: ['/Config/Devices/LoadOnDc/DeviceState'],
+//   batteryBox: ['/Config/Devices/BatteryIp/DeviceState'],
+//
+//   grid: [
+//     '/GridMeter/Ac/L1/Power/Active',
+//     '/GridMeter/Ac/L2/Power/Active',
+//     '/GridMeter/Ac/L3/Power/Active'
+//   ],
+//   gridToAcInConnection: ['/GridMeter/Ac/Power/Active'],
+//
+//   gridBus: [
+//     '/GridMeter/Ac/L1/Voltage',
+//     '/GridMeter/Ac/L2/Voltage',
+//     '/GridMeter/Ac/L3/Voltage'
+//   ],
+//   gridBusToPvOnGridbusConnection: ['/PvOnAcGrid/Power/Active'],
+//
+//   gridBusToLoadOnGridBusConnection: ['/LoadOnAcGrid/Power/Active'],
+//   gridBusToIslandBusConnection: ['/AcGridToAcIsland/Power/Active'],
+//
+//   islandBus: [
+//     '/AcDc/Ac/L1/Voltage',
+//     '/AcDc/Ac/L2/Voltage',
+//     '/AcDc/Ac/L3/Voltage'
+//   ],
+//   islandBusToLoadOnIslandBusConnection: ['/LoadOnAcIsland/Ac/Power/Active'],
+//   islandBusToInverter: ['/AcDc/Dc/Power'],
+//   pvOnIslandBusToIslandBusConnection: ['/PvOnAcIsland/Power/Active'],
+//
+//   inverter: [
+//     '/AcDc/Ac/L1/Power/Active',
+//     '/AcDc/Ac/L2/Power/Active',
+//     '/AcDc/Ac/L3/Power/Active',
+//     '/AcDc/Ac/L4/Power/Active'
+//   ],
+//   inverterToDcBus: ['/AcDcToDcLink/Power'],
+//
+//   dcBus: ['/DcDc/Dc/Link/Voltage'],
+//   dcBusToDcDcConnection: ['/DcDc/Dc/Link/Power'],
+//   pvOnDcBusToDcBusConnection: ['/PvOnDc/Dc/Power'],
+//   dcBusToLoadOnDcConnection: ['/LoadOnDc/Power'],
+//
+//   dcDc: ['/DcDc/Dc/Battery/Voltage'],
+//   dcDCToBatteryConnection: ['/Battery/Dc/Power'],
+//
+//   battery: [
+//     '/Battery/Soc',
+//     '/Battery/Dc/Voltage',
+//     '/Battery/Dc/Current',
+//     '/Battery/Temperature',
+//     '/Battery/Devices/1/Dc/Voltage',
+//     '/Battery/Devices/2/Dc/Voltage',
+//     '/Battery/Devices/3/Dc/Voltage',
+//     '/Battery/Devices/4/Dc/Voltage',
+//     '/Battery/Devices/5/Dc/Voltage',
+//     '/Battery/Devices/6/Dc/Voltage',
+//     '/Battery/Devices/7/Dc/Voltage',
+//     '/Battery/Devices/8/Dc/Voltage',
+//     '/Battery/Devices/9/Dc/Voltage',
+//     '/Battery/Devices/10/Dc/Voltage'
+//   ],
+//
+//   batteryView: batteryIds.flatMap((id) =>
+//     batteryPaths.map((path) => path.replace('%id%', id.toString()))
+//   ),
+//
+//   pvView: pvIds.flatMap((id) =>
+//     PvPaths.map((path) => path.replace('%id%', id.toString()))
+//   ),
+//
+//   minimumSoC: ['/Config/MinSoc'],
+//   installedDcDcPower: ['/DcDc/SystemControl/NumberOfConnectedSlaves'],
+//   gridSetPoint: ['/Config/GridSetPoint'],
+//   maximumDischargePower: ['/Config/MaxBatteryDischargingCurrent'],
+//   DcDcNum: ['/DcDc/SystemControl/NumberOfConnectedSlaves'],
+//   calibrationChargeState: ['/Config/ForceCalibrationChargeState'],
+//   mode: ['/EssControl/Mode'],
+//   repetitiveCalibrationChargeDate: [
+//     '/Config/DayAndTimeForRepetitiveCalibration'
+//   ],
+//   additionalCalibrationChargeDate: [
+//     '/Config/DayAndTimeForAdditionalCalibration'
+//   ]
+// };
+//
+// //We are using the function every time we fetch the data from S3 (every 2 seconds).
+// //The data is of the following form: TopologyValues
+// //key: I_BoxDataValue[] ==> key: [{unit:'',value:''},{unit:'',value:''},...]
+// //battery_view: [ {"Battery_id": 2,'FwVersion': {'unit':,'value':}},
+// //                {"Battery_id": 4,'FwVersion': {'unit':,'value':}}
+// //]
+// //For batteries, we follow a different approach. We define a key battery_view that is of type Battery[]
+//
+// export const extractValues = (
+//   timeSeriesData: DataPoint
+// ): TopologyValues | null => {
+//   const extractedValues: TopologyValues = {} as TopologyValues;
+//   for (const topologyKey of Object.keys(topologyPaths)) {
+//     //Each topologykey may have more than one paths (for example inverter)
+//     const paths = topologyPaths[topologyKey];
+//     let topologyValues: { unit: string; value: string | number }[] = [];
+//
+//     if (topologyKey === 'pvView') {
+//       extractedValues[topologyKey] = [];
+//       let pv_index = 0;
+//       let pathIndex = 0;
+//
+//       while (pathIndex < paths.length) {
+//         let pv = {};
+//         let existingKeys = 0;
+//
+//         //We prepare a pv object for each node. We extract the number of nodes from the  '/PvOnDc/NbrOfStrings' path.
+//         //PvKeys[0] is the pv id.
+//         pv[PvKeys[0]] = pv_index;
+//         //Then, search all the remaining battery keys
+//         for (let i = 1; i < PvKeys.length; i++) {
+//           const path = paths[pathIndex];
+//           if (timeSeriesData.value.hasOwnProperty(path)) {
+//             existingKeys++;
+//
+//             pv[PvKeys[i]] = {
+//               unit: timeSeriesData.value[path].unit.includes('~')
+//                 ? timeSeriesData.value[path].unit.replace('~', '')
+//                 : timeSeriesData.value[path].unit,
+//               value:
+//                 typeof timeSeriesData.value[path].value === 'string'
+//                   ? timeSeriesData.value[path].value
+//                   : Number(timeSeriesData.value[path].value).toFixed(1)
+//             };
+//           }
+//           pathIndex++;
+//         }
+//         pv_index++;
+//         if (existingKeys > 0) {
+//           extractedValues[topologyKey].push(pv as Pv);
+//         }
+//       }
+//     } else if (topologyKey === 'batteryView') {
+//       extractedValues[topologyKey] = [];
+//       const node_ids_from_csv = timeSeriesData.value[
+//         '/Config/Devices/BatteryNodes'
+//       ].value
+//         .toString()
+//         .split(',');
+//
+//       let battery_index = 0;
+//       let pathIndex = 0;
+//
+//       while (pathIndex < paths.length) {
+//         let battery = {};
+//         let existingKeys = 0;
+//
+//         //We prepare a battery object for each node. We extract the nodes from the  '/Config/Devices/BatteryNodes' path. For example, nodes can be [2,4,5,6] (one is missing)
+//         //BatteryKeys[0] is the battery id. We set the battery id to the corresponding node id.
+//         battery[BatteryKeys[0]] = node_ids_from_csv[battery_index];
+//         //Then, search all the remaining battery keys
+//         for (let i = 1; i < BatteryKeys.length; i++) {
+//           const path = paths[pathIndex];
+//           if (timeSeriesData.value.hasOwnProperty(path)) {
+//             existingKeys++;
+//
+//             battery[BatteryKeys[i]] = {
+//               unit: timeSeriesData.value[path].unit.includes('~')
+//                 ? timeSeriesData.value[path].unit.replace('~', '')
+//                 : timeSeriesData.value[path].unit,
+//               value:
+//                 typeof timeSeriesData.value[path].value === 'string'
+//                   ? timeSeriesData.value[path].value
+//                   : Number(timeSeriesData.value[path].value).toFixed(1)
+//             };
+//           }
+//           pathIndex++;
+//         }
+//         battery_index++;
+//         if (existingKeys > 0) {
+//           extractedValues[topologyKey].push(battery as Battery);
+//         }
+//       }
+//     } else {
+//       // Check if any of the specified paths exist in the dataRecord
+//       for (const path of paths) {
+//         if (timeSeriesData.value.hasOwnProperty(path)) {
+//           topologyValues.push({
+//             unit: timeSeriesData.value[path].unit.includes('~')
+//               ? timeSeriesData.value[path].unit.replace('~', '')
+//               : timeSeriesData.value[path].unit,
+//             value: timeSeriesData.value[path].value
+//           });
+//         }
+//       }
+//       if (topologyValues.length > 0) {
+//         extractedValues[topologyKey] = topologyValues;
+//       }
+//     }
+//   }
+//
+//   return extractedValues;
+// };
 
-export interface Pv {
-  PvId: number;
-  Power: I_BoxDataValue;
-  Voltage: I_BoxDataValue;
-  Current: I_BoxDataValue;
-}
+export const getHighestConnectionValue = (values: JSONRecordData) => {
+  // Define the paths you want to check
+  const paths = [
+    'GridMeter.Ac.Power.Active',
+    'PvOnAcGrid.Power.Active',
+    'AcGridToAcIsland.Power.Active',
+    'LoadOnAcGrid.Power.Active',
+    'PvOnAcIsland.Power.Active',
+    'AcDc.Dc.Power',
+    'LoadOnAcIsland.Ac.Power.Active',
+    'AcDcToDcLink.Power',
+    'PvOnDc.Dc.Power',
+    'DcDc.Dc.Link.Power',
+    'LoadOnDc.Power',
+    'Battery.Dc.Power'
+  ];
 
-export interface Battery {
-  BatteryId: number;
-  FwVersion: I_BoxDataValue;
-  Power: I_BoxDataValue;
-  Voltage: I_BoxDataValue;
-  Soc: I_BoxDataValue;
-  AverageTemperature: I_BoxDataValue;
-  Warnings: I_BoxDataValue;
-  Alarms: I_BoxDataValue;
-  Current: I_BoxDataValue;
-  BusCurrent: I_BoxDataValue;
-  CellsCurrent: I_BoxDataValue;
-  HeatingCurrent: I_BoxDataValue;
-  HeatingPower: I_BoxDataValue;
-  SOCAh: I_BoxDataValue;
-  BlueLeds: I_BoxDataValue;
-  RedLeds: I_BoxDataValue;
-  GreenLeds: I_BoxDataValue;
-  AmberLeds: I_BoxDataValue;
-  HeatingTemperature: I_BoxDataValue;
-  BoardTemperature: I_BoxDataValue;
-  LeftCellsTemperature: I_BoxDataValue;
-  RightCellsTemperature: I_BoxDataValue;
-  StateTemperature: I_BoxDataValue;
-  String1Active: I_BoxDataValue;
-  String2Active: I_BoxDataValue;
-  String3Active: I_BoxDataValue;
-  String4Active: I_BoxDataValue;
-  String5Active: I_BoxDataValue;
-  ConnectedToDcBus: I_BoxDataValue;
-  AlarmOutActive: I_BoxDataValue;
-  InternalFanActive: I_BoxDataValue;
-  VoltMeasurementAllowed: I_BoxDataValue;
-  AuxRelayBus: I_BoxDataValue;
-  RemoteStateActive: I_BoxDataValue;
-  RiscActive: I_BoxDataValue;
-  Eoc: I_BoxDataValue;
-  SerialNumber: I_BoxDataValue;
-  TimeSinceTOC: I_BoxDataValue;
-  CalibrationChargeRequested: I_BoxDataValue;
-  MaxChargePower: I_BoxDataValue;
-  MaxDischargePower: I_BoxDataValue;
-}
+  // Helper function to safely get a value from a nested path
+  const getValueAtPath = (obj: any, path: string) => {
+    return path.split('.').reduce((acc, part) => {
+      return acc && acc[part] !== undefined ? acc[part] : null;
+    }, obj);
+  };
 
-const PvKeys = ['PvId', 'Power', 'Voltage', 'Current'];
+  // Find the maximum value by iterating over the paths
+  return paths.reduce((maxValue, path) => {
+    // Safely get the value at the path
+    const value = getValueAtPath(values, path);
 
-const BatteryKeys = [
-  'BatteryId',
-  'FwVersion',
-  'Power',
-  'Voltage',
-  'Soc',
-  'AverageTemperature',
-  'Warnings',
-  'Alarms',
-  'Current',
-  'BusCurrent',
-  'CellsCurrent',
-  'HeatingCurrent',
-  'HeatingPower',
-  'SOCAh',
-  'BlueLeds',
-  'RedLeds',
-  'GreenLeds',
-  'AmberLeds',
-  'HeatingTemperature',
-  'BoardTemperature',
-  'LeftCellsTemperature',
-  'RightCellsTemperature',
-  'StateTemperature',
-  'String1Active',
-  'String2Active',
-  'String3Active',
-  'String4Active',
-  'String5Active',
-  'ConnectedToDcBus',
-  'AlarmOutActive',
-  'InternalFanActive',
-  'VoltMeasurementAllowed',
-  'AuxRelayBus',
-  'RemoteStateActive',
-  'RiscActive',
-  'Eoc',
-  'SerialNumber',
-  'TimeSinceTOC',
-  'CalibrationChargeRequested',
-  'MaxChargePower',
-  'MaxDischargePower'
-];
-
-export type TopologyValues = {
-  gridBox: I_BoxDataValue[];
-  pvOnAcGridBox: I_BoxDataValue[];
-  loadOnAcGridBox: I_BoxDataValue[];
-  pvOnIslandBusBox: I_BoxDataValue[];
-  loadOnIslandBusBox: I_BoxDataValue[];
-  pvOnDcBox: I_BoxDataValue[];
-  loadOnDcBox: I_BoxDataValue[];
-  batteryBox: I_BoxDataValue[];
-
-  grid: I_BoxDataValue[];
-  gridToAcInConnection: I_BoxDataValue[];
-  gridBus: I_BoxDataValue[];
-  islandBus: I_BoxDataValue[];
-  dcBus: I_BoxDataValue[];
-  dcBusToDcDcConnection: I_BoxDataValue[];
-  dcDCToBatteryConnection: I_BoxDataValue[];
-  battery: I_BoxDataValue[];
-  dcBusToLoadOnDcConnection: I_BoxDataValue[];
-  islandBusToLoadOnIslandBusConnection: I_BoxDataValue[];
-  gridBusToPvOnGridbusConnection: I_BoxDataValue[];
-  gridBusToLoadOnGridBusConnection: I_BoxDataValue[];
-  inverter: I_BoxDataValue[];
-  dcDc: I_BoxDataValue[];
-  islandBusToInverter: I_BoxDataValue[];
-  inverterToDcBus: I_BoxDataValue[];
-  gridBusToIslandBusConnection: I_BoxDataValue[];
-  pvOnDcBusToDcBusConnection: I_BoxDataValue[];
-  pvOnIslandBusToIslandBusConnection: I_BoxDataValue[];
-  minimumSoC: I_BoxDataValue[];
-  installedDcDcPower: I_BoxDataValue[];
-  gridSetPoint: I_BoxDataValue[];
-  maximumDischargePower: I_BoxDataValue[];
-  DcDcNum: I_BoxDataValue[];
-  calibrationChargeState: I_BoxDataValue[];
-  mode: I_BoxDataValue[];
-  repetitiveCalibrationChargeDate: I_BoxDataValue[];
-  additionalCalibrationChargeDate: I_BoxDataValue[];
-
-  batteryView: Battery[];
-
-  pvView: Pv[];
-};
-type TopologyPaths = { [key in keyof TopologyValues]: string[] };
-
-const batteryIds = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
-
-const pvIds = [
-  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
-  23, 24, 25, 26, 27, 28, 29, 30
-];
-
-const PvPaths = [
-  '/PvOnDc/Strings/%id%/Power',
-  '/PvOnDc/Strings/%id%/Voltage',
-  '/PvOnDc/Strings/%id%/Current'
-];
-
-const batteryPaths = [
-  '/Battery/Devices/%id%/FwVersion',
-  '/Battery/Devices/%id%/Dc/Power',
-  '/Battery/Devices/%id%/Dc/Voltage',
-  '/Battery/Devices/%id%/Soc',
-  '/Battery/Devices/%id%/Temperatures/Cells/Average',
-  '/Battery/Devices/%id%/Warnings',
-  '/Battery/Devices/%id%/Alarms',
-
-  '/Battery/Devices/%id%/Dc/Current',
-  '/Battery/Devices/%id%/BusCurrent',
-  '/Battery/Devices/%id%/CellsCurrent',
-  '/Battery/Devices/%id%/HeatingCurrent',
-  '/Battery/Devices/%id%/HeatingPower',
-  '/Battery/Devices/%id%/SOCAh',
-
-  '/Battery/Devices/%id%/Leds/Blue',
-  '/Battery/Devices/%id%/Leds/Red',
-  '/Battery/Devices/%id%/Leds/Green',
-  '/Battery/Devices/%id%/Leds/Amber',
-
-  '/Battery/Devices/%id%/Temperatures/Heating',
-  '/Battery/Devices/%id%/Temperatures/Board',
-  '/Battery/Devices/%id%/Temperatures/Cells/Left',
-  '/Battery/Devices/%id%/Temperatures/Cells/Right',
-  '/Battery/Devices/%id%/Temperatures/State',
-
-  '/Battery/Devices/%id%/BatteryStrings/String1Active',
-  '/Battery/Devices/%id%/BatteryStrings/String2Active',
-  '/Battery/Devices/%id%/BatteryStrings/String3Active',
-  '/Battery/Devices/%id%/BatteryStrings/String4Active',
-  '/Battery/Devices/%id%/BatteryStrings/String5Active',
-
-  '/Battery/Devices/%id%/IoStatus/ConnectedToDcBus',
-  '/Battery/Devices/%id%/IoStatus/AlarmOutActive',
-  '/Battery/Devices/%id%/IoStatus/InternalFanActive',
-  '/Battery/Devices/%id%/IoStatus/VoltMeasurementAllowed',
-  '/Battery/Devices/%id%/IoStatus/AuxRelayBus',
-  '/Battery/Devices/%id%/IoStatus/RemoteStateActive',
-  '/Battery/Devices/%id%/IoStatus/RiscActive',
-
-  '/Battery/Devices/%id%/Eoc',
-  '/Battery/Devices/%id%/SerialNumber',
-  '/Battery/Devices/%id%/TimeSinceTOC',
-  '/Battery/Devices/%id%/CalibrationChargeRequested',
-  '/Battery/Devices/%id%/MaxChargePower',
-  '/Battery/Devices/%id%/MaxDischargePower'
-];
-
-export const topologyPaths: TopologyPaths = {
-  gridBox: ['/Config/Devices/GridMeterIp/DeviceState'],
-  pvOnAcGridBox: ['/Config/Devices/PvOnAcGrid/DeviceState'],
-  loadOnAcGridBox: ['/Config/Devices/LoadOnAcGrid/DeviceState'],
-  pvOnIslandBusBox: ['/Config/Devices/PvOnAcIsland/DeviceState'],
-  loadOnIslandBusBox: ['/Config/Devices/IslandBusLoadMeterIp/DeviceState'],
-  pvOnDcBox: ['/Config/Devices/PvOnDc/DeviceState'],
-  loadOnDcBox: ['/Config/Devices/LoadOnDc/DeviceState'],
-  batteryBox: ['/Config/Devices/BatteryIp/DeviceState'],
-
-  grid: [
-    '/GridMeter/Ac/L1/Power/Active',
-    '/GridMeter/Ac/L2/Power/Active',
-    '/GridMeter/Ac/L3/Power/Active'
-  ],
-  gridToAcInConnection: ['/GridMeter/Ac/Power/Active'],
-
-  gridBus: [
-    '/GridMeter/Ac/L1/Voltage',
-    '/GridMeter/Ac/L2/Voltage',
-    '/GridMeter/Ac/L3/Voltage'
-  ],
-  gridBusToPvOnGridbusConnection: ['/PvOnAcGrid/Power/Active'],
-
-  gridBusToLoadOnGridBusConnection: ['/LoadOnAcGrid/Power/Active'],
-  gridBusToIslandBusConnection: ['/AcGridToAcIsland/Power/Active'],
-
-  islandBus: [
-    '/AcDc/Ac/L1/Voltage',
-    '/AcDc/Ac/L2/Voltage',
-    '/AcDc/Ac/L3/Voltage'
-  ],
-  islandBusToLoadOnIslandBusConnection: ['/LoadOnAcIsland/Ac/Power/Active'],
-  islandBusToInverter: ['/AcDc/Dc/Power'],
-  pvOnIslandBusToIslandBusConnection: ['/PvOnAcIsland/Power/Active'],
-
-  inverter: [
-    '/AcDc/Ac/L1/Power/Active',
-    '/AcDc/Ac/L2/Power/Active',
-    '/AcDc/Ac/L3/Power/Active',
-    '/AcDc/Ac/L4/Power/Active'
-  ],
-  inverterToDcBus: ['/AcDcToDcLink/Power'],
-
-  dcBus: ['/DcDc/Dc/Link/Voltage'],
-  dcBusToDcDcConnection: ['/DcDc/Dc/Link/Power'],
-  pvOnDcBusToDcBusConnection: ['/PvOnDc/Dc/Power'],
-  dcBusToLoadOnDcConnection: ['/LoadOnDc/Power'],
-
-  dcDc: ['/DcDc/Dc/Battery/Voltage'],
-  dcDCToBatteryConnection: ['/Battery/Dc/Power'],
-
-  battery: [
-    '/Battery/Soc',
-    '/Battery/Dc/Voltage',
-    '/Battery/Dc/Current',
-    '/Battery/Temperature',
-    '/Battery/Devices/1/Dc/Voltage',
-    '/Battery/Devices/2/Dc/Voltage',
-    '/Battery/Devices/3/Dc/Voltage',
-    '/Battery/Devices/4/Dc/Voltage',
-    '/Battery/Devices/5/Dc/Voltage',
-    '/Battery/Devices/6/Dc/Voltage',
-    '/Battery/Devices/7/Dc/Voltage',
-    '/Battery/Devices/8/Dc/Voltage',
-    '/Battery/Devices/9/Dc/Voltage',
-    '/Battery/Devices/10/Dc/Voltage'
-  ],
-
-  batteryView: batteryIds.flatMap((id) =>
-    batteryPaths.map((path) => path.replace('%id%', id.toString()))
-  ),
-
-  pvView: pvIds.flatMap((id) =>
-    PvPaths.map((path) => path.replace('%id%', id.toString()))
-  ),
-
-  minimumSoC: ['/Config/MinSoc'],
-  installedDcDcPower: ['/DcDc/SystemControl/NumberOfConnectedSlaves'],
-  gridSetPoint: ['/Config/GridSetPoint'],
-  maximumDischargePower: ['/Config/MaxBatteryDischargingCurrent'],
-  DcDcNum: ['/DcDc/SystemControl/NumberOfConnectedSlaves'],
-  calibrationChargeState: ['/Config/ForceCalibrationChargeState'],
-  mode: ['/EssControl/Mode'],
-  repetitiveCalibrationChargeDate: [
-    '/Config/DayAndTimeForRepetitiveCalibration'
-  ],
-  additionalCalibrationChargeDate: [
-    '/Config/DayAndTimeForAdditionalCalibration'
-  ]
-};
-
-//We are using the function every time we fetch the data from S3 (every 2 seconds).
-//The data is of the following form: TopologyValues
-//key: I_BoxDataValue[] ==> key: [{unit:'',value:''},{unit:'',value:''},...]
-//battery_view: [ {"Battery_id": 2,'FwVersion': {'unit':,'value':}},
-//                {"Battery_id": 4,'FwVersion': {'unit':,'value':}}
-//]
-//For batteries, we follow a different approach. We define a key battery_view that is of type Battery[]
-
-export const extractValues = (
-  timeSeriesData: DataPoint
-): TopologyValues | null => {
-  const extractedValues: TopologyValues = {} as TopologyValues;
-  for (const topologyKey of Object.keys(topologyPaths)) {
-    //Each topologykey may have more than one paths (for example inverter)
-    const paths = topologyPaths[topologyKey];
-    let topologyValues: { unit: string; value: string | number }[] = [];
-
-    if (topologyKey === 'pvView') {
-      extractedValues[topologyKey] = [];
-      let pv_index = 0;
-      let pathIndex = 0;
-
-      while (pathIndex < paths.length) {
-        let pv = {};
-        let existingKeys = 0;
-
-        //We prepare a pv object for each node. We extract the number of nodes from the  '/PvOnDc/NbrOfStrings' path.
-        //PvKeys[0] is the pv id.
-        pv[PvKeys[0]] = pv_index;
-        //Then, search all the remaining battery keys
-        for (let i = 1; i < PvKeys.length; i++) {
-          const path = paths[pathIndex];
-          if (timeSeriesData.value.hasOwnProperty(path)) {
-            existingKeys++;
-
-            pv[PvKeys[i]] = {
-              unit: timeSeriesData.value[path].unit.includes('~')
-                ? timeSeriesData.value[path].unit.replace('~', '')
-                : timeSeriesData.value[path].unit,
-              value:
-                typeof timeSeriesData.value[path].value === 'string'
-                  ? timeSeriesData.value[path].value
-                  : Number(timeSeriesData.value[path].value).toFixed(1)
-            };
-          }
-          pathIndex++;
-        }
-        pv_index++;
-        if (existingKeys > 0) {
-          extractedValues[topologyKey].push(pv as Pv);
-        }
-      }
-    } else if (topologyKey === 'batteryView') {
-      extractedValues[topologyKey] = [];
-      const node_ids_from_csv = timeSeriesData.value[
-        '/Config/Devices/BatteryNodes'
-      ].value
-        .toString()
-        .split(',');
-
-      let battery_index = 0;
-      let pathIndex = 0;
-
-      while (pathIndex < paths.length) {
-        let battery = {};
-        let existingKeys = 0;
-
-        //We prepare a battery object for each node. We extract the nodes from the  '/Config/Devices/BatteryNodes' path. For example, nodes can be [2,4,5,6] (one is missing)
-        //BatteryKeys[0] is the battery id. We set the battery id to the corresponding node id.
-        battery[BatteryKeys[0]] = node_ids_from_csv[battery_index];
-        //Then, search all the remaining battery keys
-        for (let i = 1; i < BatteryKeys.length; i++) {
-          const path = paths[pathIndex];
-          if (timeSeriesData.value.hasOwnProperty(path)) {
-            existingKeys++;
-
-            battery[BatteryKeys[i]] = {
-              unit: timeSeriesData.value[path].unit.includes('~')
-                ? timeSeriesData.value[path].unit.replace('~', '')
-                : timeSeriesData.value[path].unit,
-              value:
-                typeof timeSeriesData.value[path].value === 'string'
-                  ? timeSeriesData.value[path].value
-                  : Number(timeSeriesData.value[path].value).toFixed(1)
-            };
-          }
-          pathIndex++;
-        }
-        battery_index++;
-        if (existingKeys > 0) {
-          extractedValues[topologyKey].push(battery as Battery);
-        }
-      }
-    } else {
-      // Check if any of the specified paths exist in the dataRecord
-      for (const path of paths) {
-        if (timeSeriesData.value.hasOwnProperty(path)) {
-          topologyValues.push({
-            unit: timeSeriesData.value[path].unit.includes('~')
-              ? timeSeriesData.value[path].unit.replace('~', '')
-              : timeSeriesData.value[path].unit,
-            value: timeSeriesData.value[path].value
-          });
-        }
-      }
-      if (topologyValues.length > 0) {
-        extractedValues[topologyKey] = topologyValues;
-      }
+    // If the value exists and is a number, check if it's greater than the current max
+    if (value !== null) {
+      return Math.max(maxValue, Math.abs(value));
     }
-  }
-
-  return extractedValues;
+    return maxValue;
+  }, 0); // Initialize maxValue to 0
 };
 
-export const getHighestConnectionValue = (values: TopologyValues) => {
-  return Object.keys(values)
-    .filter((value) => value.includes('Connection'))
-    .reduce((acc, curr) => {
-      let topologyValue = values[
-        curr as keyof TopologyValues
-      ][0] as I_BoxDataValue;
-
-      const value = Math.abs(topologyValue.value as number);
-      return value > acc ? value : acc;
-    }, 0);
-};
-
-export const getAmount = (
-  highestConnectionValue: number,
-  values: I_BoxDataValue[]
-) => {
-  // console.log(
-  //   'value=',
-  //   Math.abs(values[0].value as number),
-  //   'highest is',
-  //   highestConnectionValue,
-  //   'and amount is ',
-  //   parseFloat(
-  //     (Math.abs(values[0].value as number) / highestConnectionValue).toFixed(1)
-  //   )
-  // );
-  return parseFloat(
-    (Math.abs(values[0].value as number) / highestConnectionValue).toFixed(1)
-  );
+export const getAmount = (highestConnectionValue: number, value: number) => {
+  return parseFloat((Math.abs(value) / highestConnectionValue).toFixed(1));
 };
diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx
index 0b3c81f96..5e798ae25 100644
--- a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx
@@ -15,8 +15,8 @@ import {
   chartAggregatedDataInterface,
   chartDataInterface,
   overviewInterface,
-  transformInputToAggregatedData,
-  transformInputToDailyData
+  transformInputToAggregatedDataJson,
+  transformInputToDailyDataJson
 } from 'src/interfaces/Chart';
 import Button from '@mui/material/Button';
 import { FormattedMessage } from 'react-intl';
@@ -82,13 +82,13 @@ function Overview(props: OverviewProps) {
   const [endDate, setEndDate] = useState(dayjs());
   const [isZooming, setIsZooming] = useState(false);
 
-  console.log(
-    UnixTime.fromTicks(new Date().getTime() / 1000).earlier(
-      TimeSpan.fromDays(1)
-    )
-  );
-
-  console.log(UnixTime.fromTicks(new Date().getTime() / 1000));
+  // console.log(
+  //   UnixTime.fromTicks(new Date().getTime() / 1000).earlier(
+  //     TimeSpan.fromDays(1)
+  //   )
+  // );
+  //
+  // console.log(UnixTime.fromTicks(new Date().getTime() / 1000));
   useEffect(() => {
     if (isZooming) {
       setLoading(true);
@@ -101,7 +101,7 @@ function Overview(props: OverviewProps) {
     const resultPromise: Promise<{
       chartData: chartDataInterface;
       chartOverview: overviewInterface;
-    }> = transformInputToDailyData(
+    }> = transformInputToDailyDataJson(
       props.s3Credentials,
       props.id,
       UnixTime.fromTicks(new Date().getTime() / 1000).earlier(
@@ -136,7 +136,7 @@ function Overview(props: OverviewProps) {
     const resultPromise: Promise<{
       chartData: chartDataInterface;
       chartOverview: overviewInterface;
-    }> = transformInputToDailyData(
+    }> = transformInputToDailyDataJson(
       props.s3Credentials,
       props.id,
       UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
@@ -193,7 +193,7 @@ function Overview(props: OverviewProps) {
       chartAggregatedData: chartAggregatedDataInterface;
       chartOverview: overviewInterface;
       dateList: string[];
-    }> = transformInputToAggregatedData(
+    }> = transformInputToAggregatedDataJson(
       props.s3Credentials,
       dayjs().subtract(1, 'week'),
       dayjs()
@@ -264,7 +264,7 @@ function Overview(props: OverviewProps) {
       const resultPromise: Promise<{
         chartData: chartDataInterface;
         chartOverview: overviewInterface;
-      }> = transformInputToDailyData(
+      }> = transformInputToDailyDataJson(
         props.s3Credentials,
         props.id,
         UnixTime.fromTicks(startDate.unix()),
@@ -296,7 +296,7 @@ function Overview(props: OverviewProps) {
         chartAggregatedData: chartAggregatedDataInterface;
         chartOverview: overviewInterface;
         dateList: string[];
-      }> = transformInputToAggregatedData(
+      }> = transformInputToAggregatedDataJson(
         props.s3Credentials,
         startDate,
         endDate
diff --git a/typescript/frontend-marios2/src/content/dashboards/PvView/PvView.tsx b/typescript/frontend-marios2/src/content/dashboards/PvView/PvView.tsx
index 3bdd2825c..49434e2a6 100644
--- a/typescript/frontend-marios2/src/content/dashboards/PvView/PvView.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/PvView/PvView.tsx
@@ -10,13 +10,13 @@ import {
   TableRow,
   Typography
 } from '@mui/material';
-import { TopologyValues } from '../Log/graph.util';
+import { JSONRecordData } from '../Log/graph.util';
 import { useLocation, useNavigate } from 'react-router-dom';
 import routes from '../../../Resources/routes.json';
 import CircularProgress from '@mui/material/CircularProgress';
 
 interface PvViewProps {
-  values: TopologyValues;
+  values: JSONRecordData;
   connected: boolean;
 }
 
@@ -26,9 +26,14 @@ function PvView(props: PvViewProps) {
   }
   const currentLocation = useLocation();
   const navigate = useNavigate();
+
   const sortedPvView =
-    props.values != null
-      ? [...props.values.pvView].sort((a, b) => a.PvId - b.PvId)
+    props.values != null && props.values.PvOnDc
+      ? Object.entries(props.values.PvOnDc.Strings)
+          .map(([pvId, pv]) => {
+            return { pvId, pv }; // Here we return an object with the id and device
+          })
+          .sort((a, b) => parseInt(b.pvId) - parseInt(a.pvId))
       : [];
 
   const [loading, setLoading] = useState(sortedPvView.length == 0);
@@ -37,13 +42,13 @@ function PvView(props: PvViewProps) {
     navigate(routes.mainstats);
   };
 
-  const findBatteryData = (batteryId: number) => {
-    for (let i = 0; i < props.values.batteryView.length; i++) {
-      if (props.values.batteryView[i].BatteryId == batteryId) {
-        return props.values.batteryView[i];
-      }
-    }
-  };
+  // const findBatteryData = (batteryId: number) => {
+  //   for (let i = 0; i < props.values.batteryView.length; i++) {
+  //     if (props.values.batteryView[i].BatteryId == batteryId) {
+  //       return props.values.batteryView[i];
+  //     }
+  //   }
+  // };
 
   useEffect(() => {
     if (sortedPvView.length == 0) {
@@ -123,9 +128,9 @@ function PvView(props: PvViewProps) {
                 </TableRow>
               </TableHead>
               <TableBody>
-                {sortedPvView.map((pv) => (
+                {sortedPvView.map(({ pvId, pv }) => (
                   <TableRow
-                    key={pv.PvId}
+                    key={pvId}
                     style={{
                       height: '10px'
                     }}
@@ -136,42 +141,44 @@ function PvView(props: PvViewProps) {
                       align="center"
                       sx={{ width: '10%', fontWeight: 'bold', color: 'black' }}
                     >
-                      {'AMPT ' + pv.PvId}
+                      {'AMPT ' + pvId}
                     </TableCell>
 
                     <TableCell
                       sx={{
                         width: '10%',
                         textAlign: 'center',
+                        fontWeight: 'bold',
                         backgroundColor:
-                          pv.Current.value == 0 ? '#FF033E' : '#32CD32',
-                        color: pv.Power.value === '' ? 'white' : 'inherit'
+                          pv.Current == 0 ? '#FF033E' : '#32CD32',
+                        color: 'inherit'
                       }}
                     >
-                      {pv.Power.value + ' ' + pv.Power.unit}
+                      {pv.Power + ' W'}
                     </TableCell>
                     <TableCell
                       sx={{
                         width: '10%',
                         textAlign: 'center',
-
+                        fontWeight: 'bold',
                         backgroundColor:
-                          pv.Current.value == 0 ? '#FF033E' : '#32CD32',
-                        color: pv.Voltage.value === '' ? 'white' : 'inherit'
+                          pv.Current == 0 ? '#FF033E' : '#32CD32',
+                        color: 'inherit'
                       }}
                     >
-                      {pv.Voltage.value + ' ' + pv.Voltage.unit}
+                      {pv.Voltage + ' V'}
                     </TableCell>
                     <TableCell
                       sx={{
                         width: '10%',
                         textAlign: 'center',
+                        fontWeight: 'bold',
                         backgroundColor:
-                          pv.Current.value == 0 ? '#FF033E' : '#32CD32',
-                        color: pv.Current.value === '' ? 'white' : 'inherit'
+                          pv.Current == 0 ? '#FF033E' : '#32CD32',
+                        color: 'inherit'
                       }}
                     >
-                      {pv.Current.value + ' ' + pv.Current.unit}
+                      {pv.Current + ' A'}
                     </TableCell>
                   </TableRow>
                 ))}
diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx
index d5131e1d2..db2a2ddb0 100644
--- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx
@@ -104,6 +104,8 @@ function SalidomoInstallation(props: singleInstallationProps) {
     setLoading(false);
     console.log('NUMBER OF FILES=' + Object.keys(res).length);
 
+    console.log('res=', res);
+
     while (continueFetching.current) {
       for (const timestamp of Object.keys(res)) {
         if (!continueFetching.current) {
@@ -111,7 +113,7 @@ function SalidomoInstallation(props: singleInstallationProps) {
           return false;
         }
         console.log(`Timestamp: ${timestamp}`);
-        //console.log('object is' + res[timestamp]);
+        console.log('object is', res);
 
         // Set values asynchronously with delay
         setValues(res[timestamp]);
diff --git a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx
index 2adc8315b..2eaeed85e 100644
--- a/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/SodiohomeInstallations/Installation.tsx
@@ -8,7 +8,7 @@ import {
 } from '@mui/material';
 import { I_Installation } from 'src/interfaces/InstallationTypes';
 import { UserContext } from 'src/contexts/userContext';
-import { TopologyValues } from 'src/content/dashboards/Log/graph.util';
+import { JSONRecordData } from 'src/content/dashboards/Log/graph.util';
 import { FormattedMessage } from 'react-intl';
 import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
 import routes from '../../../Resources/routes.json';
@@ -36,7 +36,7 @@ function SodioHomeInstallation(props: singleInstallationProps) {
   const location = useLocation().pathname;
   const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
   const [currentTab, setCurrentTab] = useState<string>(undefined);
-  const [values, setValues] = useState<TopologyValues | null>(null);
+  const [values, setValues] = useState<JSONRecordData | null>(null);
   const status = props.current_installation.status;
   const [
     failedToCommunicateWithInstallation,
diff --git a/typescript/frontend-marios2/src/content/dashboards/Topology/Topology.tsx b/typescript/frontend-marios2/src/content/dashboards/Topology/Topology.tsx
index bae7deb89..d9a92dae1 100644
--- a/typescript/frontend-marios2/src/content/dashboards/Topology/Topology.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/Topology/Topology.tsx
@@ -10,11 +10,11 @@ import TopologyColumn from './topologyColumn';
 import {
   getAmount,
   getHighestConnectionValue,
-  TopologyValues
+  JSONRecordData
 } from '../Log/graph.util';
 
 interface TopologyProps {
-  values: TopologyValues;
+  values: JSONRecordData;
   connected: boolean;
   loading: boolean;
 }
@@ -25,6 +25,9 @@ function Topology(props: TopologyProps) {
   }
   const highestConnectionValue =
     props.values != null ? getHighestConnectionValue(props.values) : 0;
+
+  //console.log(props.values.DcDc.Dc.Battery.Voltage);
+
   const [showValues, setShowValues] = useState(false);
 
   const handleSwitch = () => () => {
@@ -109,17 +112,38 @@ function Topology(props: TopologyProps) {
               <TopologyColumn
                 centerBox={{
                   title: 'Grid',
-                  data: props.values.grid,
+                  data: props.values?.GridMeter
+                    ? [
+                        {
+                          value: props.values.GridMeter.Ac.L1.Power.Active,
+                          unit: 'W'
+                        },
+                        {
+                          value: props.values.GridMeter.Ac.L2.Power.Active,
+                          unit: 'W'
+                        },
+                        {
+                          value: props.values.GridMeter.Ac.L3.Power.Active,
+                          unit: 'W'
+                        }
+                      ]
+                    : undefined,
                   connected:
-                    props.values.gridBox[0].value.toString() != 'Disabled'
+                    props.values.Config.Devices.GridMeterIp.DeviceState !=
+                    'Disabled'
                 }}
                 centerConnection={{
                   orientation: 'horizontal',
-                  data: props.values.gridToAcInConnection,
-                  amount: props.values.gridToAcInConnection
+                  data: props.values?.GridMeter
+                    ? {
+                        value: props.values.GridMeter.Ac.Power.Active,
+                        unit: 'W'
+                      }
+                    : undefined,
+                  amount: props.values?.GridMeter
                     ? getAmount(
                         highestConnectionValue,
-                        props.values.gridToAcInConnection
+                        props.values.GridMeter.Ac.Power.Active
                       )
                     : 0,
                   showValues: showValues
@@ -127,57 +151,103 @@ function Topology(props: TopologyProps) {
                 isLast={false}
                 isFirst={true}
               />
+              {/*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/}
               <TopologyColumn
                 topBox={{
                   title: 'Pv Inverter',
-                  data: props.values.gridBusToPvOnGridbusConnection,
+                  data: props.values?.PvOnAcGrid
+                    ? [
+                        {
+                          value: props.values.PvOnAcGrid.Power.Active,
+                          unit: 'W'
+                        }
+                      ]
+                    : undefined,
                   connected:
-                    props.values.pvOnAcGridBox[0].value.toString() != 'Disabled'
+                    props.values.Config.Devices.PvOnAcGrid.DeviceState !==
+                    'Disabled'
                 }}
                 topConnection={{
                   orientation: 'vertical',
                   position: 'top',
-                  data: props.values.gridBusToPvOnGridbusConnection,
-                  amount: props.values.gridBusToPvOnGridbusConnection
+                  data: props.values?.PvOnAcGrid
+                    ? {
+                        value: props.values.PvOnAcGrid.Power.Active,
+                        unit: 'W'
+                      }
+                    : undefined,
+                  amount: props.values?.PvOnAcGrid
                     ? getAmount(
                         highestConnectionValue,
-                        props.values.gridBusToPvOnGridbusConnection
+                        props.values.PvOnAcGrid.Power.Active
                       )
                     : 0,
                   showValues: showValues
                 }}
                 centerBox={{
                   title: 'Grid Bus',
-                  data: props.values.gridBus,
+                  data: props.values?.GridMeter
+                    ? [
+                        {
+                          value: props.values.GridMeter.Ac.L1.Voltage,
+                          unit: 'V'
+                        },
+                        {
+                          value: props.values.GridMeter.Ac.L2.Voltage,
+                          unit: 'V'
+                        },
+                        {
+                          value: props.values.GridMeter.Ac.L3.Voltage,
+                          unit: 'V'
+                        }
+                      ]
+                    : undefined,
                   connected: true
                 }}
                 centerConnection={{
                   orientation: 'horizontal',
 
-                  amount: props.values.gridBusToIslandBusConnection
+                  data: props.values?.AcGridToAcIsland
+                    ? {
+                        value: props.values.AcGridToAcIsland.Power.Active,
+                        unit: 'W'
+                      }
+                    : undefined,
+                  amount: props.values?.AcGridToAcIsland
                     ? getAmount(
                         highestConnectionValue,
-                        props.values.gridBusToIslandBusConnection
+                        props.values.AcGridToAcIsland.Power.Active
                       )
                     : 0,
-                  data: props.values.gridBusToIslandBusConnection,
                   showValues: showValues
                 }}
                 bottomBox={{
                   title: 'AC Loads',
-                  data: props.values.gridBusToLoadOnGridBusConnection,
+                  data: props.values?.LoadOnAcGrid
+                    ? [
+                        {
+                          value: props.values.LoadOnAcGrid.Power.Active,
+                          unit: 'W'
+                        }
+                      ]
+                    : undefined,
                   connected:
-                    props.values.loadOnAcGridBox[0].value.toString() !=
+                    props.values.Config.Devices.LoadOnAcGrid.DeviceState !=
                     'Disabled'
                 }}
                 bottomConnection={{
                   orientation: 'vertical',
                   position: 'bottom',
-                  data: props.values.gridBusToLoadOnGridBusConnection,
-                  amount: props.values.gridBusToLoadOnGridBusConnection
+                  data: props.values?.LoadOnAcGrid
+                    ? {
+                        value: props.values.LoadOnAcGrid.Power.Active,
+                        unit: 'W'
+                      }
+                    : undefined,
+                  amount: props.values?.LoadOnAcGrid
                     ? getAmount(
                         highestConnectionValue,
-                        props.values.gridBusToLoadOnGridBusConnection
+                        props.values.LoadOnAcGrid.Power.Active
                       )
                     : 0,
                   showValues: showValues
@@ -185,58 +255,102 @@ function Topology(props: TopologyProps) {
                 isLast={false}
                 isFirst={false}
               />
+              {/*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/}
               <TopologyColumn
                 topBox={{
                   title: 'Pv Inverter',
-                  data: props.values.pvOnIslandBusToIslandBusConnection,
+                  data: props.values?.PvOnAcIsland
+                    ? [
+                        {
+                          value: props.values.PvOnAcIsland.Power.Active,
+                          unit: 'W'
+                        }
+                      ]
+                    : undefined,
                   connected:
-                    props.values.pvOnIslandBusBox[0].value.toString() !=
+                    props.values.Config.Devices.PvOnAcIsland.DeviceState !==
                     'Disabled'
                 }}
                 topConnection={{
                   orientation: 'vertical',
                   position: 'top',
-                  data: props.values.pvOnIslandBusToIslandBusConnection,
-                  amount: props.values.pvOnIslandBusToIslandBusConnection
+                  data: props.values?.PvOnAcIsland
+                    ? {
+                        value: props.values.PvOnAcIsland.Power.Active,
+                        unit: 'W'
+                      }
+                    : undefined,
+                  amount: props.values?.PvOnAcIsland
                     ? getAmount(
                         highestConnectionValue,
-                        props.values.pvOnIslandBusToIslandBusConnection
+                        props.values.PvOnAcIsland.Power.Active
                       )
                     : 0,
                   showValues: showValues
                 }}
                 centerBox={{
                   title: 'Island Bus',
-                  data: props.values.islandBus,
+                  data: props.values?.AcDc
+                    ? [
+                        {
+                          value: props.values.AcDc.Ac.L1.Voltage,
+                          unit: 'V'
+                        },
+                        {
+                          value: props.values.AcDc.Ac.L2.Voltage,
+                          unit: 'V'
+                        },
+                        {
+                          value: props.values.AcDc.Ac.L3.Voltage,
+                          unit: 'V'
+                        }
+                      ]
+                    : undefined,
                   connected: true
                 }}
                 centerConnection={{
                   orientation: 'horizontal',
-                  data: props.values.islandBusToInverter,
-                  amount: props.values.islandBusToInverter
+                  data: props.values?.AcDc
+                    ? {
+                        value: props.values.AcDc.Dc.Power,
+                        unit: 'W'
+                      }
+                    : undefined,
+                  amount: props.values.AcDc
                     ? getAmount(
                         highestConnectionValue,
-                        props.values.islandBusToInverter
+                        props.values.AcDc.Dc.Power
                       )
                     : 0,
                   showValues: showValues
                 }}
                 bottomBox={{
                   title: 'AC Loads',
-
-                  data: props.values.islandBusToLoadOnIslandBusConnection,
+                  data: props.values?.LoadOnAcIsland
+                    ? [
+                        {
+                          value: props.values.LoadOnAcIsland.Ac.Power.Active,
+                          unit: 'W'
+                        }
+                      ]
+                    : undefined,
                   connected:
-                    props.values.loadOnIslandBusBox[0].value.toString() !=
-                    'Disabled'
+                    props.values.Config.Devices.IslandBusLoadMeterIp
+                      .DeviceState != 'Disabled'
                 }}
                 bottomConnection={{
                   orientation: 'vertical',
                   position: 'bottom',
-                  data: props.values.islandBusToLoadOnIslandBusConnection,
-                  amount: props.values.islandBusToLoadOnIslandBusConnection
+                  data: props.values?.LoadOnAcIsland
+                    ? {
+                        value: props.values.LoadOnAcIsland.Ac.Power.Active,
+                        unit: 'W'
+                      }
+                    : undefined,
+                  amount: props.values?.LoadOnAcIsland
                     ? getAmount(
                         highestConnectionValue,
-                        props.values.islandBusToLoadOnIslandBusConnection
+                        props.values.LoadOnAcIsland.Ac.Power.Active
                       )
                     : 0,
                   showValues: showValues
@@ -244,19 +358,40 @@ function Topology(props: TopologyProps) {
                 isLast={false}
                 isFirst={false}
               />
+              {/*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/}
               <TopologyColumn
                 centerBox={{
                   title: 'AC-DC',
-                  data: props.values.inverter,
+                  data: props.values.AcDc
+                    ? [
+                        {
+                          value: props.values.AcDc.Ac.L1.Power.Active,
+                          unit: 'W'
+                        },
+                        {
+                          value: props.values.AcDc.Ac.L2.Power.Active,
+                          unit: 'W'
+                        },
+                        {
+                          value: props.values.AcDc.Ac.L3.Power.Active,
+                          unit: 'W'
+                        }
+                      ]
+                    : undefined,
                   connected: true
                 }}
                 centerConnection={{
                   orientation: 'horizontal',
-                  data: props.values.inverterToDcBus,
-                  amount: props.values.inverterToDcBus
+                  data: props.values?.AcDcToDcLink
+                    ? {
+                        value: props.values.AcDcToDcLink.Power,
+                        unit: 'W'
+                      }
+                    : undefined,
+                  amount: props.values?.AcDcToDcLink
                     ? getAmount(
                         highestConnectionValue,
-                        props.values.inverterToDcBus
+                        props.values.AcDcToDcLink.Power
                       )
                     : 0,
                   showValues: showValues
@@ -264,55 +399,93 @@ function Topology(props: TopologyProps) {
                 isLast={false}
                 isFirst={false}
               />
+              {/*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/}
               <TopologyColumn
                 topBox={{
                   title: 'Pv DC-DC',
-                  data: props.values.pvOnDcBusToDcBusConnection,
+                  data: props.values?.PvOnDc
+                    ? [
+                        {
+                          value: props.values.PvOnDc.Dc.Power,
+                          unit: 'W'
+                        }
+                      ]
+                    : undefined,
                   connected:
-                    props.values.pvOnDcBox[0].value.toString() != 'Disabled'
+                    props.values.Config.Devices.PvOnDc.DeviceState != 'Disabled'
                 }}
                 topConnection={{
                   orientation: 'vertical',
                   position: 'top',
-                  data: props.values.pvOnDcBusToDcBusConnection,
-                  amount: props.values.pvOnDcBusToDcBusConnection
+                  data: props.values?.PvOnDc
+                    ? {
+                        value: props.values.PvOnDc.Dc.Power,
+                        unit: 'W'
+                      }
+                    : undefined,
+                  amount: props.values?.PvOnDc
                     ? getAmount(
                         highestConnectionValue,
-                        props.values.pvOnDcBusToDcBusConnection
+                        props.values.PvOnDc.Dc.Power
                       )
                     : 0,
                   showValues: showValues
                 }}
                 centerBox={{
                   title: 'DC Link',
-                  data: props.values.dcBus,
+                  data: props.values?.DcDc
+                    ? [
+                        {
+                          value: props.values.DcDc.Dc.Link.Voltage,
+                          unit: 'W'
+                        }
+                      ]
+                    : undefined,
                   connected: true
                 }}
                 centerConnection={{
                   orientation: 'horizontal',
-                  data: props.values.dcBusToDcDcConnection,
-                  amount: props.values.dcBusToDcDcConnection
+                  data: props.values?.DcDc
+                    ? {
+                        value: props.values.DcDc.Dc.Link.Power,
+                        unit: 'W'
+                      }
+                    : undefined,
+                  amount: props.values?.DcDc
                     ? getAmount(
                         highestConnectionValue,
-                        props.values.dcBusToDcDcConnection
+                        props.values.DcDc.Dc.Link.Power
                       )
                     : 0,
                   showValues: showValues
                 }}
                 bottomBox={{
                   title: 'DC Loads',
-                  data: props.values.dcBusToLoadOnDcConnection,
+                  data: props.values?.LoadOnDc
+                    ? [
+                        {
+                          value: props.values.LoadOnDc.Power,
+                          unit: 'W'
+                        }
+                      ]
+                    : undefined,
                   connected:
-                    props.values.loadOnDcBox[0].value.toString() != 'Disabled'
+                    props.values.Config.Devices.LoadOnDc.DeviceState !=
+                    'Disabled'
                 }}
                 bottomConnection={{
                   orientation: 'vertical',
                   position: 'bottom',
-                  data: props.values.dcBusToLoadOnDcConnection,
-                  amount: props.values.dcBusToLoadOnDcConnection
+                  data: props.values?.LoadOnDc
+                    ? {
+                        value: props.values.LoadOnDc.Power,
+                        unit: 'W'
+                      }
+                    : undefined,
+                  amount: props.values?.LoadOnDc
                     ? getAmount(
                         highestConnectionValue,
-                        props.values.dcBusToLoadOnDcConnection
+                        props.values.LoadOnDc.Power
                       )
                     : 0,
                   showValues: showValues
@@ -320,19 +493,33 @@ function Topology(props: TopologyProps) {
                 isLast={false}
                 isFirst={false}
               />
+              {/*-------------------------------------------------------------------------------------------------------------------------------------------------------------*/}
+
               <TopologyColumn
                 centerBox={{
                   title: 'DC-DC',
-                  data: props.values.dcDc,
+                  data: props.values?.DcDc
+                    ? [
+                        {
+                          value: props.values.DcDc.Dc.Battery.Voltage,
+                          unit: 'V'
+                        }
+                      ]
+                    : undefined,
                   connected: true
                 }}
                 centerConnection={{
                   orientation: 'horizontal',
-                  data: props.values.dcDCToBatteryConnection,
-                  amount: props.values.dcDCToBatteryConnection
+                  data: props.values?.Battery?.Dc
+                    ? {
+                        value: props.values.Battery.Dc.Power,
+                        unit: 'W'
+                      }
+                    : undefined,
+                  amount: props.values?.Battery?.Dc
                     ? getAmount(
                         highestConnectionValue,
-                        props.values.dcDCToBatteryConnection
+                        props.values.Battery.Dc.Power
                       )
                     : 0,
                   showValues: showValues
@@ -343,9 +530,35 @@ function Topology(props: TopologyProps) {
               <TopologyColumn
                 centerBox={{
                   title: 'Battery',
-                  data: props.values.battery,
+                  data: props.values.Battery?.Dc
+                    ? [
+                        {
+                          value: props.values.Battery.Soc,
+                          unit: '%'
+                        },
+                        {
+                          value: props.values.Battery.Dc.Voltage,
+                          unit: 'V'
+                        },
+                        {
+                          value: props.values.Battery.Dc.Current,
+                          unit: 'A'
+                        },
+                        {
+                          value: props.values.Battery.Temperature,
+                          unit: '°C'
+                        },
+                        {
+                          value: Object.keys(props.values.Battery.Devices)
+                            .length,
+                          unit: ''
+                        }
+                      ]
+                    : undefined,
+
                   connected:
-                    props.values.batteryBox[0].value.toString() != 'Disabled'
+                    props.values.Config.Devices.BatteryIp.DeviceState !=
+                    'Disabled'
                 }}
                 isLast={true}
                 isFirst={false}
diff --git a/typescript/frontend-marios2/src/content/dashboards/Topology/topologyBox.tsx b/typescript/frontend-marios2/src/content/dashboards/Topology/topologyBox.tsx
index 8c3e7aed2..aa720646d 100644
--- a/typescript/frontend-marios2/src/content/dashboards/Topology/topologyBox.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/Topology/topologyBox.tsx
@@ -20,8 +20,9 @@ export interface TopologyBoxProps {
 
 function formatPower(value, unit) {
   if (isNaN(value)) {
-    return 'Invalid';
+    return '';
   }
+  value = parseFloat(value);
 
   const prefixes = ['', 'k', 'M', 'G', 'T'];
   let magnitude = 0;
@@ -40,6 +41,7 @@ function formatPower(value, unit) {
 
   //Filter all values less than 100 Watts
   if (magnitude === 0 && value < 100 && unit === 'W') {
+    //console.log('DROP THIS VALUE ' + value);
     return 0;
   }
 
@@ -55,8 +57,13 @@ function TopologyBox(props: TopologyBoxProps) {
     <Card
       sx={{
         visibility:
-          //props.data && props.data.values[0].value != 0 ? 'visible' : 'hidden',
-          props.connected ? 'visible' : 'hidden',
+          props.data &&
+          typeof props.data[0].value === 'number' &&
+          isNaN(props.data[0].value)
+            ? 'hidden'
+            : props.connected
+            ? 'visible'
+            : 'hidden',
 
         width: isMobile ? '90px' : '104px',
         height:
@@ -94,7 +101,7 @@ function TopologyBox(props: TopologyBoxProps) {
         >
           {props.data && props.title === 'Battery' && (
             <Typography variant="h5" noWrap>
-              {props.title} (x{props.data.length - 4})
+              {props.title} (x{props.data[4].value})
             </Typography>
           )}
           {props.title != 'Battery' && (
diff --git a/typescript/frontend-marios2/src/content/dashboards/Topology/topologyFlow.tsx b/typescript/frontend-marios2/src/content/dashboards/Topology/topologyFlow.tsx
index 95e436e1c..e4c2069dc 100644
--- a/typescript/frontend-marios2/src/content/dashboards/Topology/topologyFlow.tsx
+++ b/typescript/frontend-marios2/src/content/dashboards/Topology/topologyFlow.tsx
@@ -6,7 +6,7 @@ import { I_BoxDataValue } from '../Log/graph.util';
 export interface TopologyFlowProps {
   orientation: string;
   position?: string;
-  data?: I_BoxDataValue[];
+  data?: I_BoxDataValue;
   amount?: number;
   showValues: boolean;
 }
@@ -28,6 +28,8 @@ function formatPower(value) {
     return 'Invalid';
   }
 
+  value = parseFloat(value);
+
   const prefixes = ['', 'k', 'M', 'G', 'T'];
   let magnitude = 0;
   let negative = false;
@@ -103,7 +105,7 @@ function TopologyFlow(props: TopologyFlowProps) {
             zIndex: 1
           }}
         >
-          {props.showValues && props.data[0].value != 0 && (
+          {props.showValues && props.data.value != 0 && (
             <Typography
               sx={{
                 marginTop: '1px',
@@ -114,12 +116,10 @@ function TopologyFlow(props: TopologyFlowProps) {
                 zIndex: 1
               }}
             >
-              {formatPower(props.data[0].value) === 0
+              {formatPower(props.data.value) === 0
                 ? null
-                : formatPower(props.data[0].value)}
-              {formatPower(props.data[0].value) === 0
-                ? null
-                : props.data[0].unit}
+                : formatPower(props.data.value)}
+              {formatPower(props.data.value) === 0 ? null : props.data.unit}
             </Typography>
           )}
         </Box>
@@ -154,15 +154,13 @@ function TopologyFlow(props: TopologyFlowProps) {
       >
         {props.orientation === 'horizontal' &&
           props.data &&
-          props.data[0].value != 0 && (
+          props.data.value != 0 && (
             <>
               <div className="container">
                 {dotStyles.map((style, index) => (
                   <div
                     className={
-                      (props.data[0].value as number) >= 0
-                        ? 'dotRight'
-                        : 'dotLeft'
+                      (props.data.value as number) >= 0 ? 'dotRight' : 'dotLeft'
                     }
                     key={index}
                     style={{
@@ -177,7 +175,7 @@ function TopologyFlow(props: TopologyFlowProps) {
 
         {props.orientation === 'vertical' &&
           props.data &&
-          props.data[0].value != 0 && (
+          props.data.value != 0 && (
             <div className="dot-container">
               {dotStylesVertical.map((style, index) => (
                 <div
diff --git a/typescript/frontend-marios2/src/interfaces/Chart.tsx b/typescript/frontend-marios2/src/interfaces/Chart.tsx
index 8216d8feb..32d48d3e2 100644
--- a/typescript/frontend-marios2/src/interfaces/Chart.tsx
+++ b/typescript/frontend-marios2/src/interfaces/Chart.tsx
@@ -1,8 +1,6 @@
 import dayjs from 'dayjs';
 import {
-  fetchAggregatedData,
   fetchAggregatedDataJson,
-  fetchData,
   fetchDataJson
 } from '../content/dashboards/Installations/fetchData';
 import { FetchResult } from '../dataCache/dataCache';
@@ -12,6 +10,7 @@ import { DataRecord } from '../dataCache/data';
 import axiosConfig from '../Resources/axiosConfig';
 import { AxiosError, AxiosResponse } from 'axios';
 import { JSONRecordData } from '../content/dashboards/Log/graph.util';
+import get from 'lodash/get';
 
 export interface chartInfoInterface {
   magnitude: number;
@@ -173,10 +172,11 @@ export const transformInputToBatteryViewDataJson = async (
       ];
 
       const result = results[i][timestamp];
+      console.log(result);
+      const battery_nodes =
+        result.Config.Devices.BatteryNodes.toString().split(',');
 
-      const battery_nodes = result.Config.Devices.BatteryNodes.value
-        .toString()
-        .split(',');
+      console.log(battery_nodes);
 
       //Initialize the chartData structure based on the node names extracted from the first result
       let old_length = pathsToSave.length;
@@ -220,35 +220,35 @@ export const transformInputToBatteryViewDataJson = async (
         let category = categories[category_index];
 
         for (let j = 0; j < pathsToSave.length; j++) {
-          let path =
-            pathsToSearch[j] + pathCategories[category_index] + '.value';
+          let path = pathsToSearch[j] + pathCategories[category_index];
 
-          //if (result[path]) {
-          const value = path
-            .split('.')
-            .reduce((o, key) => (o ? o[key] : undefined), result);
+          if (get(result, path) !== undefined) {
+            const value = path
+              .split('.')
+              .reduce((o, key) => (o ? o[key] : undefined), result);
 
-          if (value < chartOverview[category].min) {
-            chartOverview[category].min = value;
+            if (value < chartOverview[category].min) {
+              chartOverview[category].min = value;
+            }
+
+            if (value > chartOverview[category].max) {
+              chartOverview[category].max = value;
+            }
+            chartData[category].data[pathsToSave[j]].data.push([
+              adjustedTimestampArray[i],
+              value
+            ]);
+          } else {
+            // chartData[category].data[pathsToSave[j]].data.push([
+            //   adjustedTimestampArray[i],
+            //   null
+            // ]);
           }
-
-          if (value > chartOverview[category].max) {
-            chartOverview[category].max = value;
-          }
-          chartData[category].data[pathsToSave[j]].data.push([
-            adjustedTimestampArray[i],
-            value
-          ]);
-          // } else {
-          //   // chartData[category].data[pathsToSave[j]].data.push([
-          //   //   adjustedTimestampArray[i],
-          //   //   null
-          //   // ]);
-          // }
         }
       }
     }
   }
+
   categories.forEach((category) => {
     let value = Math.max(
       Math.abs(chartOverview[category].max),
@@ -278,6 +278,198 @@ export const transformInputToBatteryViewDataJson = async (
   chartOverview.Current.unit =
     '(' + prefixes[chartOverview['Current'].magnitude] + 'A' + ')';
 
+  console.log(chartData);
+
+  return {
+    chartData: chartData,
+    chartOverview: chartOverview
+  };
+};
+
+export const transformInputToDailyDataJson = async (
+  s3Credentials: I_S3Credentials,
+  id: number,
+  start_time?: UnixTime,
+  end_time?: UnixTime
+): Promise<{
+  chartData: chartDataInterface;
+  chartOverview: overviewInterface;
+}> => {
+  const prefixes = ['', 'k', 'M', 'G', 'T'];
+  const MAX_NUMBER = 9999999;
+  const pathsToSearch = [
+    'Battery.Soc',
+    'Battery.Temperature',
+    'Battery.Dc.Power',
+    'GridMeter.Ac.Power.Active',
+    'PvOnDc.Dc.Power',
+    'DcDc.Dc.Link.Voltage',
+    'LoadOnAcGrid.Power.Active',
+    'LoadOnDc.Power'
+  ];
+  const categories = [
+    'soc',
+    'temperature',
+    'dcPower',
+    'gridPower',
+    'pvProduction',
+    'dcBusVoltage',
+    'ACLoad',
+    'DCLoad'
+  ];
+
+  const chartData: chartDataInterface = {
+    soc: { name: 'State Of Charge', data: [] },
+    temperature: { name: 'Battery Temperature', data: [] },
+    dcPower: { name: 'Battery Power', data: [] },
+    gridPower: { name: 'Grid Power', data: [] },
+    pvProduction: { name: 'Pv Production', data: [] },
+    dcBusVoltage: { name: 'DC Bus Voltage', data: [] },
+    ACLoad: { name: 'AC Load', data: [] },
+    DCLoad: { name: 'DC Load', data: [] }
+  };
+
+  const chartOverview: overviewInterface = {
+    soc: { magnitude: 0, unit: '', min: 0, max: 0 },
+    temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
+    dcPower: { magnitude: 0, unit: '', min: 0, max: 0 },
+    dcPowerWithoutHeating: { magnitude: 0, unit: '', min: 0, max: 0 },
+    gridPower: { magnitude: 0, unit: '', min: 0, max: 0 },
+    pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 },
+    dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 },
+    overview: { magnitude: 0, unit: '', min: 0, max: 0 },
+    ACLoad: { magnitude: 0, unit: '', min: 0, max: 0 },
+    DCLoad: { magnitude: 0, unit: '', min: 0, max: 0 }
+  };
+
+  categories.forEach((category) => {
+    chartData[category].data = [];
+    chartOverview[category] = {
+      magnitude: 0,
+      unit: '',
+      min: MAX_NUMBER,
+      max: -MAX_NUMBER
+    };
+  });
+
+  let timestampArray: number[] = [];
+  let adjustedTimestampArray = [];
+  const timestampPromises = [];
+
+  await axiosConfig
+    .get(
+      `/GetCsvTimestampsForInstallation?id=${id}&start=${start_time.ticks}&end=${end_time.ticks}`
+    )
+    .then((res: AxiosResponse<number[]>) => {
+      timestampArray = res.data;
+    })
+    .catch((err: AxiosError) => {
+      if (err.response && err.response.status == 401) {
+        //removeToken();
+        //navigate(routes.login);
+      }
+    });
+
+  for (var i = 0; i < timestampArray.length; i++) {
+    timestampPromises.push(
+      fetchJsonDataForOneTime(
+        UnixTime.fromTicks(timestampArray[i]),
+        s3Credentials
+      )
+    );
+
+    const adjustedTimestamp = new Date(timestampArray[i] * 1000);
+    //Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
+    adjustedTimestamp.setHours(
+      adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
+    );
+    adjustedTimestampArray.push(adjustedTimestamp);
+  }
+
+  const results: Promise<FetchResult<Record<string, JSONRecordData>>>[] =
+    await Promise.all(timestampPromises);
+
+  for (let i = 0; i < results.length; i++) {
+    if (results[i] == null) {
+      // Handle not available or try later case
+    } else {
+      const timestamp = Object.keys(results[i])[
+        Object.keys(results[i]).length - 1
+      ];
+      const result = results[i][timestamp];
+      let category_index = 0;
+      // eslint-disable-next-line @typescript-eslint/no-loop-func
+      pathsToSearch.forEach((path) => {
+        if (get(result, path) !== undefined) {
+          const value = path
+            .split('.')
+            .reduce((o, key) => (o ? o[key] : undefined), result);
+
+          if (value < chartOverview[categories[category_index]].min) {
+            chartOverview[categories[category_index]].min = value;
+          }
+
+          if (value > chartOverview[categories[category_index]].max) {
+            chartOverview[categories[category_index]].max = value;
+          }
+          chartData[categories[category_index]].data.push([
+            adjustedTimestampArray[i],
+            value
+          ]);
+        } else {
+          //data[path].push([adjustedTimestamp, null]);
+        }
+        category_index++;
+      });
+    }
+  }
+
+  categories.forEach((category) => {
+    let value = Math.max(
+      Math.abs(chartOverview[category].max),
+      Math.abs(chartOverview[category].min)
+    );
+    let magnitude = 0;
+
+    if (value < 0) {
+      value = -value;
+    }
+    while (value >= 1000) {
+      value /= 1000;
+      magnitude++;
+    }
+    chartOverview[category].magnitude = magnitude;
+  });
+
+  chartOverview.soc.unit = '(%)';
+  chartOverview.soc.min = 0;
+  chartOverview.soc.max = 100;
+  chartOverview.temperature.unit = '(°C)';
+  chartOverview.dcPower.unit =
+    '(' + prefixes[chartOverview['dcPower'].magnitude] + 'W' + ')';
+  chartOverview.gridPower.unit =
+    '(' + prefixes[chartOverview['gridPower'].magnitude] + 'W' + ')';
+  chartOverview.pvProduction.unit =
+    '(' + prefixes[chartOverview['pvProduction'].magnitude] + 'W' + ')';
+  chartOverview.dcBusVoltage.unit =
+    '(' + prefixes[chartOverview['dcBusVoltage'].magnitude] + 'V' + ')';
+
+  chartOverview.overview = {
+    magnitude: Math.max(
+      chartOverview['gridPower'].magnitude,
+      chartOverview['pvProduction'].magnitude
+    ),
+    unit: '(kW)',
+    min: Math.min(
+      chartOverview['gridPower'].min,
+      chartOverview['pvProduction'].min
+    ),
+    max: Math.max(
+      chartOverview['gridPower'].max,
+      chartOverview['pvProduction'].max
+    )
+  };
+
   return {
     chartData: chartData,
     chartOverview: chartOverview
@@ -401,7 +593,6 @@ export const transformInputToAggregatedDataJson = async (
     ) {
       // Handle not available or try later case
     } else {
-      console.log(result);
       dateList.push(currentDay.format('DD-MM'));
       pathsToSearch.forEach((path) => {
         const value = path
@@ -528,8 +719,6 @@ export const transformInputToAggregatedDataJson = async (
     )
   };
 
-  // console.log(chartAggregatedData);
-
   return {
     chartAggregatedData: chartAggregatedData,
     chartOverview: chartOverview,
@@ -537,6 +726,15 @@ export const transformInputToAggregatedDataJson = async (
   };
 };
 
+//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+
 //UNCOMMENT THE FOLLOWING FOR CSV
 // We use this function in order to retrieve data for main stats.
 //The data is of the following form:
@@ -548,215 +746,215 @@ export const transformInputToAggregatedDataJson = async (
 //                          'Node1': {name:'Node1', data: [[timestamp,value],[timestamp,value]]},
 //                          'Node2': {name:'Node2', data: [[timestamp,value],[timestamp,value]]},
 //        ]}
-
-export const transformInputToBatteryViewData = async (
-  s3Credentials: I_S3Credentials,
-  id: number,
-  product: number,
-  start_time?: UnixTime,
-  end_time?: UnixTime
-): Promise<{
-  chartData: BatteryDataInterface;
-  chartOverview: BatteryOverviewInterface;
-}> => {
-  const prefixes = ['', 'k', 'M', 'G', 'T'];
-  const MAX_NUMBER = 9999999;
-  const categories = ['Soc', 'Temperature', 'Power', 'Voltage', 'Current'];
-  const pathCategories = [
-    'Soc',
-    'Temperatures/Cells/Average',
-    'Dc/Power',
-    'Dc/Voltage',
-    'Dc/Current'
-  ];
-
-  const pathsToSearch = [
-    '/Battery/Devices/1/',
-    '/Battery/Devices/2/',
-    '/Battery/Devices/3/',
-    '/Battery/Devices/4/',
-    '/Battery/Devices/5/',
-    '/Battery/Devices/6/',
-    '/Battery/Devices/7/',
-    '/Battery/Devices/8/',
-    '/Battery/Devices/9/',
-    '/Battery/Devices/10/'
-  ];
-
-  const pathsToSave = [];
-
-  const chartData: BatteryDataInterface = {
-    Soc: { name: 'State Of Charge', data: [] },
-    Temperature: { name: 'Temperature', data: [] },
-    Power: { name: 'Power', data: [] },
-    Voltage: { name: 'Voltage', data: [] },
-    Current: { name: 'Voltage', data: [] }
-  };
-
-  const chartOverview: BatteryOverviewInterface = {
-    Soc: { magnitude: 0, unit: '', min: 0, max: 0 },
-    Temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
-    Power: { magnitude: 0, unit: '', min: 0, max: 0 },
-    Voltage: { magnitude: 0, unit: '', min: 0, max: 0 },
-    Current: { magnitude: 0, unit: '', min: 0, max: 0 }
-  };
-
-  let initialiation = true;
-
-  let timestampArray: number[] = [];
-  let adjustedTimestampArray = [];
-  const timestampPromises = [];
-
-  await axiosConfig
-    .get(
-      `/GetCsvTimestampsForInstallation?id=${id}&start=${start_time.ticks}&end=${end_time.ticks}`
-    )
-    .then((res: AxiosResponse<number[]>) => {
-      timestampArray = res.data;
-    })
-    .catch((err: AxiosError) => {
-      if (err.response && err.response.status == 401) {
-        //removeToken();
-        //navigate(routes.login);
-      }
-    });
-
-  for (var i = 0; i < timestampArray.length; i++) {
-    timestampPromises.push(
-      fetchDataForOneTime(
-        UnixTime.fromTicks(timestampArray[i], true),
-        s3Credentials
-      )
-    );
-
-    const adjustedTimestamp =
-      product == 0
-        ? new Date(timestampArray[i] * 1000)
-        : new Date(timestampArray[i] * 100000);
-    //Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
-    adjustedTimestamp.setHours(
-      adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
-    );
-    adjustedTimestampArray.push(adjustedTimestamp);
-  }
-
-  const results: Promise<FetchResult<Record<string, DataRecord>>>[] =
-    await Promise.all(timestampPromises);
-
-  for (let i = 0; i < results.length; i++) {
-    if (results[i] == null) {
-      // Handle not available or try later case
-    } else {
-      const timestamp = Object.keys(results[i])[
-        Object.keys(results[i]).length - 1
-      ];
-      const result = results[i][timestamp];
-      const battery_nodes = result['/Config/Devices/BatteryNodes'].value
-        .toString()
-        .split(',');
-
-      //Initialize the chartData structure based on the node names extracted from the first result
-      let old_length = pathsToSave.length;
-
-      if (battery_nodes.length > old_length) {
-        battery_nodes.forEach((node) => {
-          if (!pathsToSave.includes('Node' + node)) {
-            pathsToSave.push('Node' + node);
-          }
-        });
-      }
-
-      if (initialiation) {
-        initialiation = false;
-        categories.forEach((category) => {
-          chartData[category].data = [];
-          chartOverview[category] = {
-            magnitude: 0,
-            unit: '',
-            min: MAX_NUMBER,
-            max: -MAX_NUMBER
-          };
-        });
-      }
-
-      if (battery_nodes.length > old_length) {
-        categories.forEach((category) => {
-          pathsToSave.forEach((path) => {
-            if (pathsToSave.indexOf(path) >= old_length) {
-              chartData[category].data[path] = { name: path, data: [] };
-            }
-          });
-        });
-      }
-
-      for (
-        let category_index = 0;
-        category_index < pathCategories.length;
-        category_index++
-      ) {
-        let category = categories[category_index];
-
-        for (let j = 0; j < pathsToSave.length; j++) {
-          let path = pathsToSearch[j] + pathCategories[category_index];
-
-          if (result[path]) {
-            const value = result[path];
-
-            if (value.value < chartOverview[category].min) {
-              chartOverview[category].min = value.value;
-            }
-
-            if (value.value > chartOverview[category].max) {
-              chartOverview[category].max = value.value;
-            }
-            chartData[category].data[pathsToSave[j]].data.push([
-              adjustedTimestampArray[i],
-              value.value
-            ]);
-          } else {
-            // chartData[category].data[pathsToSave[j]].data.push([
-            //   adjustedTimestampArray[i],
-            //   null
-            // ]);
-          }
-        }
-      }
-    }
-  }
-  categories.forEach((category) => {
-    let value = Math.max(
-      Math.abs(chartOverview[category].max),
-      Math.abs(chartOverview[category].min)
-    );
-    let magnitude = 0;
-
-    if (value < 0) {
-      value = -value;
-    }
-    while (value >= 1000) {
-      value /= 1000;
-      magnitude++;
-    }
-    chartOverview[category].magnitude = magnitude;
-  });
-
-  chartOverview.Soc.unit = '(%)';
-  chartOverview.Soc.min = 0;
-  chartOverview.Soc.max = 100;
-  chartOverview.Temperature.unit = '(°C)';
-  chartOverview.Power.unit =
-    '(' + prefixes[chartOverview['Power'].magnitude] + 'W' + ')';
-  chartOverview.Voltage.unit =
-    '(' + prefixes[chartOverview['Voltage'].magnitude] + 'V' + ')';
-
-  chartOverview.Current.unit =
-    '(' + prefixes[chartOverview['Current'].magnitude] + 'A' + ')';
-
-  return {
-    chartData: chartData,
-    chartOverview: chartOverview
-  };
-};
+//
+// export const transformInputToBatteryViewData = async (
+//   s3Credentials: I_S3Credentials,
+//   id: number,
+//   product: number,
+//   start_time?: UnixTime,
+//   end_time?: UnixTime
+// ): Promise<{
+//   chartData: BatteryDataInterface;
+//   chartOverview: BatteryOverviewInterface;
+// }> => {
+//   const prefixes = ['', 'k', 'M', 'G', 'T'];
+//   const MAX_NUMBER = 9999999;
+//   const categories = ['Soc', 'Temperature', 'Power', 'Voltage', 'Current'];
+//   const pathCategories = [
+//     'Soc',
+//     'Temperatures/Cells/Average',
+//     'Dc/Power',
+//     'Dc/Voltage',
+//     'Dc/Current'
+//   ];
+//
+//   const pathsToSearch = [
+//     '/Battery/Devices/1/',
+//     '/Battery/Devices/2/',
+//     '/Battery/Devices/3/',
+//     '/Battery/Devices/4/',
+//     '/Battery/Devices/5/',
+//     '/Battery/Devices/6/',
+//     '/Battery/Devices/7/',
+//     '/Battery/Devices/8/',
+//     '/Battery/Devices/9/',
+//     '/Battery/Devices/10/'
+//   ];
+//
+//   const pathsToSave = [];
+//
+//   const chartData: BatteryDataInterface = {
+//     Soc: { name: 'State Of Charge', data: [] },
+//     Temperature: { name: 'Temperature', data: [] },
+//     Power: { name: 'Power', data: [] },
+//     Voltage: { name: 'Voltage', data: [] },
+//     Current: { name: 'Voltage', data: [] }
+//   };
+//
+//   const chartOverview: BatteryOverviewInterface = {
+//     Soc: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     Temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     Power: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     Voltage: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     Current: { magnitude: 0, unit: '', min: 0, max: 0 }
+//   };
+//
+//   let initialiation = true;
+//
+//   let timestampArray: number[] = [];
+//   let adjustedTimestampArray = [];
+//   const timestampPromises = [];
+//
+//   await axiosConfig
+//     .get(
+//       `/GetCsvTimestampsForInstallation?id=${id}&start=${start_time.ticks}&end=${end_time.ticks}`
+//     )
+//     .then((res: AxiosResponse<number[]>) => {
+//       timestampArray = res.data;
+//     })
+//     .catch((err: AxiosError) => {
+//       if (err.response && err.response.status == 401) {
+//         //removeToken();
+//         //navigate(routes.login);
+//       }
+//     });
+//
+//   for (var i = 0; i < timestampArray.length; i++) {
+//     timestampPromises.push(
+//       fetchDataForOneTime(
+//         UnixTime.fromTicks(timestampArray[i], true),
+//         s3Credentials
+//       )
+//     );
+//
+//     const adjustedTimestamp =
+//       product == 0
+//         ? new Date(timestampArray[i] * 1000)
+//         : new Date(timestampArray[i] * 100000);
+//     //Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
+//     adjustedTimestamp.setHours(
+//       adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
+//     );
+//     adjustedTimestampArray.push(adjustedTimestamp);
+//   }
+//
+//   const results: Promise<FetchResult<Record<string, DataRecord>>>[] =
+//     await Promise.all(timestampPromises);
+//
+//   for (let i = 0; i < results.length; i++) {
+//     if (results[i] == null) {
+//       // Handle not available or try later case
+//     } else {
+//       const timestamp = Object.keys(results[i])[
+//         Object.keys(results[i]).length - 1
+//       ];
+//       const result = results[i][timestamp];
+//       const battery_nodes = result['/Config/Devices/BatteryNodes'].value
+//         .toString()
+//         .split(',');
+//
+//       //Initialize the chartData structure based on the node names extracted from the first result
+//       let old_length = pathsToSave.length;
+//
+//       if (battery_nodes.length > old_length) {
+//         battery_nodes.forEach((node) => {
+//           if (!pathsToSave.includes('Node' + node)) {
+//             pathsToSave.push('Node' + node);
+//           }
+//         });
+//       }
+//
+//       if (initialiation) {
+//         initialiation = false;
+//         categories.forEach((category) => {
+//           chartData[category].data = [];
+//           chartOverview[category] = {
+//             magnitude: 0,
+//             unit: '',
+//             min: MAX_NUMBER,
+//             max: -MAX_NUMBER
+//           };
+//         });
+//       }
+//
+//       if (battery_nodes.length > old_length) {
+//         categories.forEach((category) => {
+//           pathsToSave.forEach((path) => {
+//             if (pathsToSave.indexOf(path) >= old_length) {
+//               chartData[category].data[path] = { name: path, data: [] };
+//             }
+//           });
+//         });
+//       }
+//
+//       for (
+//         let category_index = 0;
+//         category_index < pathCategories.length;
+//         category_index++
+//       ) {
+//         let category = categories[category_index];
+//
+//         for (let j = 0; j < pathsToSave.length; j++) {
+//           let path = pathsToSearch[j] + pathCategories[category_index];
+//
+//           if (result[path]) {
+//             const value = result[path];
+//
+//             if (value.value < chartOverview[category].min) {
+//               chartOverview[category].min = value.value;
+//             }
+//
+//             if (value.value > chartOverview[category].max) {
+//               chartOverview[category].max = value.value;
+//             }
+//             chartData[category].data[pathsToSave[j]].data.push([
+//               adjustedTimestampArray[i],
+//               value.value
+//             ]);
+//           } else {
+//             // chartData[category].data[pathsToSave[j]].data.push([
+//             //   adjustedTimestampArray[i],
+//             //   null
+//             // ]);
+//           }
+//         }
+//       }
+//     }
+//   }
+//   categories.forEach((category) => {
+//     let value = Math.max(
+//       Math.abs(chartOverview[category].max),
+//       Math.abs(chartOverview[category].min)
+//     );
+//     let magnitude = 0;
+//
+//     if (value < 0) {
+//       value = -value;
+//     }
+//     while (value >= 1000) {
+//       value /= 1000;
+//       magnitude++;
+//     }
+//     chartOverview[category].magnitude = magnitude;
+//   });
+//
+//   chartOverview.Soc.unit = '(%)';
+//   chartOverview.Soc.min = 0;
+//   chartOverview.Soc.max = 100;
+//   chartOverview.Temperature.unit = '(°C)';
+//   chartOverview.Power.unit =
+//     '(' + prefixes[chartOverview['Power'].magnitude] + 'W' + ')';
+//   chartOverview.Voltage.unit =
+//     '(' + prefixes[chartOverview['Voltage'].magnitude] + 'V' + ')';
+//
+//   chartOverview.Current.unit =
+//     '(' + prefixes[chartOverview['Current'].magnitude] + 'A' + ')';
+//
+//   return {
+//     chartData: chartData,
+//     chartOverview: chartOverview
+//   };
+// };
 
 // We use this function in order to retrieve data for main stats.
 // The data is of the following form:
@@ -768,444 +966,444 @@ export const transformInputToBatteryViewData = async (
 //                          'Node1': {name:'Node1', data: [[timestamp,value],[timestamp,value]]},
 //                          'Node2': {name:'Node2', data: [[timestamp,value],[timestamp,value]]},
 //        ]}
-
-const fetchDataForOneTime = async (
-  startUnixTime: UnixTime,
-  s3Credentials: I_S3Credentials
-): Promise<FetchResult<Record<string, DataRecord>>> => {
-  var timeperiodToSearch = 2;
-  let res;
-  let timestampToFetch;
-
-  for (var i = 0; i < timeperiodToSearch; i++) {
-    timestampToFetch = startUnixTime.later(TimeSpan.fromSeconds(i));
-
-    try {
-      res = await fetchData(timestampToFetch, s3Credentials);
-
-      if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
-        //console.log('Successfully fetched ' + timestampToFetch);
-        return res;
-      }
-    } catch (err) {
-      console.error('Error fetching data:', err);
-    }
-  }
-  return null;
-};
-
-export const transformInputToDailyData = async (
-  s3Credentials: I_S3Credentials,
-  id: number,
-  start_time?: UnixTime,
-  end_time?: UnixTime
-): Promise<{
-  chartData: chartDataInterface;
-  chartOverview: overviewInterface;
-}> => {
-  //const navigate = useNavigate();
-  //const tokencontext = useContext(TokenContext);
-  //const { removeToken } = tokencontext;
-  const prefixes = ['', 'k', 'M', 'G', 'T'];
-  const MAX_NUMBER = 9999999;
-  const pathsToSearch = [
-    '/Battery/Soc',
-    '/Battery/Temperature',
-    '/Battery/Dc/Power',
-    '/GridMeter/Ac/Power/Active',
-    '/PvOnDc/Dc/Power',
-    '/DcDc/Dc/Link/Voltage',
-    '/LoadOnAcGrid/Power/Active',
-    '/LoadOnDc/Power'
-  ];
-  const categories = [
-    'soc',
-    'temperature',
-    'dcPower',
-    'gridPower',
-    'pvProduction',
-    'dcBusVoltage',
-    'ACLoad',
-    'DCLoad'
-  ];
-
-  const chartData: chartDataInterface = {
-    soc: { name: 'State Of Charge', data: [] },
-    temperature: { name: 'Battery Temperature', data: [] },
-    dcPower: { name: 'Battery Power', data: [] },
-    gridPower: { name: 'Grid Power', data: [] },
-    pvProduction: { name: 'Pv Production', data: [] },
-    dcBusVoltage: { name: 'DC Bus Voltage', data: [] },
-    ACLoad: { name: 'AC Load', data: [] },
-    DCLoad: { name: 'DC Load', data: [] }
-  };
-
-  const chartOverview: overviewInterface = {
-    soc: { magnitude: 0, unit: '', min: 0, max: 0 },
-    temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
-    dcPower: { magnitude: 0, unit: '', min: 0, max: 0 },
-    dcPowerWithoutHeating: { magnitude: 0, unit: '', min: 0, max: 0 },
-    gridPower: { magnitude: 0, unit: '', min: 0, max: 0 },
-    pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 },
-    dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 },
-    overview: { magnitude: 0, unit: '', min: 0, max: 0 },
-    ACLoad: { magnitude: 0, unit: '', min: 0, max: 0 },
-    DCLoad: { magnitude: 0, unit: '', min: 0, max: 0 }
-  };
-
-  categories.forEach((category) => {
-    chartData[category].data = [];
-    chartOverview[category] = {
-      magnitude: 0,
-      unit: '',
-      min: MAX_NUMBER,
-      max: -MAX_NUMBER
-    };
-  });
-
-  let timestampArray: number[] = [];
-  let adjustedTimestampArray = [];
-  const timestampPromises = [];
-
-  await axiosConfig
-    .get(
-      `/GetCsvTimestampsForInstallation?id=${id}&start=${start_time.ticks}&end=${end_time.ticks}`
-    )
-    .then((res: AxiosResponse<number[]>) => {
-      timestampArray = res.data;
-    })
-    .catch((err: AxiosError) => {
-      if (err.response && err.response.status == 401) {
-        //removeToken();
-        //navigate(routes.login);
-      }
-    });
-
-  for (var i = 0; i < timestampArray.length; i++) {
-    timestampPromises.push(
-      fetchDataForOneTime(UnixTime.fromTicks(timestampArray[i]), s3Credentials)
-    );
-
-    const adjustedTimestamp = new Date(timestampArray[i] * 1000);
-    //Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
-    adjustedTimestamp.setHours(
-      adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
-    );
-    adjustedTimestampArray.push(adjustedTimestamp);
-  }
-
-  const results: Promise<FetchResult<Record<string, DataRecord>>>[] =
-    await Promise.all(timestampPromises);
-
-  for (let i = 0; i < results.length; i++) {
-    if (results[i] == null) {
-      // Handle not available or try later case
-    } else {
-      const timestamp = Object.keys(results[i])[
-        Object.keys(results[i]).length - 1
-      ];
-      const result = results[i][timestamp];
-      let category_index = 0;
-      // eslint-disable-next-line @typescript-eslint/no-loop-func
-      pathsToSearch.forEach((path) => {
-        if (result[path]) {
-          const value = result[path];
-
-          if (value.value < chartOverview[categories[category_index]].min) {
-            chartOverview[categories[category_index]].min = value.value;
-          }
-
-          if (value.value > chartOverview[categories[category_index]].max) {
-            chartOverview[categories[category_index]].max = value.value;
-          }
-          chartData[categories[category_index]].data.push([
-            adjustedTimestampArray[i],
-            value.value
-          ]);
-        } else {
-          //data[path].push([adjustedTimestamp, null]);
-        }
-        category_index++;
-      });
-    }
-  }
-
-  categories.forEach((category) => {
-    let value = Math.max(
-      Math.abs(chartOverview[category].max),
-      Math.abs(chartOverview[category].min)
-    );
-    let magnitude = 0;
-
-    if (value < 0) {
-      value = -value;
-    }
-    while (value >= 1000) {
-      value /= 1000;
-      magnitude++;
-    }
-    chartOverview[category].magnitude = magnitude;
-  });
-
-  chartOverview.soc.unit = '(%)';
-  chartOverview.soc.min = 0;
-  chartOverview.soc.max = 100;
-  chartOverview.temperature.unit = '(°C)';
-  chartOverview.dcPower.unit =
-    '(' + prefixes[chartOverview['dcPower'].magnitude] + 'W' + ')';
-  chartOverview.gridPower.unit =
-    '(' + prefixes[chartOverview['gridPower'].magnitude] + 'W' + ')';
-  chartOverview.pvProduction.unit =
-    '(' + prefixes[chartOverview['pvProduction'].magnitude] + 'W' + ')';
-  chartOverview.dcBusVoltage.unit =
-    '(' + prefixes[chartOverview['dcBusVoltage'].magnitude] + 'V' + ')';
-
-  chartOverview.overview = {
-    magnitude: Math.max(
-      chartOverview['gridPower'].magnitude,
-      chartOverview['pvProduction'].magnitude
-    ),
-    unit: '(kW)',
-    min: Math.min(
-      chartOverview['gridPower'].min,
-      chartOverview['pvProduction'].min
-    ),
-    max: Math.max(
-      chartOverview['gridPower'].max,
-      chartOverview['pvProduction'].max
-    )
-  };
-
-  return {
-    chartData: chartData,
-    chartOverview: chartOverview
-  };
-};
-
-export const transformInputToAggregatedData = async (
-  s3Credentials: I_S3Credentials,
-  start_date: dayjs.Dayjs,
-  end_date: dayjs.Dayjs
-): Promise<{
-  chartAggregatedData: chartAggregatedDataInterface;
-  chartOverview: overviewInterface;
-  dateList: string[];
-}> => {
-  const data = {};
-  const overviewData = {};
-  const MAX_NUMBER = 9999999;
-  const dateList = [];
-
-  //let currentDate = dayjs();
-  let currentDay = start_date;
-  // type === 'weekly'
-  //   ? currentDate.subtract(1, 'week')
-  //   : currentDate.subtract(1, 'month');
-
-  const pathsToSearch = [
-    '/MinSoc',
-    '/MaxSoc',
-    '/PvPower',
-    '/DischargingBatteryPower',
-    '/ChargingBatteryPower',
-    '/GridImportPower',
-    '/GridExportPower',
-    '/HeatingPower'
-  ];
-
-  const categories = [
-    'minsoc',
-    'maxsoc',
-    'pvProduction',
-    'dcChargingPower',
-    'heatingPower',
-    'dcDischargingPower',
-    'gridImportPower',
-    'gridExportPower'
-  ];
-
-  const chartAggregatedData: chartAggregatedDataInterface = {
-    minsoc: { name: 'min SOC', data: [] },
-    maxsoc: { name: 'max SOC', data: [] },
-    pvProduction: { name: 'Pv Energy', data: [] },
-    dcChargingPower: { name: 'Charging Battery Energy', data: [] },
-    heatingPower: { name: 'Heating Energy', data: [] },
-    dcDischargingPower: { name: 'Discharging Battery Energy', data: [] },
-    gridImportPower: { name: 'Grid Import Energy', data: [] },
-    gridExportPower: { name: 'Grid Export Energy', data: [] }
-  };
-
-  const chartOverview: overviewInterface = {
-    soc: { magnitude: 0, unit: '', min: 0, max: 0 },
-    temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
-    dcPower: { magnitude: 0, unit: '', min: 0, max: 0 },
-    dcPowerWithoutHeating: { magnitude: 0, unit: '', min: 0, max: 0 },
-    gridPower: { magnitude: 0, unit: '', min: 0, max: 0 },
-    pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 },
-    dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 },
-    overview: { magnitude: 0, unit: '', min: 0, max: 0 },
-    ACLoad: { magnitude: 0, unit: '', min: 0, max: 0 },
-    DCLoad: { magnitude: 0, unit: '', min: 0, max: 0 }
-  };
-
-  pathsToSearch.forEach((path) => {
-    data[path] = [];
-    overviewData[path] = {
-      magnitude: 0,
-      unit: '',
-      min: MAX_NUMBER,
-      max: -MAX_NUMBER
-    };
-  });
-
-  const timestampPromises = [];
-
-  while (currentDay.isBefore(end_date)) {
-    timestampPromises.push(
-      fetchAggregatedData(currentDay.format('YYYY-MM-DD'), s3Credentials)
-    );
-    currentDay = currentDay.add(1, 'day');
-  }
-
-  const results = await Promise.all(timestampPromises);
-  currentDay = start_date;
-
-  for (let i = 0; i < results.length; i++) {
-    const result = results[i];
-    if (
-      result === FetchResult.notAvailable ||
-      result === FetchResult.tryLater
-    ) {
-      // Handle not available or try later case
-    } else {
-      dateList.push(currentDay.format('DD-MM'));
-      pathsToSearch.forEach((path) => {
-        if (result[path]) {
-          if (path === '/GridExportPower') {
-            result[path].value = -result[path].value;
-          }
-          if (result[path].value < overviewData[path].min) {
-            overviewData[path].min = result[path].value;
-          }
-          if (result[path].value > overviewData[path].max) {
-            overviewData[path].max = result[path].value;
-          }
-          // if (path === '/GridExportPower' && Math.abs(result[path].value) < 0.1) {
-          //   result[path].value = -0.3;
-          // }
-          data[path].push(result[path].value as number);
-        }
-      });
-    }
-    currentDay = currentDay.add(1, 'day');
-  }
-
-  pathsToSearch.forEach((path) => {
-    let value = Math.max(
-      Math.abs(overviewData[path].max),
-      Math.abs(overviewData[path].min)
-    );
-    let magnitude = 0;
-
-    if (value < 0) {
-      value = -value;
-    }
-    while (value >= 1000) {
-      value /= 1000;
-      magnitude++;
-    }
-    overviewData[path].magnitude = magnitude;
-  });
-
-  let path = '/MinSoc';
-  chartAggregatedData.minsoc.data = data[path];
-
-  path = '/MaxSoc';
-  chartAggregatedData.maxsoc.data = data[path];
-
-  chartOverview.soc = {
-    unit: '(%)',
-    magnitude: overviewData[path].magnitude,
-    min: 0,
-    max: 100
-  };
-
-  path = '/PvPower';
-  chartAggregatedData.pvProduction.data = data[path];
-
-  chartOverview.pvProduction = {
-    magnitude: overviewData[path].magnitude,
-    unit: '(kWh)',
-    min: overviewData[path].min,
-    max: overviewData[path].max
-  };
-
-  path = '/ChargingBatteryPower';
-  chartAggregatedData.dcChargingPower.data = data[path];
-
-  path = '/DischargingBatteryPower';
-  chartAggregatedData.dcDischargingPower.data = data[path];
-
-  path = '/HeatingPower';
-  chartAggregatedData.heatingPower.data = data[path];
-
-  chartOverview.dcPowerWithoutHeating = {
-    magnitude: Math.max(
-      overviewData['/ChargingBatteryPower'].magnitude,
-      overviewData['/DischargingBatteryPower'].magnitude
-    ),
-    unit: '(kWh)',
-    min: overviewData['/DischargingBatteryPower'].min,
-    max: overviewData['/ChargingBatteryPower'].max
-  };
-
-  chartOverview.dcPower = {
-    magnitude: Math.max(
-      overviewData['/ChargingBatteryPower'].magnitude,
-      overviewData['/HeatingPower'].magnitude,
-      overviewData['/DischargingBatteryPower'].magnitude
-    ),
-    unit: '(kWh)',
-    min: overviewData['/DischargingBatteryPower'].min,
-    max:
-      overviewData['/ChargingBatteryPower'].max +
-      overviewData['/HeatingPower'].max
-  };
-
-  path = '/GridImportPower';
-  chartAggregatedData.gridImportPower.data = data[path];
-
-  path = '/GridExportPower';
-  chartAggregatedData.gridExportPower.data = data[path];
-
-  chartOverview.gridPower = {
-    magnitude: Math.max(
-      overviewData['/GridImportPower'].magnitude,
-      overviewData['/GridExportPower'].magnitude
-    ),
-    unit: '(kWh)',
-    min: overviewData['/GridExportPower'].min,
-    max: overviewData['/GridImportPower'].max
-  };
-
-  chartOverview.overview = {
-    magnitude: 0,
-    unit: '(kWh)',
-    min: Math.min(
-      overviewData['/GridImportPower'].min,
-      overviewData['/GridExportPower'].min,
-      overviewData['/PvPower'].min
-    ),
-    max: Math.max(
-      overviewData['/GridImportPower'].max,
-      overviewData['/GridExportPower'].max,
-      overviewData['/PvPower'].max
-    )
-  };
-
-  return {
-    chartAggregatedData: chartAggregatedData,
-    chartOverview: chartOverview,
-    dateList: dateList
-  };
-};
+//
+// const fetchDataForOneTime = async (
+//   startUnixTime: UnixTime,
+//   s3Credentials: I_S3Credentials
+// ): Promise<FetchResult<Record<string, DataRecord>>> => {
+//   var timeperiodToSearch = 2;
+//   let res;
+//   let timestampToFetch;
+//
+//   for (var i = 0; i < timeperiodToSearch; i++) {
+//     timestampToFetch = startUnixTime.later(TimeSpan.fromSeconds(i));
+//
+//     try {
+//       res = await fetchData(timestampToFetch, s3Credentials);
+//
+//       if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
+//         //console.log('Successfully fetched ' + timestampToFetch);
+//         return res;
+//       }
+//     } catch (err) {
+//       console.error('Error fetching data:', err);
+//     }
+//   }
+//   return null;
+// };
+//
+// export const transformInputToDailyData = async (
+//   s3Credentials: I_S3Credentials,
+//   id: number,
+//   start_time?: UnixTime,
+//   end_time?: UnixTime
+// ): Promise<{
+//   chartData: chartDataInterface;
+//   chartOverview: overviewInterface;
+// }> => {
+//   //const navigate = useNavigate();
+//   //const tokencontext = useContext(TokenContext);
+//   //const { removeToken } = tokencontext;
+//   const prefixes = ['', 'k', 'M', 'G', 'T'];
+//   const MAX_NUMBER = 9999999;
+//   const pathsToSearch = [
+//     '/Battery/Soc',
+//     '/Battery/Temperature',
+//     '/Battery/Dc/Power',
+//     '/GridMeter/Ac/Power/Active',
+//     '/PvOnDc/Dc/Power',
+//     '/DcDc/Dc/Link/Voltage',
+//     '/LoadOnAcGrid/Power/Active',
+//     '/LoadOnDc/Power'
+//   ];
+//   const categories = [
+//     'soc',
+//     'temperature',
+//     'dcPower',
+//     'gridPower',
+//     'pvProduction',
+//     'dcBusVoltage',
+//     'ACLoad',
+//     'DCLoad'
+//   ];
+//
+//   const chartData: chartDataInterface = {
+//     soc: { name: 'State Of Charge', data: [] },
+//     temperature: { name: 'Battery Temperature', data: [] },
+//     dcPower: { name: 'Battery Power', data: [] },
+//     gridPower: { name: 'Grid Power', data: [] },
+//     pvProduction: { name: 'Pv Production', data: [] },
+//     dcBusVoltage: { name: 'DC Bus Voltage', data: [] },
+//     ACLoad: { name: 'AC Load', data: [] },
+//     DCLoad: { name: 'DC Load', data: [] }
+//   };
+//
+//   const chartOverview: overviewInterface = {
+//     soc: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     dcPower: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     dcPowerWithoutHeating: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     gridPower: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     overview: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     ACLoad: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     DCLoad: { magnitude: 0, unit: '', min: 0, max: 0 }
+//   };
+//
+//   categories.forEach((category) => {
+//     chartData[category].data = [];
+//     chartOverview[category] = {
+//       magnitude: 0,
+//       unit: '',
+//       min: MAX_NUMBER,
+//       max: -MAX_NUMBER
+//     };
+//   });
+//
+//   let timestampArray: number[] = [];
+//   let adjustedTimestampArray = [];
+//   const timestampPromises = [];
+//
+//   await axiosConfig
+//     .get(
+//       `/GetCsvTimestampsForInstallation?id=${id}&start=${start_time.ticks}&end=${end_time.ticks}`
+//     )
+//     .then((res: AxiosResponse<number[]>) => {
+//       timestampArray = res.data;
+//     })
+//     .catch((err: AxiosError) => {
+//       if (err.response && err.response.status == 401) {
+//         //removeToken();
+//         //navigate(routes.login);
+//       }
+//     });
+//
+//   for (var i = 0; i < timestampArray.length; i++) {
+//     timestampPromises.push(
+//       fetchDataForOneTime(UnixTime.fromTicks(timestampArray[i]), s3Credentials)
+//     );
+//
+//     const adjustedTimestamp = new Date(timestampArray[i] * 1000);
+//     //Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
+//     adjustedTimestamp.setHours(
+//       adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
+//     );
+//     adjustedTimestampArray.push(adjustedTimestamp);
+//   }
+//
+//   const results: Promise<FetchResult<Record<string, DataRecord>>>[] =
+//     await Promise.all(timestampPromises);
+//
+//   for (let i = 0; i < results.length; i++) {
+//     if (results[i] == null) {
+//       // Handle not available or try later case
+//     } else {
+//       const timestamp = Object.keys(results[i])[
+//         Object.keys(results[i]).length - 1
+//       ];
+//       const result = results[i][timestamp];
+//       let category_index = 0;
+//       // eslint-disable-next-line @typescript-eslint/no-loop-func
+//       pathsToSearch.forEach((path) => {
+//         if (result[path]) {
+//           const value = result[path];
+//
+//           if (value.value < chartOverview[categories[category_index]].min) {
+//             chartOverview[categories[category_index]].min = value.value;
+//           }
+//
+//           if (value.value > chartOverview[categories[category_index]].max) {
+//             chartOverview[categories[category_index]].max = value.value;
+//           }
+//           chartData[categories[category_index]].data.push([
+//             adjustedTimestampArray[i],
+//             value.value
+//           ]);
+//         } else {
+//           //data[path].push([adjustedTimestamp, null]);
+//         }
+//         category_index++;
+//       });
+//     }
+//   }
+//
+//   categories.forEach((category) => {
+//     let value = Math.max(
+//       Math.abs(chartOverview[category].max),
+//       Math.abs(chartOverview[category].min)
+//     );
+//     let magnitude = 0;
+//
+//     if (value < 0) {
+//       value = -value;
+//     }
+//     while (value >= 1000) {
+//       value /= 1000;
+//       magnitude++;
+//     }
+//     chartOverview[category].magnitude = magnitude;
+//   });
+//
+//   chartOverview.soc.unit = '(%)';
+//   chartOverview.soc.min = 0;
+//   chartOverview.soc.max = 100;
+//   chartOverview.temperature.unit = '(°C)';
+//   chartOverview.dcPower.unit =
+//     '(' + prefixes[chartOverview['dcPower'].magnitude] + 'W' + ')';
+//   chartOverview.gridPower.unit =
+//     '(' + prefixes[chartOverview['gridPower'].magnitude] + 'W' + ')';
+//   chartOverview.pvProduction.unit =
+//     '(' + prefixes[chartOverview['pvProduction'].magnitude] + 'W' + ')';
+//   chartOverview.dcBusVoltage.unit =
+//     '(' + prefixes[chartOverview['dcBusVoltage'].magnitude] + 'V' + ')';
+//
+//   chartOverview.overview = {
+//     magnitude: Math.max(
+//       chartOverview['gridPower'].magnitude,
+//       chartOverview['pvProduction'].magnitude
+//     ),
+//     unit: '(kW)',
+//     min: Math.min(
+//       chartOverview['gridPower'].min,
+//       chartOverview['pvProduction'].min
+//     ),
+//     max: Math.max(
+//       chartOverview['gridPower'].max,
+//       chartOverview['pvProduction'].max
+//     )
+//   };
+//
+//   return {
+//     chartData: chartData,
+//     chartOverview: chartOverview
+//   };
+// };
+//
+// export const transformInputToAggregatedData = async (
+//   s3Credentials: I_S3Credentials,
+//   start_date: dayjs.Dayjs,
+//   end_date: dayjs.Dayjs
+// ): Promise<{
+//   chartAggregatedData: chartAggregatedDataInterface;
+//   chartOverview: overviewInterface;
+//   dateList: string[];
+// }> => {
+//   const data = {};
+//   const overviewData = {};
+//   const MAX_NUMBER = 9999999;
+//   const dateList = [];
+//
+//   //let currentDate = dayjs();
+//   let currentDay = start_date;
+//   // type === 'weekly'
+//   //   ? currentDate.subtract(1, 'week')
+//   //   : currentDate.subtract(1, 'month');
+//
+//   const pathsToSearch = [
+//     '/MinSoc',
+//     '/MaxSoc',
+//     '/PvPower',
+//     '/DischargingBatteryPower',
+//     '/ChargingBatteryPower',
+//     '/GridImportPower',
+//     '/GridExportPower',
+//     '/HeatingPower'
+//   ];
+//
+//   const categories = [
+//     'minsoc',
+//     'maxsoc',
+//     'pvProduction',
+//     'dcChargingPower',
+//     'heatingPower',
+//     'dcDischargingPower',
+//     'gridImportPower',
+//     'gridExportPower'
+//   ];
+//
+//   const chartAggregatedData: chartAggregatedDataInterface = {
+//     minsoc: { name: 'min SOC', data: [] },
+//     maxsoc: { name: 'max SOC', data: [] },
+//     pvProduction: { name: 'Pv Energy', data: [] },
+//     dcChargingPower: { name: 'Charging Battery Energy', data: [] },
+//     heatingPower: { name: 'Heating Energy', data: [] },
+//     dcDischargingPower: { name: 'Discharging Battery Energy', data: [] },
+//     gridImportPower: { name: 'Grid Import Energy', data: [] },
+//     gridExportPower: { name: 'Grid Export Energy', data: [] }
+//   };
+//
+//   const chartOverview: overviewInterface = {
+//     soc: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     temperature: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     dcPower: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     dcPowerWithoutHeating: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     gridPower: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     overview: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     ACLoad: { magnitude: 0, unit: '', min: 0, max: 0 },
+//     DCLoad: { magnitude: 0, unit: '', min: 0, max: 0 }
+//   };
+//
+//   pathsToSearch.forEach((path) => {
+//     data[path] = [];
+//     overviewData[path] = {
+//       magnitude: 0,
+//       unit: '',
+//       min: MAX_NUMBER,
+//       max: -MAX_NUMBER
+//     };
+//   });
+//
+//   const timestampPromises = [];
+//
+//   while (currentDay.isBefore(end_date)) {
+//     timestampPromises.push(
+//       fetchAggregatedData(currentDay.format('YYYY-MM-DD'), s3Credentials)
+//     );
+//     currentDay = currentDay.add(1, 'day');
+//   }
+//
+//   const results = await Promise.all(timestampPromises);
+//   currentDay = start_date;
+//
+//   for (let i = 0; i < results.length; i++) {
+//     const result = results[i];
+//     if (
+//       result === FetchResult.notAvailable ||
+//       result === FetchResult.tryLater
+//     ) {
+//       // Handle not available or try later case
+//     } else {
+//       dateList.push(currentDay.format('DD-MM'));
+//       pathsToSearch.forEach((path) => {
+//         if (result[path]) {
+//           if (path === '/GridExportPower') {
+//             result[path].value = -result[path].value;
+//           }
+//           if (result[path].value < overviewData[path].min) {
+//             overviewData[path].min = result[path].value;
+//           }
+//           if (result[path].value > overviewData[path].max) {
+//             overviewData[path].max = result[path].value;
+//           }
+//           // if (path === '/GridExportPower' && Math.abs(result[path].value) < 0.1) {
+//           //   result[path].value = -0.3;
+//           // }
+//           data[path].push(result[path].value as number);
+//         }
+//       });
+//     }
+//     currentDay = currentDay.add(1, 'day');
+//   }
+//
+//   pathsToSearch.forEach((path) => {
+//     let value = Math.max(
+//       Math.abs(overviewData[path].max),
+//       Math.abs(overviewData[path].min)
+//     );
+//     let magnitude = 0;
+//
+//     if (value < 0) {
+//       value = -value;
+//     }
+//     while (value >= 1000) {
+//       value /= 1000;
+//       magnitude++;
+//     }
+//     overviewData[path].magnitude = magnitude;
+//   });
+//
+//   let path = '/MinSoc';
+//   chartAggregatedData.minsoc.data = data[path];
+//
+//   path = '/MaxSoc';
+//   chartAggregatedData.maxsoc.data = data[path];
+//
+//   chartOverview.soc = {
+//     unit: '(%)',
+//     magnitude: overviewData[path].magnitude,
+//     min: 0,
+//     max: 100
+//   };
+//
+//   path = '/PvPower';
+//   chartAggregatedData.pvProduction.data = data[path];
+//
+//   chartOverview.pvProduction = {
+//     magnitude: overviewData[path].magnitude,
+//     unit: '(kWh)',
+//     min: overviewData[path].min,
+//     max: overviewData[path].max
+//   };
+//
+//   path = '/ChargingBatteryPower';
+//   chartAggregatedData.dcChargingPower.data = data[path];
+//
+//   path = '/DischargingBatteryPower';
+//   chartAggregatedData.dcDischargingPower.data = data[path];
+//
+//   path = '/HeatingPower';
+//   chartAggregatedData.heatingPower.data = data[path];
+//
+//   chartOverview.dcPowerWithoutHeating = {
+//     magnitude: Math.max(
+//       overviewData['/ChargingBatteryPower'].magnitude,
+//       overviewData['/DischargingBatteryPower'].magnitude
+//     ),
+//     unit: '(kWh)',
+//     min: overviewData['/DischargingBatteryPower'].min,
+//     max: overviewData['/ChargingBatteryPower'].max
+//   };
+//
+//   chartOverview.dcPower = {
+//     magnitude: Math.max(
+//       overviewData['/ChargingBatteryPower'].magnitude,
+//       overviewData['/HeatingPower'].magnitude,
+//       overviewData['/DischargingBatteryPower'].magnitude
+//     ),
+//     unit: '(kWh)',
+//     min: overviewData['/DischargingBatteryPower'].min,
+//     max:
+//       overviewData['/ChargingBatteryPower'].max +
+//       overviewData['/HeatingPower'].max
+//   };
+//
+//   path = '/GridImportPower';
+//   chartAggregatedData.gridImportPower.data = data[path];
+//
+//   path = '/GridExportPower';
+//   chartAggregatedData.gridExportPower.data = data[path];
+//
+//   chartOverview.gridPower = {
+//     magnitude: Math.max(
+//       overviewData['/GridImportPower'].magnitude,
+//       overviewData['/GridExportPower'].magnitude
+//     ),
+//     unit: '(kWh)',
+//     min: overviewData['/GridExportPower'].min,
+//     max: overviewData['/GridImportPower'].max
+//   };
+//
+//   chartOverview.overview = {
+//     magnitude: 0,
+//     unit: '(kWh)',
+//     min: Math.min(
+//       overviewData['/GridImportPower'].min,
+//       overviewData['/GridExportPower'].min,
+//       overviewData['/PvPower'].min
+//     ),
+//     max: Math.max(
+//       overviewData['/GridImportPower'].max,
+//       overviewData['/GridExportPower'].max,
+//       overviewData['/PvPower'].max
+//     )
+//   };
+//
+//   return {
+//     chartAggregatedData: chartAggregatedData,
+//     chartOverview: chartOverview,
+//     dateList: dateList
+//   };
+// };