From 2aae717859d5b296f5a9404ccbf5ecf523666a03 Mon Sep 17 00:00:00 2001 From: Kim Date: Thu, 13 Jul 2023 10:18:34 +0200 Subject: [PATCH 1/5] refactoring --- csharp/App/Backend/DataTypes/Methods/Session.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/csharp/App/Backend/DataTypes/Methods/Session.cs b/csharp/App/Backend/DataTypes/Methods/Session.cs index 4856816cd..2c259ffc9 100644 --- a/csharp/App/Backend/DataTypes/Methods/Session.cs +++ b/csharp/App/Backend/DataTypes/Methods/Session.cs @@ -55,10 +55,9 @@ public static class SessionMethods var installation = Db.GetInstallationById(installationId); var parent = Db.GetFolderById(parentId); - if(installation.ParentId == parentId) return false; + if(installation == null || installation.ParentId == parentId) return false; return user is not null - && installation is not null && user.HasWriteAccess && user.HasAccessTo(installation) && user.HasAccessTo(parent) From 45631035729776af05c7e00f5f04a70a093501ce Mon Sep 17 00:00:00 2001 From: atef Date: Thu, 13 Jul 2023 10:49:17 +0200 Subject: [PATCH 2/5] Add console display in island mode and replace loginfo instead console.writeline --- csharp/App/SaliMax/src/Program.cs | 121 +++++++++++++++++++++--------- 1 file changed, 87 insertions(+), 34 deletions(-) diff --git a/csharp/App/SaliMax/src/Program.cs b/csharp/App/SaliMax/src/Program.cs index ed99a83da..d195a75d6 100644 --- a/csharp/App/SaliMax/src/Program.cs +++ b/csharp/App/SaliMax/src/Program.cs @@ -73,14 +73,14 @@ internal static class Program } catch (Exception e) { - Console.WriteLine(e); + e.LogError(); } } } private static async Task Run() { - Console.WriteLine("Starting SaliMax"); + "Starting SaliMax".LogInfo(); // Send the initial "service started" message to systemd var sdNotifyReturn = sd_notify(0, "READY=1"); @@ -99,25 +99,25 @@ internal static class Program StatusRecord ReadStatus() { - Console.WriteLine(" Reading AcDC"); + "Reading AcDC".LogInfo(); var acDc = acDcDevices.Read(); - Console.WriteLine(" Reading dcDc"); + "Reading dcDc".LogInfo(); var dcDc = dcDcDevices.Read(); - Console.WriteLine(" Reading battery"); + "Reading battery".LogInfo(); var battery = batteryDevices.Read(); - Console.WriteLine(" Reading relays"); + "Reading relays".LogInfo(); var relays = saliMaxRelaysDevice.Read(); - Console.WriteLine(" loadOnAcIsland"); + "Reading loadOnAcIsland".LogInfo(); var loadOnAcIsland = acIslandLoadMeter.Read(); - Console.WriteLine(" Reading gridMeter"); + "Reading gridMeter".LogInfo(); var gridMeter = gridMeterDevice.Read(); - Console.WriteLine(" Reading pvOnDc"); + "Reading pvOnDc".LogInfo(); var pvOnDc = amptDevice.Read(); var pvOnAcGrid = AcPowerDevice.Null; @@ -177,30 +177,35 @@ internal static class Program dcDcDevices.Write(r.DcDc); } - + const Int32 delayTime = 10; + Console.WriteLine("press ctrl-C to stop"); while (true) { sd_notify(0, "WATCHDOG=1"); - var t = UnixTime.FromTicks(UnixTime.Now.Ticks / 2 * 2); - - //t.ToUtcDateTime().WriteLine(); - + var t = UnixTime.Now; + while (t.Ticks % UpdateIntervalSeconds != 0) + { + await Task.Delay(delayTime); + t = UnixTime.Now; + } + + + var record = ReadStatus(); - PrintTopology(record); - if (record.Relays is not null) - record.Relays.ToCsv().WriteLine(); + record.Relays.ToCsv().LogInfo(); record.ControlConstants(); record.ControlSystemState(); - Console.WriteLine($"{record.StateMachine.State}: {record.StateMachine.Message}"); - - var essControl = record.ControlEss().WriteLine(); + (t + "\n").LogInfo(); + $"{record.StateMachine.State}: {record.StateMachine.Message}".LogInfo(); + $"Batteries SOC: {record.Battery.Soc}".LogInfo(); + var essControl = record.ControlEss().LogInfo(); record.EssControl = essControl; @@ -211,11 +216,13 @@ internal static class Program WriteControl(record); + PrintTopology(record); + await UploadCsv(record, t); record.Config.Save(); - "===========================================".WriteLine(); + "===========================================".LogInfo(); } // ReSharper disable once FunctionNeverReturns } @@ -234,13 +241,32 @@ internal static class Program var islandToGridBusPower = inverterPower + islandLoadPower; var gridLoadPower = s.LoadOnAcGrid is null ? 0: s.LoadOnAcGrid.Power.Active; - var gridPowerByPhase = TextBlock.AlignLeft(s.GridMeter.Ac.L1.Power.Active.ToDisplayString(), + TextBlock gridBusColumn; + TextBlock gridBox; + TextBlock totalBoxes; + + + if (s.GridMeter is not null) + { + var gridPowerByPhase = TextBlock.AlignLeft(s.GridMeter.Ac.L1.Power.Active.ToDisplayString(), s.GridMeter.Ac.L2.Power.Active.ToDisplayString(), s.GridMeter.Ac.L3.Power.Active.ToDisplayString()); + + var gridVoltageByPhase = TextBlock.AlignLeft(s.GridMeter.Ac.L1.Voltage.ToDisplayString(), + s.GridMeter.Ac.L2.Voltage.ToDisplayString(), + s.GridMeter.Ac.L3.Voltage.ToDisplayString()); + + gridBusColumn = ColumnBox("Pv", "Grid Bus", "Load" , gridVoltageByPhase , gridLoadPower); + gridBox = TextBlock.AlignLeft(gridPowerByPhase).TitleBox("Grid"); + + } + else + { + gridBusColumn = TextBlock.Spacer(0); + gridBox = TextBlock.Spacer(0); + } + - var gridVoltageByPhase = TextBlock.AlignLeft(s.GridMeter.Ac.L1.Voltage.ToDisplayString(), - s.GridMeter.Ac.L2.Voltage.ToDisplayString(), - s.GridMeter.Ac.L3.Voltage.ToDisplayString()); var inverterPowerByPhase = TextBlock.AlignLeft(s.AcDc.Ac.L1.Power.Active.ToDisplayString(), s.AcDc.Ac.L2.Power.Active.ToDisplayString(), @@ -264,7 +290,6 @@ internal static class Program var anyBatteryAlarm = s.Battery.Alarms.Any(); var anyBatteryWarning = s.Battery.Warnings.Any(); - var gridBusColumn = ColumnBox("Pv", "Grid Bus", "Load" , gridVoltageByPhase , gridLoadPower); var islandBusColumn = ColumnBox("Pv", "Island Bus", "Load" , inverterPowerByPhase, islandLoadPower); var dcBusColumn = ColumnBox("Pv", "Dc Bus", "Load" , dcLinkVoltage, 0, pvOnDcPower); var gridBusFlow = Flow.Horizontal(gridPower); @@ -274,8 +299,7 @@ internal static class Program var flowDcBusToDcDc = Flow.Horizontal(dcdcPower); var flowDcDcToBattery = Flow.Horizontal(dcBatteryPower); - var gridBox = TextBlock.AlignLeft(gridPowerByPhase).TitleBox("Grid"); - var inverterBox = TextBlock.AlignLeft(inverterPowerByAcDc).TitleBox("Inverter"); + var inverterBox = TextBlock.AlignLeft(inverterPowerByAcDc).TitleBox("AC/DC"); var dcDcBox = TextBlock.AlignLeft(dc48Voltage).TitleBox("DC/DC"); var batteryAvgBox = TextBlock.AlignLeft(batteryVoltage, batterySoc, @@ -297,11 +321,27 @@ internal static class Program var individualBatteries = batteryBoxes.Any() ? TextBlock.AlignLeft(batteryBoxes) : TextBlock.Spacer(1); - - var totalBoxes = TextBlock.AlignCenterVertical(gridBox, - gridBusFlow, - gridBusColumn, - flowGridBusToIslandBus, + + if (s.GridMeter is not null) + { + totalBoxes = TextBlock.AlignCenterVertical(gridBox, + gridBusFlow, + gridBusColumn, + flowGridBusToIslandBus, + islandBusColumn, + flowIslandBusToInverter, + inverterBox, + flowInverterToDcBus, + dcBusColumn, + flowDcBusToDcDc, + dcDcBox, + flowDcDcToBattery, + batteryAvgBox, + individualBatteries); + } + else + { + totalBoxes = TextBlock.AlignCenterVertical( islandBusColumn, flowIslandBusToInverter, inverterBox, @@ -312,6 +352,8 @@ internal static class Program flowDcDcToBattery, batteryAvgBox, individualBatteries); + } + totalBoxes.WriteLine(); } @@ -363,10 +405,18 @@ internal static class Program private static void ControlConstants(this StatusRecord r) { var inverters = r.AcDc.Devices; + var dcDevices = r.DcDc.Devices; inverters.ForEach(d => d.Control.Dc.MaxVoltage = r.Config.MaxDcBusVoltage); inverters.ForEach(d => d.Control.Dc.MinVoltage = r.Config.MinDcBusVoltage); inverters.ForEach(d => d.Control.Dc.ReferenceVoltage = r.Config.ReferenceDcBusVoltage); + + // dcDevices.ForEach(d => d.Control. Dc.MaxVoltage = r.Config.MaxDcBusVoltage); + // dcDevices.ForEach(d => d.Control. Dc.MinVoltage = r.Config.MinDcBusVoltage); + // dcDevices.ForEach(d => d.Control. Dc.ReferenceVoltage = r.Config.ReferenceDcBusVoltage); + + r.DcDc.ResetAlarms(); + r.AcDc.ResetAlarms(); } @@ -408,11 +458,13 @@ internal static class Program 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; @@ -427,7 +479,8 @@ internal static class Program sc.UseSlaveIdForAddressing = true; sc.SlaveErrorHandling = SlaveErrorHandling.Relaxed; sc.SubSlaveErrorHandling = SubSlaveErrorHandling.Off; - sc.ResetAlarmsAndWarnings = true; + + sc.ResetAlarmsAndWarnings = true; } private static async Task UploadCsv(StatusRecord status, UnixTime timeStamp) From 8db786f965a0de3fd9ef7930f9d00cf9c30f731e Mon Sep 17 00:00:00 2001 From: Kim Date: Thu, 13 Jul 2023 13:23:05 +0200 Subject: [PATCH 3/5] In-Memory Database with automatic versioning Backups. When starting the server the newest backup is loaded into memory. --- csharp/App/Backend/Backend.csproj | 1 + csharp/App/Backend/Controller.cs | 1 - csharp/App/Backend/Database/Create.cs | 27 ++++--- csharp/App/Backend/Database/Db.cs | 77 ++++++++++++++++++- csharp/App/Backend/Database/Delete.cs | 19 +++-- csharp/App/Backend/Database/Update.cs | 13 +++- csharp/App/Backend/{ => DbBackups}/db.sqlite | Bin 122880 -> 122880 bytes 7 files changed, 117 insertions(+), 21 deletions(-) rename csharp/App/Backend/{ => DbBackups}/db.sqlite (99%) diff --git a/csharp/App/Backend/Backend.csproj b/csharp/App/Backend/Backend.csproj index 6959e506d..bf230bd4e 100644 --- a/csharp/App/Backend/Backend.csproj +++ b/csharp/App/Backend/Backend.csproj @@ -27,6 +27,7 @@ + diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index f3e80527e..74b45cb9a 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -20,7 +20,6 @@ public class Controller : ControllerBase if (user is null) { - Console.WriteLine("I have no user"); throw new Exceptions(400,"Null User Exception", "Must provide a user to log in as.", Request.Path.Value!); } diff --git a/csharp/App/Backend/Database/Create.cs b/csharp/App/Backend/Database/Create.cs index 95849acaa..24944ba2b 100644 --- a/csharp/App/Backend/Database/Create.cs +++ b/csharp/App/Backend/Database/Create.cs @@ -7,54 +7,61 @@ namespace InnovEnergy.App.Backend.Database; public static partial class Db { + private static Boolean Insert(Object obj) + { + var success = Connection.Insert(obj) > 0; + if(success) BackupDatabase(); + return success; + } + 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; + return Insert(installation); } public static Boolean Create(DeletedInstallation installation) { - return Connection.Insert(installation) > 0; + return Insert(installation); } public static Boolean Create(Folder folder) { - return Connection.Insert(folder) > 0; + return Insert(folder); } public static Boolean Create(DeletedFolder folder) { - return Connection.Insert(folder) > 0; + return Insert(folder); } public static Boolean Create(User user) { - return Connection.Insert(user) > 0; + return Insert(user); } public static Boolean Create(DeletedUser user) { - return Connection.Insert(user) > 0; + return Insert(user); } public static Boolean Create(Session session) { - return Connection.Insert(session) > 0; + return Insert(session); } public static Boolean Create(InstallationAccess installationAccess) { - return Connection.Insert(installationAccess) > 0; + return Insert(installationAccess); } public static Boolean Create(FolderAccess folderAccess) { - return Connection.Insert(folderAccess) > 0; + return Insert(folderAccess); } public static Boolean Create(OrderNumber2Installation o2i) { - return Connection.Insert(o2i) > 0; + return Insert(o2i); } } \ No newline at end of file diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs index ea82f6788..a3918d2cb 100644 --- a/csharp/App/Backend/Database/Db.cs +++ b/csharp/App/Backend/Database/Db.cs @@ -1,12 +1,16 @@ +using System.Data.SQLite; using System.Reactive.Concurrency; using System.Reactive.Linq; +using System.Runtime.InteropServices; using CliWrap; using CliWrap.Buffered; using InnovEnergy.App.Backend.DataTypes; using InnovEnergy.App.Backend.DataTypes.Methods; using InnovEnergy.App.Backend.Relations; using InnovEnergy.Lib.Utils; +using Microsoft.Identity.Client; using SQLite; +using SQLiteConnection = SQLite.SQLiteConnection; namespace InnovEnergy.App.Backend.Database; @@ -16,8 +20,79 @@ public static partial class Db { internal const String DbPath = "./db.sqlite"; - private static SQLiteConnection Connection { get; } = new SQLiteConnection(DbPath); + private static SQLiteConnection Connection { get; } = ((Func)(() => + { + var latestDb = new DirectoryInfo(@"DbBackups").GetFiles() + .OrderBy(f => f.LastWriteTime) + .First().Name; + var fileConnection = new SQLiteConnection("DbBackups/"+latestDb); + + var memoryConnection = new SQLiteConnection(":memory:"); + + // fileConnection.Backup(memoryConnection.DatabasePath); + + memoryConnection.CreateTable(); + memoryConnection.CreateTable(); + memoryConnection.CreateTable(); + memoryConnection.CreateTable(); + memoryConnection.CreateTable(); + memoryConnection.CreateTable(); + memoryConnection.CreateTable(); + memoryConnection.CreateTable(); + memoryConnection.CreateTable(); + memoryConnection.CreateTable(); + + foreach (var obj in fileConnection.Table()) + { + memoryConnection.Insert(obj); + } + foreach (var obj in fileConnection.Table()) + { + memoryConnection.Insert(obj); + } + foreach (var obj in fileConnection.Table()) + { + memoryConnection.Insert(obj); + } + foreach (var obj in fileConnection.Table()) + { + memoryConnection.Insert(obj); + } + foreach (var obj in fileConnection.Table()) + { + memoryConnection.Insert(obj); + } + foreach (var obj in fileConnection.Table()) + { + memoryConnection.Insert(obj); + } + foreach (var obj in fileConnection.Table()) + { + memoryConnection.Insert(obj); + } + foreach (var obj in fileConnection.Table()) + { + memoryConnection.Insert(obj); + } + foreach (var obj in fileConnection.Table()) + { + memoryConnection.Insert(obj); + } + foreach (var obj in fileConnection.Table()) + { + memoryConnection.Insert(obj); + } + + return memoryConnection; + }))(); + + public static void BackupDatabase() + { + var filename = "db-" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + ".sqlite"; + Connection.Backup("DbBackups/"+filename); + } + public static TableQuery Sessions => Connection.Table(); public static TableQuery Folders => Connection.Table(); public static TableQuery Installations => Connection.Table(); diff --git a/csharp/App/Backend/Database/Delete.cs b/csharp/App/Backend/Database/Delete.cs index 200b63855..a99229584 100644 --- a/csharp/App/Backend/Database/Delete.cs +++ b/csharp/App/Backend/Database/Delete.cs @@ -22,9 +22,10 @@ public static partial class Db Boolean DeleteDescendantFolderAndItsDependencies(Folder f) { FolderAccess .Delete(r => r.FolderId == f.Id); - Installations.Delete(r => r.ParentId == f.Id); - - return Folders.Delete(r => r.Id == f.Id) > 0; + Installations.Delete(r => r.ParentId == f.Id); + var delete = Folders.Delete(r => r.Id == f.Id); + BackupDatabase(); + return delete > 0; } } @@ -35,7 +36,9 @@ public static partial class Db Boolean DeleteInstallationAndItsDependencies() { InstallationAccess.Delete(i => i.InstallationId == installation.Id); - return Installations.Delete(i => i.Id == installation.Id) > 0; + var delete = Installations.Delete(i => i.Id == installation.Id); + BackupDatabase(); + return delete > 0; } } @@ -48,7 +51,9 @@ public static partial class Db FolderAccess .Delete(u => u.UserId == user.Id); InstallationAccess.Delete(u => u.UserId == user.Id); - return Users.Delete(u => u.Id == user.Id) > 0; + var delete = Users.Delete(u => u.Id == user.Id); + BackupDatabase(); + return delete > 0; } } @@ -58,6 +63,8 @@ public static partial class Db // private!! private static Boolean Delete(Session session) { - return Sessions.Delete(s => s.Id == session.Id) > 0; + var delete = Sessions.Delete(s => s.Id == session.Id) > 0; + BackupDatabase(); + return delete; } } \ No newline at end of file diff --git a/csharp/App/Backend/Database/Update.cs b/csharp/App/Backend/Database/Update.cs index a90749a45..094487552 100644 --- a/csharp/App/Backend/Database/Update.cs +++ b/csharp/App/Backend/Database/Update.cs @@ -5,14 +5,21 @@ namespace InnovEnergy.App.Backend.Database; public static partial class Db { + private static Boolean Update(Object obj) + { + var success = Connection.Update(obj) > 0; + if(success) BackupDatabase(); + return success; + } + public static Boolean Update(Folder folder) { - return Connection.Update(folder) > 0; + return Update(obj: folder); } public static Boolean Update(Installation installation) { - return Connection.Update(installation) > 0; + return Update(obj: installation); } public static Boolean Update(User user) @@ -24,7 +31,7 @@ public static partial class Db user.ParentId = originalUser.ParentId; user.Name = originalUser.Name; - return Connection.Update(user) > 0; + return Update(obj: user); } diff --git a/csharp/App/Backend/db.sqlite b/csharp/App/Backend/DbBackups/db.sqlite similarity index 99% rename from csharp/App/Backend/db.sqlite rename to csharp/App/Backend/DbBackups/db.sqlite index 9a363a376558e36a632523bcdff07de64a7ffe59..55d2bbbeb72b0663fdd6c765f6ff16c0bae667df 100644 GIT binary patch delta 73 zcmZoTz}|3xeS$Qj&qNt#MxTud%knuMGw|-jJ1salK@YK7KQ)- delta 63 zcmV-F0Kor%zz2Z92ap>9OOYHy0ZXx9rf&%301dbfmJH;x5fIi45P?wx5g!#J2nY=$ V8AD?LBa_H|FoPt0w Date: Thu, 13 Jul 2023 16:17:58 +0200 Subject: [PATCH 4/5] Added S3Explorer a cmdline tool to grab and display data from our s3 buckets --- csharp/App/Backend/Resources/s3AdminKey.json | 4 + csharp/App/Backend/S3/S3Access.cs | 6 +- csharp/App/Backend/S3/S3Cmd.cs | 15 +- csharp/App/S3Explorer/Program.cs | 79 ++++++ csharp/App/S3Explorer/S3Explorer.csproj | 14 + csharp/App/S3Explorer/SnakeGameSS.cs | 283 +++++++++++++++++++ csharp/InnovEnergy.sln | 7 + 7 files changed, 402 insertions(+), 6 deletions(-) create mode 100644 csharp/App/Backend/Resources/s3AdminKey.json create mode 100644 csharp/App/S3Explorer/Program.cs create mode 100644 csharp/App/S3Explorer/S3Explorer.csproj create mode 100644 csharp/App/S3Explorer/SnakeGameSS.cs diff --git a/csharp/App/Backend/Resources/s3AdminKey.json b/csharp/App/Backend/Resources/s3AdminKey.json new file mode 100644 index 000000000..0cacecb5b --- /dev/null +++ b/csharp/App/Backend/Resources/s3AdminKey.json @@ -0,0 +1,4 @@ +{ + "Key": "EXO1abcb772bf43ab72951ba1dc", + "Secret": "_ym1KsGBSp90S5dwhZn18XD-u9Y4ghHvyIxg5gv5fHw" +} \ No newline at end of file diff --git a/csharp/App/Backend/S3/S3Access.cs b/csharp/App/Backend/S3/S3Access.cs index d509ae460..03dcfb4c7 100644 --- a/csharp/App/Backend/S3/S3Access.cs +++ b/csharp/App/Backend/S3/S3Access.cs @@ -9,9 +9,5 @@ public static class S3Access { public static S3Cmd ReadOnly => Deserialize(OpenRead("./Resources/s3ReadOnlyKey.json"))!; public static S3Cmd ReadWrite => Deserialize(OpenRead("./Resources/s3ReadWriteKey.json"))!; - - public static async Task CreateKey(String bucketName) - { - throw new NotImplementedException(); - } + public static S3Cmd Admin => Deserialize(OpenRead("./Resources/s3AdminKey.json"))!; } \ No newline at end of file diff --git a/csharp/App/Backend/S3/S3Cmd.cs b/csharp/App/Backend/S3/S3Cmd.cs index 6cd4a8f6e..40c32fa09 100644 --- a/csharp/App/Backend/S3/S3Cmd.cs +++ b/csharp/App/Backend/S3/S3Cmd.cs @@ -57,11 +57,23 @@ public class S3Cmd "; var result = await Run(bucketName, "mb"); - var setCors = await Run(bucketName, "PutBucketCors", cors); + var setCors = await Run(bucketName, "setcors", cors); return result.ExitCode == 0 && setCors.ExitCode == 0; } + public async Task ListFilesInBucket(String bucketName) + { + var result = await Run(bucketName, "ls"); + return result.StandardOutput; + } + + public async Task GetFileText(String bucketName, String filename) + { + var result = await Run(bucketName + "/" + filename, "get", "--force"); + return File.ReadAllLines("./" + filename); + } + public async Task DeleteBucket(String bucketName) { var result = await Run(bucketName, "rb"); @@ -86,4 +98,5 @@ public class S3Cmd .WithArguments(args) .ExecuteBufferedAsync(); } + } \ No newline at end of file diff --git a/csharp/App/S3Explorer/Program.cs b/csharp/App/S3Explorer/Program.cs new file mode 100644 index 000000000..a00764ff8 --- /dev/null +++ b/csharp/App/S3Explorer/Program.cs @@ -0,0 +1,79 @@ +๏ปฟusing InnovEnergy.App.Backend.S3; +using InnovEnergy.Lib.Utils; + +namespace S3Explorer; + +public static class Program +{ + + public static async Task Main(String[] args) + { + if (args.Contains("-s")) + { + await SnakeGameSs.PlaySnake(); + } + + if (args.Length < 4 || args.Contains("-h")) + { + Console.WriteLine("To use: $S3Explorer [BucketId] [from:Unix-time] [to:Unix-time] [#Data-points]"); + Console.WriteLine("-h shows this message"); + Console.WriteLine("-s ๐Ÿ"); + return 0; + } + + + Console.WriteLine(""); + + var bucketName = args[0] + "-3e5b3069-214a-43ee-8d85-57d72000c19d"; + var fromTime = Int64.Parse(args[1]); + var toTime = Int64.Parse(args[2]); + var numberOfDataPoints = Int64.Parse(args[3]); + + var time = toTime - fromTime; + var timeBetweenDataPoints = time / numberOfDataPoints; + timeBetweenDataPoints = Math.Max(timeBetweenDataPoints, 2); + + // var fileList = ListAllFileNamesInBucket(bucketName); + var timestampList = new List { }; + + for (var i = fromTime; i <= toTime; i += timeBetweenDataPoints) + { + timestampList.Add((i/2 *2).ToString()); + } + await GrabFiles(bucketName,timestampList); + + return 0; + } + + private static async Task GrabFiles(String bucketName, List timestampList) + { + var last = timestampList.Last(); + var fileCsv = await S3Access.Admin.GetFileText(bucketName, last + ".csv"); + var dataKeys = fileCsv + .Select(l => l.Split(";")) + .Select(l => l[0]) + .Prepend("Timestamp") + .JoinWith(";") + .WriteLine(); + + foreach (var timestamp in timestampList) + { + + fileCsv = await S3Access.Admin.GetFileText(bucketName,timestamp + ".csv"); + + fileCsv.Select(l => l.Split(";")) + .Select(l => l[1]) + .Prepend(timestamp) + .JoinWith(";") + .WriteLine(); + + // Parse csv, build csv + } + } + + private static List ListAllFileNamesInBucket(String bucketName) + { + // Todo refactor S3Access into Lib + return S3Access.Admin.ListFilesInBucket(bucketName).Result.Split(',').ToList(); + } +} \ No newline at end of file diff --git a/csharp/App/S3Explorer/S3Explorer.csproj b/csharp/App/S3Explorer/S3Explorer.csproj new file mode 100644 index 000000000..3c32de060 --- /dev/null +++ b/csharp/App/S3Explorer/S3Explorer.csproj @@ -0,0 +1,14 @@ + + + + Exe + net7.0 + enable + enable + + + + + + + diff --git a/csharp/App/S3Explorer/SnakeGameSS.cs b/csharp/App/S3Explorer/SnakeGameSS.cs new file mode 100644 index 000000000..60d5eb253 --- /dev/null +++ b/csharp/App/S3Explorer/SnakeGameSS.cs @@ -0,0 +1,283 @@ +namespace S3Explorer; + +public static class SnakeGameSs +{ + public static async Task PlaySnake() + { + var tickRate = TimeSpan.FromMilliseconds(100); + 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 _body; + private int _growthSpurtsRemaining; + + public Snake(Position spawnLocation, int initialSize = 1) + { + _body = new List { spawnLocation }; + _growthSpurtsRemaining = Math.Max(0, initialSize - 1); + Dead = false; + } + + public bool Dead { get; private set; } + public Position Head => _body.First(); + private IEnumerable 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(0, numberOfRows + 1); + var left = random.Next(0, numberOfColumns + 1); + var position = new Position(top, left); + var apple = new Apple(position); + + return apple; + } + } +} \ No newline at end of file diff --git a/csharp/InnovEnergy.sln b/csharp/InnovEnergy.sln index beaac246d..735ef04c7 100644 --- a/csharp/InnovEnergy.sln +++ b/csharp/InnovEnergy.sln @@ -77,6 +77,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adam6360D", "Lib\Devices\Ad EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IEM3kGridMeter", "Lib\Devices\IEM3kGridMeter\IEM3kGridMeter.csproj", "{1391165D-51F1-45B4-8B7F-042A20AA0277}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Explorer", "App\S3Explorer\S3Explorer.csproj", "{EB56EF94-D8A7-4111-A8E7-A87EF13596DA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -196,6 +198,10 @@ Global {1391165D-51F1-45B4-8B7F-042A20AA0277}.Debug|Any CPU.Build.0 = Debug|Any CPU {1391165D-51F1-45B4-8B7F-042A20AA0277}.Release|Any CPU.ActiveCfg = Release|Any CPU {1391165D-51F1-45B4-8B7F-042A20AA0277}.Release|Any CPU.Build.0 = Release|Any CPU + {EB56EF94-D8A7-4111-A8E7-A87EF13596DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB56EF94-D8A7-4111-A8E7-A87EF13596DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB56EF94-D8A7-4111-A8E7-A87EF13596DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB56EF94-D8A7-4111-A8E7-A87EF13596DA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {CF4834CB-91B7-4172-AC13-ECDA8613CD17} = {145597B4-3E30-45E6-9F72-4DD43194539A} @@ -230,5 +236,6 @@ Global {1A56992B-CB72-490F-99A4-DF1186BA3A18} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} {A3C79247-4CAA-44BE-921E-7285AB39E71F} = {4931A385-24DC-4E78-BFF4-356F8D6D5183} {1391165D-51F1-45B4-8B7F-042A20AA0277} = {4931A385-24DC-4E78-BFF4-356F8D6D5183} + {EB56EF94-D8A7-4111-A8E7-A87EF13596DA} = {145597B4-3E30-45E6-9F72-4DD43194539A} EndGlobalSection EndGlobal From 9777771c7f800c0dd2819ae486caca1d41889585 Mon Sep 17 00:00:00 2001 From: Kim Date: Thu, 13 Jul 2023 16:37:52 +0200 Subject: [PATCH 5/5] Refactoring S3Explorer --- csharp/App/S3Explorer/Program.cs | 73 +++++++++++++++++++------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/csharp/App/S3Explorer/Program.cs b/csharp/App/S3Explorer/Program.cs index a00764ff8..69c9d8890 100644 --- a/csharp/App/S3Explorer/Program.cs +++ b/csharp/App/S3Explorer/Program.cs @@ -8,48 +8,52 @@ public static class Program public static async Task Main(String[] args) { + // Todo refactor S3Access into Lib + + // Sssssecret if (args.Contains("-s")) { await SnakeGameSs.PlaySnake(); } + // Help message if (args.Length < 4 || args.Contains("-h")) { - Console.WriteLine("To use: $S3Explorer [BucketId] [from:Unix-time] [to:Unix-time] [#Data-points]"); - Console.WriteLine("-h shows this message"); + Console.WriteLine("Usage: S3Explorer [BucketId] [from:Unix-time] [to:Unix-time] [#Data-points]"); + Console.WriteLine("-h Shows this message."); Console.WriteLine("-s ๐Ÿ"); return 0; } - - - Console.WriteLine(""); + // Parsing Arguments var bucketName = args[0] + "-3e5b3069-214a-43ee-8d85-57d72000c19d"; - var fromTime = Int64.Parse(args[1]); - var toTime = Int64.Parse(args[2]); + var startTime = Int64.Parse(args[1]); + var endTime = Int64.Parse(args[2]); var numberOfDataPoints = Int64.Parse(args[3]); - - var time = toTime - fromTime; - var timeBetweenDataPoints = time / numberOfDataPoints; - timeBetweenDataPoints = Math.Max(timeBetweenDataPoints, 2); - // var fileList = ListAllFileNamesInBucket(bucketName); - var timestampList = new List { }; + var timeBetweenDataPoints = TimeBetweenDataPoints(startTime, endTime, numberOfDataPoints); - for (var i = fromTime; i <= toTime; i += timeBetweenDataPoints) + // Building a List of the timestamps we want to grab the files for. + var timestampList = new List { }; + for (var i = startTime; i <= endTime; i += timeBetweenDataPoints) { + //Rounding to even numbers only (we only save every second second) timestampList.Add((i/2 *2).ToString()); } - await GrabFiles(bucketName,timestampList); + + await PrintFiles(bucketName,timestampList); + // Success return 0; } - private static async Task GrabFiles(String bucketName, List timestampList) + private static async Task PrintFiles(String bucketName, List timestampList) { - var last = timestampList.Last(); - var fileCsv = await S3Access.Admin.GetFileText(bucketName, last + ".csv"); - var dataKeys = fileCsv + var newestDataFilename = timestampList.Last(); + var csvFileText = await GetFileText(bucketName, newestDataFilename); + + // Building Header-Row from the newest data + csvFileText .Select(l => l.Split(";")) .Select(l => l[0]) .Prepend("Timestamp") @@ -58,22 +62,33 @@ public static class Program foreach (var timestamp in timestampList) { - - fileCsv = await S3Access.Admin.GetFileText(bucketName,timestamp + ".csv"); - - fileCsv.Select(l => l.Split(";")) + csvFileText = await GetFileText(bucketName, timestamp); + + // Writing Data below data-keys in a timestamped row + csvFileText.Select(l => l.Split(";")) .Select(l => l[1]) .Prepend(timestamp) .JoinWith(";") .WriteLine(); - - // Parse csv, build csv } - } - private static List ListAllFileNamesInBucket(String bucketName) + } + + private static Int64 TimeBetweenDataPoints(Int64 startTime, Int64 endTime, Int64 numberOfDataPoints) { - // Todo refactor S3Access into Lib - return S3Access.Admin.ListFilesInBucket(bucketName).Result.Split(',').ToList(); + // Calculating temporal distance of data files from the number of requested points. + var timeSpan = endTime - startTime; + var timeBetweenDataPoints = timeSpan / numberOfDataPoints; + + // We only upload data every second second so sampling more is impossible. + // If this ever changes we might have to change this as well. + timeBetweenDataPoints = Math.Max(timeBetweenDataPoints, 2); + return timeBetweenDataPoints; + } + + // This Method extracts the Text from a given csv file on the s3 bucket + private static async Task GetFileText(String bucketName, String filename) + { + return await S3Access.Admin.GetFileText(bucketName, filename + ".csv"); } } \ No newline at end of file