diff --git a/csharp/InnovEnergy.sln b/csharp/InnovEnergy.sln index 9247b7144..69f33a105 100644 --- a/csharp/InnovEnergy.sln +++ b/csharp/InnovEnergy.sln @@ -24,8 +24,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3", "lib/S3/S3.csproj", "{ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "deprecated", "deprecated", "{46DE03C4-52D1-47AA-8E60-8BB15361D723}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CsController", "app/CsController/CsController.csproj", "{72DBBE42-A09F-43C0-9613-331039857056}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SaliMax", "app/SaliMax/SaliMax.csproj", "{25073794-D859-4824-9984-194C7E928496}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatusApi", "lib/StatusApi/StatusApi.csproj", "{9D17E78C-8A70-43DB-A619-DC12D20D023D}" @@ -112,10 +110,6 @@ Global {C3639841-13F4-4F24-99C6-7D965593BF89}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3639841-13F4-4F24-99C6-7D965593BF89}.Release|Any CPU.ActiveCfg = Release|Any CPU {C3639841-13F4-4F24-99C6-7D965593BF89}.Release|Any CPU.Build.0 = Release|Any CPU - {72DBBE42-A09F-43C0-9613-331039857056}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {72DBBE42-A09F-43C0-9613-331039857056}.Debug|Any CPU.Build.0 = Debug|Any CPU - {72DBBE42-A09F-43C0-9613-331039857056}.Release|Any CPU.ActiveCfg = Release|Any CPU - {72DBBE42-A09F-43C0-9613-331039857056}.Release|Any CPU.Build.0 = Release|Any CPU {25073794-D859-4824-9984-194C7E928496}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {25073794-D859-4824-9984-194C7E928496}.Debug|Any CPU.Build.0 = Debug|Any CPU {25073794-D859-4824-9984-194C7E928496}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -195,7 +189,6 @@ Global {E3A5F3A3-72A5-47CC-85C6-2D8E962A0EC1} = {145597B4-3E30-45E6-9F72-4DD43194539A} {46DE03C4-52D1-47AA-8E60-8BB15361D723} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} {4A67D79F-F0C9-4BBC-9601-D5948E6C05D3} = {46DE03C4-52D1-47AA-8E60-8BB15361D723} - {72DBBE42-A09F-43C0-9613-331039857056} = {145597B4-3E30-45E6-9F72-4DD43194539A} {25073794-D859-4824-9984-194C7E928496} = {145597B4-3E30-45E6-9F72-4DD43194539A} {9D17E78C-8A70-43DB-A619-DC12D20D023D} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} {C3639841-13F4-4F24-99C6-7D965593BF89} = {46DE03C4-52D1-47AA-8E60-8BB15361D723} diff --git a/csharp/app/Backend/Backend.csproj b/csharp/app/Backend/Backend.csproj index c082226db..e6a136cb1 100644 --- a/csharp/app/Backend/Backend.csproj +++ b/csharp/app/Backend/Backend.csproj @@ -4,6 +4,8 @@ net6.0 enable enable + preview + Innovenergy.Backend diff --git a/csharp/app/Backend/Controllers/Controller.cs b/csharp/app/Backend/Controllers/Controller.cs index 32b53084e..10110397e 100644 --- a/csharp/app/Backend/Controllers/Controller.cs +++ b/csharp/app/Backend/Controllers/Controller.cs @@ -1,21 +1,21 @@ using System.Net; using System.Text; -using System.Text.Json; -using Backend.Database; -using Backend.Model; -using Backend.Model.Relations; -using Backend.Utils; +using Innovenergy.Backend.Database; +using Innovenergy.Backend.Model; +using Innovenergy.Backend.Model.Relations; +using Innovenergy.Backend.Utils; using Microsoft.AspNetCore.Mvc; using HttpContextAccessor = Microsoft.AspNetCore.Http.HttpContextAccessor; -namespace Backend.Controllers; +namespace Innovenergy.Backend.Controllers; [ApiController] [Route("api/")] public class Controller { - [ProducesResponseType(200)] - [ProducesResponseType(401)] + [Returns] + [Returns(HttpStatusCode.Unauthorized)] + [Returns(HttpStatusCode.BadRequest)] [HttpPost($"{nameof(Login)}")] public Object Login(Credentials credentials) { @@ -29,181 +29,175 @@ public class Controller if (user is null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); - // if (!VerifyPassword(password, user)) - // return new HttpResponseMessage(HttpStatusCode.Unauthorized); + #if !DEBUG + if (!VerifyPassword(credentials.Password, user)) + return new HttpResponseMessage(HttpStatusCode.Unauthorized); + #endif var ses = new Session(user); db.NewSession(ses); return ses.Token; } - private static Boolean VerifyPassword(String password, User user) - { - var pwdBytes = Encoding.UTF8.GetBytes(password); - var saltBytes = Encoding.UTF8.GetBytes(user.Salt + "innovEnergy"); - var pwdHash = Crypto.ComputeHash(pwdBytes, saltBytes); - return user.Password == pwdHash; - } - - [ProducesResponseType(200)] - [ProducesResponseType(401)] + [Returns(HttpStatusCode.OK)] + [Returns(HttpStatusCode.Unauthorized)] [HttpPost($"{nameof(Logout)}")] public Object Logout() { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; - using var db = Db.Connect(); - var currentUser = (User)ctx.Items["User"]; + var caller = GetCaller(); - if (currentUser is null) - return new HttpResponseMessage(HttpStatusCode.Conflict); + if (caller is null) + return new HttpResponseMessage(HttpStatusCode.Unauthorized); - return db.DeleteSession(currentUser.Id); + using var db = Db.Connect(); + return db.DeleteSession(caller.Id); } - [ProducesResponseType(200)] - [ProducesResponseType(401)] - [HttpPost($"{nameof(UpdateS3Creds)}")] - public Object UpdateS3Creds() + + [Returns(HttpStatusCode.OK)] + [Returns(HttpStatusCode.Unauthorized)] + [HttpPost($"{nameof(UpdateS3Credentials)}")] + public Object UpdateS3Credentials() { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; - using var db = Db.Connect(); - var currentUser = (User)ctx!.Items["User"]!; + // TODO: S3Credentials should be per session, not per user - return db.CreateAndSaveUserS3ApiKey(currentUser); + var caller = GetCaller(); + if (caller is null) + return new HttpResponseMessage(HttpStatusCode.Unauthorized); + + using var db = Db.Connect(); + + return db.CreateAndSaveUserS3ApiKey(caller); } - [ProducesResponseType(typeof(User), 200)] - [ProducesResponseType(401)] + [Returns] + [Returns(HttpStatusCode.Unauthorized)] [HttpGet($"{nameof(GetUserById)}")] public Object GetUserById(Int64 id) { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; - using var db = Db.Connect(); - var currentUser = (User)ctx.Items["User"]; - var viewedUser = db.GetUserById(id); - - //using the same error to prevent fishing for ids - if (currentUser == null || viewedUser == null || !db.IsParentOfChild(currentUser, viewedUser)) + var caller = GetCaller(); + if (caller is null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); + + using var db = Db.Connect(); - return viewedUser; + var user = db + .GetDescendantUsers(caller) + .FirstOrDefault(u => u.Id == id); + + return user as Object ?? new HttpResponseMessage(HttpStatusCode.Unauthorized); } - [ProducesResponseType(typeof(Installation), 200)] - [ProducesResponseType(401)] + + [Returns] + [Returns(HttpStatusCode.Unauthorized)] [HttpGet($"{nameof(GetInstallationById)}")] public Object GetInstallationById(Int64 id) { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; - using var db = Db.Connect(); - var currentUser = (User)ctx.Items["User"]; - - if (currentUser == null) + var caller = GetCaller(); + if (caller == null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); + using var db = Db.Connect(); + var installation = db - .GetAllAccessibleInstallations(currentUser) + .GetAllAccessibleInstallations(caller) .FirstOrDefault(i => i.Id == id); - if (installation is null) - return new HttpResponseMessage(HttpStatusCode.NotFound); - - return installation; + return installation as Object ?? new HttpResponseMessage(HttpStatusCode.NotFound); } - [ProducesResponseType(typeof(Folder), 200)] - [ProducesResponseType(401)] + + [Returns] + [Returns(HttpStatusCode.Unauthorized)] [HttpGet($"{nameof(GetFolderById)}")] public Object GetFolderById(Int64 id) { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; + var caller = GetCaller(); + if (caller == null) + return new HttpResponseMessage(HttpStatusCode.Unauthorized); + using var db = Db.Connect(); - var currentUser = (User)ctx.Items["User"]; - + var folder = db - .GetAllAccessibleFolders(currentUser!) + .GetAllAccessibleFolders(caller) .FirstOrDefault(f => f.Id == id); - if(folder is null) - return new HttpResponseMessage(HttpStatusCode.NotFound); - - return folder; + return folder as Object ?? new HttpResponseMessage(HttpStatusCode.NotFound); } - [ProducesResponseType(200)] - [ProducesResponseType(401)] + + [Returns] // assuming swagger knows about arrays but not lists (JSON) + [Returns(HttpStatusCode.Unauthorized)] [HttpGet($"{nameof(GetAllInstallations)}/")] public Object GetAllInstallations() { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; - using var db = Db.Connect(); - var user = (User)ctx.Items["User"]; - - if (user == null) + var caller = GetCaller(); + if (caller == null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); + + using var db = Db.Connect(); - return db.GetAllAccessibleInstallations(user).ToList(); + return db + .GetAllAccessibleInstallations(caller) + .ToList(); // important! } - [ProducesResponseType(200)] - [ProducesResponseType(401)] + + [Returns] // assuming swagger knows about arrays but not lists (JSON) + [Returns(HttpStatusCode.Unauthorized)] [HttpGet($"{nameof(GetAllFolders)}/")] public Object GetAllFolders() { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; - var user = (User)ctx.Items["User"]; - - using var db = Db.Connect(); - - if (user == null) + var caller = GetCaller(); + if (caller == null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); - - return db.GetAllAccessibleFolders(user).ToList(); + + using var db = Db.Connect(); + return db + .GetAllAccessibleFolders(caller) + .ToList(); // important! } + - [ProducesResponseType(200)] - [ProducesResponseType(401)] + [Returns(HttpStatusCode.OK)] + [Returns(HttpStatusCode.Unauthorized)] [HttpPut($"{nameof(UpdateUser)}/")] public Object UpdateUser(User updatedUser) { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; - using var db = Db.Connect(); - var currentUser = (User)ctx.Items["User"]; - - if (currentUser == null || !currentUser.HasWriteAccess || !db.IsParentOfChild(currentUser, updatedUser)) + // TODO: distinguish between create and update + + var caller = GetCaller(); + if (caller == null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); - return db.GetUserById(updatedUser.Id) != null ? db.UpdateUser(updatedUser) : db.CreateUser(updatedUser); + using var db = Db.Connect(); + + return db.GetUserById(updatedUser.Id) != null + ? db.UpdateUser(updatedUser) + : db.CreateUser(updatedUser); } - [ProducesResponseType(200)] - [ProducesResponseType(401)] + + [Returns(HttpStatusCode.OK)] + [Returns(HttpStatusCode.Unauthorized)] [HttpPut($"{nameof(UpdateInstallation)}/")] public Object UpdateInstallation(Installation updatedInstallation) { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; + var caller = GetCaller(); - var currentUser = (User)ctx.Items["User"]; - - if (currentUser == null || !currentUser.HasWriteAccess) + if (caller is null || !caller.HasWriteAccess) return new HttpResponseMessage(HttpStatusCode.Unauthorized); - + using var db = Db.Connect(); - var hasAccess = db.GetAllAccessibleInstallations(currentUser) - .Any(i => i.Id == updatedInstallation.Id); - if (!hasAccess) + var hasAccessToInstallation = db + .GetAllAccessibleInstallations(caller) + .Any(i => i.Id == updatedInstallation.Id); + + if (!hasAccessToInstallation) return new HttpResponseMessage(HttpStatusCode.Unauthorized); // TODO: accessibility by other users etc @@ -213,64 +207,68 @@ public class Controller } - [ProducesResponseType(200)] - [ProducesResponseType(401)] + [Returns(HttpStatusCode.OK)] + [Returns(HttpStatusCode.Unauthorized)] [HttpPut($"{nameof(UpdateFolder)}/")] - public Object UpdateFolder(Folder updatedFolder) + public Object UpdateFolder(Folder folder) { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; - using var db = Db.Connect(); - var currentUser = (User)ctx.Items["User"]; - - if (currentUser == null || !currentUser.HasWriteAccess) + var caller = GetCaller(); + + if (caller is null || !caller.HasWriteAccess) return new HttpResponseMessage(HttpStatusCode.Unauthorized); - var hasAccess = db.GetAllAccessibleFolders(currentUser) - .Any(f => f.Id == updatedFolder.Id); + using var db = Db.Connect(); + + var hasAccessToFolder = db + .GetAllAccessibleFolders(caller) + .Any(f => f.Id == folder.Id); - if (!hasAccess) + if (!hasAccessToFolder) return new HttpResponseMessage(HttpStatusCode.Unauthorized); // TODO: accessibility by other users etc // TODO: sanity check changes - return db.UpdateFolder(updatedFolder); + return db.UpdateFolder(folder); } - [ProducesResponseType(200)] - [ProducesResponseType(401)] + [Returns(HttpStatusCode.OK)] + [Returns(HttpStatusCode.Unauthorized)] [HttpDelete($"{nameof(DeleteUser)}/")] public Object DeleteUser(Int64 userId) { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; - using var db = Db.Connect(); - var currentUser = (User)ctx.Items["User"]; - var userToBeDeleted = db.GetUserById(userId); + var caller = GetCaller(); - if (currentUser == null - || userToBeDeleted == null - || !currentUser.HasWriteAccess - || !db.IsParentOfChild(currentUser,userToBeDeleted)) + if (caller is null || !caller.HasWriteAccess) + return new HttpResponseMessage(HttpStatusCode.Unauthorized); + + using var db = Db.Connect(); + + var userToBeDeleted = db + .GetDescendantUsers(caller) + .FirstOrDefault(u => u.Id == userId); + + if (userToBeDeleted is null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); return db.DeleteUser(userToBeDeleted); } - [ProducesResponseType(200)] - [ProducesResponseType(401)] + [Returns(HttpStatusCode.OK)] + [Returns(HttpStatusCode.Unauthorized)] [HttpDelete($"{nameof(DeleteInstallation)}/")] - public Object DeleteInstallation(Int64 idOfInstallationToBeDeleted) + public Object DeleteInstallation(Int64 installationId) { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; + var caller = GetCaller(); + + if (caller is null || !caller.HasWriteAccess) + return new HttpResponseMessage(HttpStatusCode.Unauthorized); + using var db = Db.Connect(); - var currentUser = (User)ctx.Items["User"]; var installationToBeDeleted = db - .GetAllAccessibleInstallations(currentUser!) - .FirstOrDefault(i => i.Id == idOfInstallationToBeDeleted); + .GetAllAccessibleInstallations(caller) + .FirstOrDefault(i => i.Id == installationId); if (installationToBeDeleted is null) return new HttpResponseMessage(HttpStatusCode.Unauthorized); @@ -278,18 +276,20 @@ public class Controller return db.DeleteInstallation(installationToBeDeleted); } + [ProducesResponseType(200)] [ProducesResponseType(401)] [HttpDelete($"{nameof(DeleteFolder)}/")] public Object DeleteFolder(Int64 folderId) { - var ctxAccessor = new HttpContextAccessor(); - var ctx = ctxAccessor.HttpContext; + var caller = GetCaller(); + if (caller == null) + return new HttpResponseMessage(HttpStatusCode.Unauthorized); + using var db = Db.Connect(); - var currentUser = (User)ctx.Items["User"]; var folderToDelete = db - .GetAllAccessibleFolders(currentUser!) + .GetAllAccessibleFolders(caller) .FirstOrDefault(f => f.Id == folderId); if (folderToDelete is null) @@ -299,5 +299,22 @@ public class Controller } + private static User? GetCaller() + { + var ctxAccessor = new HttpContextAccessor(); + return ctxAccessor.HttpContext?.Items["User"] as User; + } + + private static Boolean VerifyPassword(String password, User user) + { + var pwdBytes = Encoding.UTF8.GetBytes(password); + var saltBytes = Encoding.UTF8.GetBytes(user.Salt + "innovEnergy"); + var pwdHash = Crypto.ComputeHash(pwdBytes, saltBytes); + + return user.Password == pwdHash; + } + } + + diff --git a/csharp/app/Backend/Controllers/Credentials.cs b/csharp/app/Backend/Controllers/Credentials.cs index 43c86a811..2c5c72b45 100644 --- a/csharp/app/Backend/Controllers/Credentials.cs +++ b/csharp/app/Backend/Controllers/Credentials.cs @@ -1,3 +1,6 @@ -namespace Backend.Controllers; +using System.Diagnostics.CodeAnalysis; +namespace Innovenergy.Backend.Controllers; + +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] public record Credentials(String Username, String Password); \ No newline at end of file diff --git a/csharp/app/Backend/Controllers/ReturnsAttribute.cs b/csharp/app/Backend/Controllers/ReturnsAttribute.cs new file mode 100644 index 000000000..6d94088f5 --- /dev/null +++ b/csharp/app/Backend/Controllers/ReturnsAttribute.cs @@ -0,0 +1,22 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc; + +namespace Innovenergy.Backend.Controllers; + +public class ReturnsAttribute : ProducesResponseTypeAttribute +{ + public ReturnsAttribute(HttpStatusCode statusCode) : base((Int32)statusCode) + { + } +} + +public class ReturnsAttribute : ProducesResponseTypeAttribute +{ + public ReturnsAttribute(HttpStatusCode statusCode) : base(typeof(T), (Int32)statusCode) + { + } + + public ReturnsAttribute() : base(typeof(T), (Int32)HttpStatusCode.OK) + { + } +} \ No newline at end of file diff --git a/csharp/app/Backend/Database/Db.cs b/csharp/app/Backend/Database/Db.cs index 92d31ca84..aaabfbfd9 100644 --- a/csharp/app/Backend/Database/Db.cs +++ b/csharp/app/Backend/Database/Db.cs @@ -1,11 +1,11 @@ using System.Diagnostics.CodeAnalysis; -using Backend.Model; -using Backend.Model.Relations; -using Backend.Utils; +using Innovenergy.Backend.Model; +using Innovenergy.Backend.Model.Relations; +using Innovenergy.Backend.Utils; using InnovEnergy.Lib.Utils; using SQLite; -namespace Backend.Database; +namespace Innovenergy.Backend.Database; public partial class Db : IDisposable { @@ -97,6 +97,8 @@ public partial class Db : IDisposable return direct.Concat(fromFolders); } + + public IEnumerable GetAllAccessibleFolders(User user) { diff --git a/csharp/app/Backend/Database/Fake.cs b/csharp/app/Backend/Database/Fake.cs index 94325df6d..427c78434 100644 --- a/csharp/app/Backend/Database/Fake.cs +++ b/csharp/app/Backend/Database/Fake.cs @@ -1,6 +1,6 @@ -using Backend.Model.Relations; +using Innovenergy.Backend.Model.Relations; -namespace Backend.Database; +namespace Innovenergy.Backend.Database; public partial class Db { diff --git a/csharp/app/Backend/Database/Folder.cs b/csharp/app/Backend/Database/Folder.cs index f7411e017..34e20f529 100644 --- a/csharp/app/Backend/Database/Folder.cs +++ b/csharp/app/Backend/Database/Folder.cs @@ -1,9 +1,9 @@ -using Backend.Model; -using Backend.Utils; +using Innovenergy.Backend.Model; +using Innovenergy.Backend.Utils; using InnovEnergy.Lib.Utils; using SQLite; -namespace Backend.Database; +namespace Innovenergy.Backend.Database; public partial class Db { @@ -37,6 +37,15 @@ public partial class Db return Installations.Where(f => f.ParentId == parent.Id); } + public IEnumerable GetChildUsers(User parent) + { + return Users.Where(f => f.ParentId == parent.Id); + } + + public IEnumerable GetDescendantUsers(User parent) + { + return parent.Traverse(GetChildUsers); + } public Result CreateFolder(Folder folder) { diff --git a/csharp/app/Backend/Database/Installation.cs b/csharp/app/Backend/Database/Installation.cs index 5473892af..a362a39df 100644 --- a/csharp/app/Backend/Database/Installation.cs +++ b/csharp/app/Backend/Database/Installation.cs @@ -1,8 +1,8 @@ -using Backend.Model; -using Backend.Utils; +using Innovenergy.Backend.Model; +using Innovenergy.Backend.Utils; using SQLite; -namespace Backend.Database; +namespace Innovenergy.Backend.Database; public partial class Db { diff --git a/csharp/app/Backend/Database/User.cs b/csharp/app/Backend/Database/User.cs index cda467968..e002c48be 100644 --- a/csharp/app/Backend/Database/User.cs +++ b/csharp/app/Backend/Database/User.cs @@ -1,18 +1,16 @@ -using System.Net; using System.Net.Mail; using System.Security.Cryptography; using System.Text; -using System.Text.Json; -using Backend.Model; -using Backend.Utils; using Flurl.Http; +using Innovenergy.Backend.Model; +using Innovenergy.Backend.Utils; using InnovEnergy.Lib.Utils; -using Microsoft.AspNetCore.DataProtection; using SQLite; + #pragma warning disable CS0472 #pragma warning disable CS8602 -namespace Backend.Database; +namespace Innovenergy.Backend.Database; public partial class Db { @@ -22,16 +20,16 @@ public partial class Db public User? GetUserById(Int64 id) { - return Users.FirstOrDefault(u => u.Id == id); + return Users.FirstOrDefault(u => u.Id == id); } - + public Boolean IsParentOfChild(User parent, User child) { var parentPointer = child.ParentId; - + if (parent.Id == child.Id) return true; - + while (parentPointer != null && parentPointer != parent.Id) { parentPointer = GetUserById(parentPointer).ParentId; @@ -46,10 +44,10 @@ public partial class Db { if (GetUserByEmail(user.Email) is not null) return Result.Error("User with that email already exists"); - + //Salting and Hashing password var salt = Crypto.GenerateSalt(); - var hashedPassword = Crypto.ComputeHash(Encoding.UTF8.GetBytes(user.Password), + var hashedPassword = Crypto.ComputeHash(Encoding.UTF8.GetBytes(user.Password), Encoding.UTF8.GetBytes(salt + "innovEnergy")); user.Salt = salt; @@ -57,49 +55,51 @@ public partial class Db return Create(user); } - + public Object CreateAndSaveUserS3ApiKey(User user) { //EXOSCALE API URL - const String url = "https://api-ch-dk-2.exoscale.com/v2/access-key"; + const String url = "https://api-ch-dk-2.exoscale.com/v2/access-key"; const String secret = "S2K1okphiCSNK4mzqr4swguFzngWAMb1OoSlZsJa9F0"; const String apiKey = "EXOb98ec9008e3ec16e19d7b593"; var payload = new - { name = user.Email, - operations = new List {"getObject", "listBucket"}, - content = new List{}}; - + { + name = user.Email, + operations = new List { "getObject", "listBucket" }, + content = new List { } + }; + var installationIdList = User2Installation .Where(i => i.UserId == user.Id) .SelectMany(i => Installations.Where(f => i.InstallationId == f.Id)) .ToList(); - + foreach (var installation in installationIdList) { - payload.content.Add(new {domain = "sos", resource_type = "bucket", resource_name = installation.Name}); //TODO CHANGE NAME TO S3BUCKET + payload.content.Add(new { domain = "sos", resource_type = "bucket", resource_name = installation.Name }); //TODO CHANGE NAME TO S3BUCKET } - + using var hmacSha1 = new HMACSHA1(Encoding.UTF8.GetBytes(secret)); var signature = Encoding.UTF8 .GetBytes(payload.ToString()) .Apply(hmacSha1.ComputeHash) .Apply(Convert.ToBase64String); - + var keyJson = url .WithHeader("Authorization", $"POST {apiKey};{signature}") .PostJsonAsync(payload) .ReceiveJson() .Result; - + return SetUserS3ApiKey(user, keyJson.GetValue("key")); } - - public Result SetUserS3ApiKey(User user,String key) + + public Result SetUserS3ApiKey(User user, String key) { user.S3Key = key; return Update(user); } - + public Result UpdateUser(User user) { var oldUser = GetUserById(user.Id); @@ -108,42 +108,39 @@ public partial class Db //Checking for unchangeable things // TODO: depends on privileges of caller - - user.Id = oldUser.Id; + + user.Id = oldUser.Id; user.ParentId = oldUser.ParentId; - user.Email = oldUser.Email; - + user.Email = oldUser.Email; + return Update(user); } public Result DeleteUser(User user) { - User2Folder .Delete(u => u.UserId == user.Id); + User2Folder.Delete(u => u.UserId == user.Id); User2Installation.Delete(u => u.UserId == user.Id); - + //Todo check for orphaned Installations/Folders - + // GetChildUsers() - + return Delete(user); } - + // TODO private static Boolean IsValidEmail(String email) { try - { + { var emailAddress = new MailAddress(email); } catch { return false; } + return true; } - - - -} - +} \ No newline at end of file diff --git a/csharp/app/Backend/Database/User2Folder.cs b/csharp/app/Backend/Database/User2Folder.cs index c55458dde..465eea722 100644 --- a/csharp/app/Backend/Database/User2Folder.cs +++ b/csharp/app/Backend/Database/User2Folder.cs @@ -1,7 +1,7 @@ -using Backend.Model.Relations; +using Innovenergy.Backend.Model.Relations; using SQLite; -namespace Backend.Database; +namespace Innovenergy.Backend.Database; public partial class Db { diff --git a/csharp/app/Backend/Database/User2Installation.cs b/csharp/app/Backend/Database/User2Installation.cs index 79ab547f5..ca329deee 100644 --- a/csharp/app/Backend/Database/User2Installation.cs +++ b/csharp/app/Backend/Database/User2Installation.cs @@ -1,7 +1,7 @@ -using Backend.Model.Relations; +using Innovenergy.Backend.Model.Relations; using SQLite; -namespace Backend.Database; +namespace Innovenergy.Backend.Database; public partial class Db { diff --git a/csharp/app/Backend/HeaderFilter.cs b/csharp/app/Backend/HeaderFilter.cs new file mode 100644 index 000000000..8ca9cdff8 --- /dev/null +++ b/csharp/app/Backend/HeaderFilter.cs @@ -0,0 +1,24 @@ +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace Innovenergy.Backend; + +/// +/// This is for convenient testing! Todo throw me out? +/// Operation filter to add the requirement of the custom header +/// +public class HeaderFilter : IOperationFilter +{ + public void Apply(OpenApiOperation operation, OperationFilterContext context) + { + operation.Parameters ??= new List(); + + operation.Parameters.Add(new OpenApiParameter + { + Name = "auth", + In = ParameterLocation.Header, + Content = new Dictionary(), + Required = false + }); + } +} \ No newline at end of file diff --git a/csharp/app/Backend/Model/Folder.cs b/csharp/app/Backend/Model/Folder.cs index fbc016201..c9c39c594 100644 --- a/csharp/app/Backend/Model/Folder.cs +++ b/csharp/app/Backend/Model/Folder.cs @@ -1,6 +1,4 @@ -using SQLite; - -namespace Backend.Model; +namespace Innovenergy.Backend.Model; public class Folder : TreeNode { diff --git a/csharp/app/Backend/Model/Installation.cs b/csharp/app/Backend/Model/Installation.cs index 9c1f23104..4cca94295 100644 --- a/csharp/app/Backend/Model/Installation.cs +++ b/csharp/app/Backend/Model/Installation.cs @@ -1,6 +1,4 @@ -using SQLite; - -namespace Backend.Model; +namespace Innovenergy.Backend.Model; public class Installation : TreeNode diff --git a/csharp/app/Backend/Model/Relations/Relation.cs b/csharp/app/Backend/Model/Relations/Relation.cs index 388892872..31250df14 100644 --- a/csharp/app/Backend/Model/Relations/Relation.cs +++ b/csharp/app/Backend/Model/Relations/Relation.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using SQLite; -namespace Backend.Model.Relations; +namespace Innovenergy.Backend.Model.Relations; public abstract class Relation { diff --git a/csharp/app/Backend/Model/Relations/Session.cs b/csharp/app/Backend/Model/Relations/Session.cs index 4db1a9188..ff64b6644 100644 --- a/csharp/app/Backend/Model/Relations/Session.cs +++ b/csharp/app/Backend/Model/Relations/Session.cs @@ -1,6 +1,6 @@ using SQLite; -namespace Backend.Model.Relations; +namespace Innovenergy.Backend.Model.Relations; public class Session : Relation { diff --git a/csharp/app/Backend/Model/Relations/User2Folder.cs b/csharp/app/Backend/Model/Relations/User2Folder.cs index 09d0456bf..6e9f56433 100644 --- a/csharp/app/Backend/Model/Relations/User2Folder.cs +++ b/csharp/app/Backend/Model/Relations/User2Folder.cs @@ -1,6 +1,6 @@ using SQLite; -namespace Backend.Model.Relations; +namespace Innovenergy.Backend.Model.Relations; internal class User2Folder : Relation { diff --git a/csharp/app/Backend/Model/Relations/User2Installation.cs b/csharp/app/Backend/Model/Relations/User2Installation.cs index 17dd179ab..43fa45d0e 100644 --- a/csharp/app/Backend/Model/Relations/User2Installation.cs +++ b/csharp/app/Backend/Model/Relations/User2Installation.cs @@ -1,6 +1,6 @@ using SQLite; -namespace Backend.Model.Relations; +namespace Innovenergy.Backend.Model.Relations; internal class User2Installation : Relation { diff --git a/csharp/app/Backend/Model/TreeNode.cs b/csharp/app/Backend/Model/TreeNode.cs index 61eeb2dba..1daf30f74 100644 --- a/csharp/app/Backend/Model/TreeNode.cs +++ b/csharp/app/Backend/Model/TreeNode.cs @@ -1,7 +1,6 @@ using SQLite; - -namespace Backend.Model; +namespace Innovenergy.Backend.Model; public abstract class TreeNode { diff --git a/csharp/app/Backend/Model/User.cs b/csharp/app/Backend/Model/User.cs index b9848cc6c..a1a0be392 100644 --- a/csharp/app/Backend/Model/User.cs +++ b/csharp/app/Backend/Model/User.cs @@ -1,6 +1,6 @@ using SQLite; -namespace Backend.Model; +namespace Innovenergy.Backend.Model; public class User : TreeNode { diff --git a/csharp/app/Backend/Utils/Crypto.cs b/csharp/app/Backend/Utils/Crypto.cs index 57362c5db..199b17fb9 100644 --- a/csharp/app/Backend/Utils/Crypto.cs +++ b/csharp/app/Backend/Utils/Crypto.cs @@ -1,6 +1,6 @@ using System.Security.Cryptography; -namespace Backend.Utils; +namespace Innovenergy.Backend.Utils; public static class Crypto { diff --git a/csharp/app/Backend/Utils/Result.cs b/csharp/app/Backend/Utils/Result.cs index 1db4a4cf4..21e85876c 100644 --- a/csharp/app/Backend/Utils/Result.cs +++ b/csharp/app/Backend/Utils/Result.cs @@ -1,4 +1,4 @@ -namespace Backend.Utils; +namespace Innovenergy.Backend.Utils; public class Result { diff --git a/csharp/app/Backend/db.sqlite b/csharp/app/Backend/db.sqlite index da6dc625a..fc3ad2ed8 100644 Binary files a/csharp/app/Backend/db.sqlite and b/csharp/app/Backend/db.sqlite differ diff --git a/csharp/app/Backend/program.cs b/csharp/app/Backend/program.cs index e7017ab8f..26c44b71c 100644 --- a/csharp/app/Backend/program.cs +++ b/csharp/app/Backend/program.cs @@ -1,104 +1,59 @@ -using Backend.Controllers; -using Backend.Database; +using Innovenergy.Backend.Database; using Microsoft.OpenApi.Models; -using Swashbuckle.AspNetCore.SwaggerGen; +namespace Innovenergy.Backend; -using (var db = Db.Connect()) +public class Program { - db.CreateFakeRelations(); -} - - - -var builder = WebApplication.CreateBuilder(args); - -builder.Services.AddControllers(); // TODO: remove magic, specify controllers explicitly -// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle - -builder.Services.AddHttpContextAccessor(); -builder.Services.AddEndpointsApiExplorer(); -builder.Services.AddCors(o => o.AddDefaultPolicy(p => p.WithOrigins("*").AllowAnyHeader().AllowAnyMethod())); -builder.Services.AddSwaggerGen(config => -{ - config.SwaggerDoc("v1", new OpenApiInfo{ Title = "My API", Version = "V1" }); - config.OperationFilter(); //Todo testing throw me out -}); - - - -var app = builder.Build(); - -// Configure the HTTP request pipeline. -if (app.Environment.IsDevelopment()) -{ - app.UseSwagger(); - app.UseSwaggerUI(cfg => cfg.EnableFilter()); -} - -app.UseCors(); -app.UseHttpsRedirection(); -app.UseAuthorization(); -app.Use(SetSessionUser); -app.MapControllers(); - -app.Run(); - - - - - - -//================= Functions for above =================== - -//Setting User for current Session -async Task SetSessionUser(HttpContext ctx, RequestDelegate next) -{ - var headers = ctx.Request.Headers; - var hasToken = headers.TryGetValue("auth", out var token); - - if (!ctx.Request.Path.ToString().Contains(nameof(Controller.Login))) + public static void Main(string[] args) { - if (!hasToken) + using (var db = Db.Connect()) + db.CreateFakeRelations(); + + var builder = WebApplication.CreateBuilder(args); + + builder.Services.AddControllers(); // TODO: remove magic, specify controllers explicitly + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + + builder.Services.AddHttpContextAccessor(); + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddCors(o => o.AddDefaultPolicy(p => p.WithOrigins("*").AllowAnyHeader().AllowAnyMethod())); + builder.Services.AddSwaggerGen(config => { - ctx.Response.StatusCode = 403; - return; - } - - using var db = Db.Connect(); - var user = db.GetUserByToken(token.ToString()); - - if (user is null) - { - ctx.Response.StatusCode = 403; - return; - } - - ctx.Items["User"] = user; - } - - await next(ctx); -} - - - - -/// -/// This is for convenient testing! Todo throw me out? -/// Operation filter to add the requirement of the custom header -/// -public class MyHeaderFilter : IOperationFilter -{ - public void Apply(OpenApiOperation operation, OperationFilterContext context) - { - operation.Parameters ??= new List(); - - operation.Parameters.Add(new OpenApiParameter - { - Name = "auth", - In = ParameterLocation.Header, - Content = new Dictionary(), - Required = false + config.SwaggerDoc("v1", new OpenApiInfo{ Title = "My API", Version = "V1" }); + config.OperationFilter(); //Todo testing throw me out }); + + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(cfg => cfg.EnableFilter()); + } + + app.UseCors(); + app.UseHttpsRedirection(); + app.UseAuthorization(); + app.Use(SetSessionUser); + app.MapControllers(); + + app.Run(); + } + + private static async Task SetSessionUser(HttpContext ctx, RequestDelegate next) + { + var headers = ctx.Request.Headers; + var hasToken = headers.TryGetValue("auth", out var token); + + if (hasToken) + { + using var db = Db.Connect(); + ctx.Items["User"] = db.GetUserByToken(token.ToString()); + } + + await next(ctx); } } \ No newline at end of file diff --git a/csharp/app/CsController/CsController.csproj b/csharp/app/CsController/CsController.csproj deleted file mode 100644 index aab629be3..000000000 --- a/csharp/app/CsController/CsController.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - - enable - InnovEnergy.CsController - true - - - - - - - - diff --git a/csharp/app/CsController/Program.cs b/csharp/app/CsController/Program.cs deleted file mode 100644 index 4d625c96c..000000000 --- a/csharp/app/CsController/Program.cs +++ /dev/null @@ -1,62 +0,0 @@ -using System.Reactive.Linq; -using InnovEnergy.Lib.Protocols.DBus; -using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes; -using InnovEnergy.Lib.Victron.VeDBus; - - -// 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 CsController "); - -//Enable out InnovEnergy GUI Page through DBusService.DBUS_SERVICE_UNSUPPORTED -// var service = new DBusService("com.victronenergy.unsupported"); -var veProperties = new VeProperties(); -veProperties.Set("/CustomName", "InnovEnergy"); -veProperties.Set("/ProductName", "InnovEnergySW"); - - -var dbus = new DBusConnection(Bus.System); -// var ep = new UnixDomainSocketEndPoint("/home/kim/graber_dbus.sock"); -// var auth = AuthenticationMethod.ExternalAsRoot(); -// var dbusAddress = new Bus(ep, auth); -// var dbus = new DBusConnection(dbusAddress); - - -await veProperties.PublishOnDBus(Bus.System, "com.victronenergy.unsupported"); - -var battery = "com.victronenergy.battery.ttyUSB0"; -var soc = 21.0; - -//TODO change ttyUSB0 for generic Battery on dbus -var names = await dbus.ListNames(); -foreach (var name in names) -{ - if(name.Contains("com.victronenergy.battery.")) - { - battery = name; - break; - } -} - -var mustCharge = dbus - .ObserveSignalMessages(sender: battery, objectPath: "/Soc") - .Select(m => m.Payload)! - .OfType>() - .Where(d => d.ContainsKey("Value")) - .Select(d => d["Value"].Value) - .OfType() - .Do(s => Console.WriteLine($"soc = {s}")) - .Select(d => d < 67) - .DistinctUntilChanged(); - - -mustCharge.Subscribe(b => -{ - dbus.SetValue("com.victronenergy.settings", "/Settings/CGwacs/BatteryLife/State", b ? 9 : 10); // 9 = charge battery, 10 = optimized (no batterylife) -}); - - -// Console.ReadLine(); \ No newline at end of file diff --git a/csharp/app/CsController/debug.sh b/csharp/app/CsController/debug.sh deleted file mode 100644 index 3f25d1795..000000000 --- a/csharp/app/CsController/debug.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -csproj="CsController.csproj" -exe="CsController" -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/CsController/service/log/run b/csharp/app/CsController/service/log/run deleted file mode 100755 index d42c9d38b..000000000 --- a/csharp/app/CsController/service/log/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -exec 2>&1 -exec multilog t s25000 n4 /var/log/CsController diff --git a/csharp/app/CsController/service/run b/csharp/app/CsController/service/run deleted file mode 100755 index a509d6c68..000000000 --- a/csharp/app/CsController/service/run +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh -exec 2>&1 -exec softlimit -d 100000000 -s 1000000 -a 100000000 /opt/innovenergy/CsController/CsController diff --git a/csharp/app/SaliMax/src/Program.cs b/csharp/app/SaliMax/src/Program.cs index 1bbbc8883..0734b5b6f 100644 --- a/csharp/app/SaliMax/src/Program.cs +++ b/csharp/app/SaliMax/src/Program.cs @@ -1,4 +1,4 @@ -#undef BatteriesAllowed + using System.Diagnostics; @@ -11,13 +11,11 @@ using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; using InnovEnergy.Lib.Devices.Ampt; using InnovEnergy.Lib.Devices.Battery48TL; -using InnovEnergy.Lib.Utils; using InnovEnergy.SaliMax.Controller; using InnovEnergy.SaliMax.Log; using InnovEnergy.SaliMax.SaliMaxRelays; using InnovEnergy.SaliMax.SystemConfig; using InnovEnergy.Time.Unix; -using Utils = InnovEnergy.Lib.StatusApi.Utils; #pragma warning disable IL2026 @@ -148,7 +146,7 @@ internal static class Program //JsonSerializer.Serialize(jsonLog, JsonOptions).WriteLine(ConsoleColor.DarkBlue); #endif - PrintTopology(status); + Topology.Print(status); while (UnixTime.Now == t) await Task.Delay(delayTime); @@ -157,181 +155,8 @@ internal static class Program } - private static void PrintTopology(StatusRecord s) - { - const String chargingSeparator = ">>>>>>>>>>"; - const String dischargingSeparator = "<<<<<<<<<"; - const Int32 height = 25; - var pwr = s.InverterStatus!.Ac.ActivePower; - var pvPower = (s.AmptStatus!.Devices[0].Dc.Voltage * s.AmptStatus.Devices[0].Dc.Current + s.AmptStatus!.Devices[1].Dc.Voltage * s.AmptStatus.Devices[1].Dc.Current).Round0(); // TODO using one Ampt - var loadPower = Utils.Round3((s.GridMeterStatus!.ActivePowerL123 + pwr)); // it's a + because the pwr is inverted - - var gridSeparator = s.GridMeterStatus!.ActivePowerL123 > 0 ? chargingSeparator : dischargingSeparator; - var inverterSeparator = -pwr > 0 ? chargingSeparator : dischargingSeparator; - var dcSeparator = -s.DcDcStatus!.Dc.Power > 0 ? chargingSeparator : dischargingSeparator; -#if BatteriesAllowed - var battery1Separator = s.BatteriesStatus[0]!.Power > 0 ? chargingSeparator : dischargingSeparator; - var battery2Separator = s.BatteriesStatus[1]!.Power > 0 ? chargingSeparator : dischargingSeparator; -#endif - - ////////////////// Grid ////////////////////// - var boxGrid = AsciiArt.CreateBox - ( - "Grid", - s.GridMeterStatus.Ac.L1.Voltage.V(), - s.GridMeterStatus.Ac.L2.Voltage.V(), - s.GridMeterStatus.Ac.L3.Voltage.V() - ).AlignCenterVertical(height); - - var gridAcBusArrow = AsciiArt.CreateHorizontalArrow(s.GridMeterStatus!.ActivePowerL123.Round0(), gridSeparator) - .AlignCenterVertical(height); - - - ////////////////// Ac Bus ////////////////////// - var boxAcBus = AsciiArt.CreateBox - ( - "AC Bus", - s.InverterStatus.Ac.L1.Voltage.V(), - s.InverterStatus.Ac.L2.Voltage.V(), - s.InverterStatus.Ac.L3.Voltage.V() - ); - - var boxLoad = AsciiArt.CreateBox - ( - "", - "LOAD", - "" - ); - - var loadRect = CreateRect(boxAcBus, boxLoad, loadPower).AlignBottom(height); - - var acBusInvertArrow = AsciiArt.CreateHorizontalArrow(-pwr.Round0(), inverterSeparator) - .AlignCenterVertical(height); - - //////////////////// Inverter ///////////////////////// - var inverterBox = AsciiArt.CreateBox - ( - "", - "Inverter", - "" - ).AlignCenterVertical(height); - - var inverterArrow = AsciiArt.CreateHorizontalArrow(-pwr.Round0(), inverterSeparator) - .AlignCenterVertical(height); - - - //////////////////// DC Bus ///////////////////////// - var dcBusBox = AsciiArt.CreateBox - ( - "DC Bus", - (s.InverterStatus.ActualDcLinkVoltageLowerHalfExt + s.InverterStatus.ActualDcLinkVoltageUpperHalfExt).V(), - "" - ); - - var pvBox = AsciiArt.CreateBox - ( - "MPPT", - ((s.AmptStatus!.Devices[0].Dc.Voltage + s.AmptStatus!.Devices[1].Dc.Voltage) / 2).Round0().V(), - "" - ); - - var pvRect = CreateRect(pvBox, dcBusBox, pvPower).AlignTop(height); - - var dcBusArrow = AsciiArt.CreateHorizontalArrow(-s.DcDcStatus!.Dc.Power, dcSeparator) - .AlignCenterVertical(height); - - //////////////////// Dc/Dc ///////////////////////// - var dcBox = AsciiArt.CreateBox - ( - "Dc/Dc", - s.DcDcStatus.BatteryVoltage.V(), - "" - ).AlignCenterVertical(height); -#if BatteriesAllowed - var dcArrow1 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[0]!.Power.Round0(), battery1Separator); - var dcArrow2 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[1]!.Power.Round0(), battery2Separator); -#else - var dcArrow1 =""; - var dcArrow2 = ""; - var dcArrowRect = CreateRect(dcArrow1, dcArrow2).AlignCenterVertical(height); -#endif - - -#if BatteriesAllowed - - //////////////////// Batteries ///////////////////////// - var battery1Box = AsciiArt.CreateBox - ( - "Battery 1", - s.BatteriesStatus[0].Voltage.V(), - s.BatteriesStatus[0].Soc.Percent(), - s.BatteriesStatus[0].Temperature.Celsius() - ); - - var battery2Box = AsciiArt.CreateBox - ( - "Battery 2", - s.BatteriesStatus[1].Voltage.V(), - s.BatteriesStatus[1].Soc.Percent(), - s.BatteriesStatus[1].Temperature.Celsius() - ); - - var batteryRect = CreateRect(battery1Box, battery2Box).AlignCenterVertical(height); - - var avgBatteryBox = AsciiArt.CreateBox - ( - "Batteries", - s.AvgBatteriesStatus!.Voltage.V(), - s.AvgBatteriesStatus.Soc.Percent(), - s.AvgBatteriesStatus.Temperature.Celsius() - ).AlignCenterVertical(height); - - - var topology = boxGrid.SideBySideWith(gridAcBusArrow, "") - .SideBySideWith(loadRect, "") - .SideBySideWith(acBusInvertArrow, "") - .SideBySideWith(inverterBox, "") - .SideBySideWith(inverterArrow, "") - .SideBySideWith(pvRect, "") - .SideBySideWith(dcBusArrow, "") - .SideBySideWith(dcBox, "") - .SideBySideWith(dcArrowRect, "") - .SideBySideWith(batteryRect, "") - .SideBySideWith(avgBatteryBox, "")+ "\n"; -#else - var topology = boxGrid.SideBySideWith(gridAcBusArrow, "") - .SideBySideWith(loadRect, "") - .SideBySideWith(acBusInvertArrow, "") - .SideBySideWith(inverterBox, "") - .SideBySideWith(inverterArrow, "") - .SideBySideWith(pvRect, "") - .SideBySideWith(dcBusArrow, "") - .SideBySideWith(dcBox, "")+ "\n"; -#endif - Console.WriteLine(topology); - } - - private static String CreateRect(String boxTop, String boxBottom, Decimal power) - { - var powerArrow = AsciiArt.CreateVerticalArrow(power); - var boxes = new[] { boxTop, powerArrow, boxBottom }; - var maxWidth = boxes.Max(l => l.Width()); - - var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines(); - return rect; - } - - private static String CreateRect(String boxTop, String boxBottom) - { - var boxes = new[] { boxTop, boxBottom }; - var maxWidth = boxes.Max(l => l.Width()); - - var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines(); - return rect; - } - // to delete not used anymore [Conditional("RELEASE")] private static void ReleaseWriteLog(JsonObject jsonLog, UnixTime timestamp) diff --git a/csharp/app/SaliMax/src/Topology.cs b/csharp/app/SaliMax/src/Topology.cs new file mode 100644 index 000000000..ab8a26ee9 --- /dev/null +++ b/csharp/app/SaliMax/src/Topology.cs @@ -0,0 +1,186 @@ +#undef BatteriesAllowed + +using InnovEnergy.Lib.Utils; +using InnovEnergy.SaliMax.Controller; +using InnovEnergy.SaliMax.Log; + +namespace InnovEnergy.SaliMax; + +public static class Topology +{ + public static void Print(StatusRecord s) + { + const String chargingSeparator = ">>>>>>>>>>"; + const String dischargingSeparator = "<<<<<<<<<"; + const Int32 height = 25; + + + var pwr = s.InverterStatus!.Ac.ActivePower; + var pvPower = (s.AmptStatus!.Devices[0].Dc.Voltage * s.AmptStatus.Devices[0].Dc.Current + s.AmptStatus!.Devices[1].Dc.Voltage * s.AmptStatus.Devices[1].Dc.Current).Round0(); // TODO using one Ampt + var loadPower = Utils.Round3((s.GridMeterStatus!.ActivePowerL123 + pwr)); // it's a + because the pwr is inverted + + var gridSeparator = s.GridMeterStatus!.ActivePowerL123 > 0 ? chargingSeparator : dischargingSeparator; + var inverterSeparator = -pwr > 0 ? chargingSeparator : dischargingSeparator; + var dcSeparator = -s.DcDcStatus!.Dc.Power > 0 ? chargingSeparator : dischargingSeparator; +#if BatteriesAllowed + var battery1Separator = s.BatteriesStatus[0]!.Power > 0 ? chargingSeparator : dischargingSeparator; + var battery2Separator = s.BatteriesStatus[1]!.Power > 0 ? chargingSeparator : dischargingSeparator; +#endif + + ////////////////// Grid ////////////////////// + var boxGrid = AsciiArt.CreateBox + ( + "Grid", + s.GridMeterStatus.Ac.L1.Voltage.V(), + s.GridMeterStatus.Ac.L2.Voltage.V(), + s.GridMeterStatus.Ac.L3.Voltage.V() + ).AlignCenterVertical(height); + + var gridAcBusArrow = AsciiArt.CreateHorizontalArrow(s.GridMeterStatus!.ActivePowerL123.Round0(), gridSeparator) + .AlignCenterVertical(height); + + + ////////////////// Ac Bus ////////////////////// + var boxAcBus = AsciiArt.CreateBox + ( + "AC Bus", + s.InverterStatus.Ac.L1.Voltage.V(), + s.InverterStatus.Ac.L2.Voltage.V(), + s.InverterStatus.Ac.L3.Voltage.V() + ); + + var boxLoad = AsciiArt.CreateBox + ( + "", + "LOAD", + "" + ); + + var loadRect = StringUtils.AlignBottom(CreateRect(boxAcBus, boxLoad, loadPower), height); + + var acBusInvertArrow = AsciiArt.CreateHorizontalArrow(-pwr.Round0(), inverterSeparator) + .AlignCenterVertical(height); + + //////////////////// Inverter ///////////////////////// + var inverterBox = AsciiArt.CreateBox + ( + "", + "Inverter", + "" + ).AlignCenterVertical(height); + + var inverterArrow = AsciiArt.CreateHorizontalArrow(-pwr.Round0(), inverterSeparator) + .AlignCenterVertical(height); + + + //////////////////// DC Bus ///////////////////////// + var dcBusBox = AsciiArt.CreateBox + ( + "DC Bus", + (s.InverterStatus.ActualDcLinkVoltageLowerHalfExt + s.InverterStatus.ActualDcLinkVoltageUpperHalfExt).V(), + "" + ); + + var pvBox = AsciiArt.CreateBox + ( + "MPPT", + ((s.AmptStatus!.Devices[0].Dc.Voltage + s.AmptStatus!.Devices[1].Dc.Voltage) / 2).Round0().V(), + "" + ); + + var pvRect = StringUtils.AlignTop(CreateRect(pvBox, dcBusBox, pvPower), height); + + var dcBusArrow = AsciiArt.CreateHorizontalArrow(-s.DcDcStatus!.Dc.Power, dcSeparator) + .AlignCenterVertical(height); + + //////////////////// Dc/Dc ///////////////////////// + var dcBox = AsciiArt.CreateBox + ( + "Dc/Dc", + s.DcDcStatus.BatteryVoltage.V(), + "" + ).AlignCenterVertical(height); +#if BatteriesAllowed + var dcArrow1 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[0]!.Power.Round0(), battery1Separator); + var dcArrow2 = AsciiArt.CreateHorizontalArrow(s.BatteriesStatus[1]!.Power.Round0(), battery2Separator); +#else + var dcArrow1 =""; + var dcArrow2 = ""; + var dcArrowRect = StringUtils.AlignCenterVertical(CreateRect(dcArrow1, dcArrow2), height); +#endif + + +#if BatteriesAllowed + + //////////////////// Batteries ///////////////////////// + var battery1Box = AsciiArt.CreateBox + ( + "Battery 1", + s.BatteriesStatus[0].Voltage.V(), + s.BatteriesStatus[0].Soc.Percent(), + s.BatteriesStatus[0].Temperature.Celsius() + ); + + var battery2Box = AsciiArt.CreateBox + ( + "Battery 2", + s.BatteriesStatus[1].Voltage.V(), + s.BatteriesStatus[1].Soc.Percent(), + s.BatteriesStatus[1].Temperature.Celsius() + ); + + var batteryRect = CreateRect(battery1Box, battery2Box).AlignCenterVertical(height); + + var avgBatteryBox = AsciiArt.CreateBox + ( + "Batteries", + s.AvgBatteriesStatus!.Voltage.V(), + s.AvgBatteriesStatus.Soc.Percent(), + s.AvgBatteriesStatus.Temperature.Celsius() + ).AlignCenterVertical(height); + + + var topology = boxGrid.SideBySideWith(gridAcBusArrow, "") + .SideBySideWith(loadRect, "") + .SideBySideWith(acBusInvertArrow, "") + .SideBySideWith(inverterBox, "") + .SideBySideWith(inverterArrow, "") + .SideBySideWith(pvRect, "") + .SideBySideWith(dcBusArrow, "") + .SideBySideWith(dcBox, "") + .SideBySideWith(dcArrowRect, "") + .SideBySideWith(batteryRect, "") + .SideBySideWith(avgBatteryBox, "")+ "\n"; +#else + var topology = boxGrid.SideBySideWith(gridAcBusArrow, "") + .SideBySideWith(loadRect, "") + .SideBySideWith(acBusInvertArrow, "") + .SideBySideWith(inverterBox, "") + .SideBySideWith(inverterArrow, "") + .SideBySideWith(pvRect, "") + .SideBySideWith(dcBusArrow, "") + .SideBySideWith(dcBox, "")+ "\n"; +#endif + Console.WriteLine(topology); + } + + private static String CreateRect(String boxTop, String boxBottom, Decimal power) + { + var powerArrow = AsciiArt.CreateVerticalArrow(power); + var boxes = new[] { boxTop, powerArrow, boxBottom }; + var maxWidth = boxes.Max(l => l.Width()); + + var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines(); + return rect; + } + + private static String CreateRect(String boxTop, String boxBottom) + { + var boxes = new[] { boxTop, boxBottom }; + var maxWidth = boxes.Max(l => l.Width()); + + var rect = boxes.Select(l => l.AlignCenterHorizontal(maxWidth)).JoinLines(); + return rect; + } + +} \ No newline at end of file