diff --git a/csharp/app/Backend/Backend.csproj b/csharp/App/Backend/Backend.csproj similarity index 72% rename from csharp/app/Backend/Backend.csproj rename to csharp/App/Backend/Backend.csproj index c082226db..a63522893 100644 --- a/csharp/app/Backend/Backend.csproj +++ b/csharp/App/Backend/Backend.csproj @@ -1,10 +1,5 @@ - - - net6.0 - enable - enable - + @@ -26,17 +21,24 @@ - - - ..\..\..\..\..\..\.nuget\packages\awssdk.core\3.7.8.10\lib\netcoreapp3.1\AWSSDK.Core.dll - - - ..\..\..\..\..\.nuget\packages\sqlite-net-pcl\1.8.116\lib\netstandard2.0\SQLite-net.dll - - + - + + + + + + + + + + + + + + PreserveNewest + diff --git a/csharp/App/Backend/Controllers/Controller.cs b/csharp/App/Backend/Controllers/Controller.cs new file mode 100644 index 000000000..46d6d67ad --- /dev/null +++ b/csharp/App/Backend/Controllers/Controller.cs @@ -0,0 +1,390 @@ +using InnovEnergy.App.Backend.Database; +using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.App.Backend.DataTypes.Methods; +using InnovEnergy.App.Backend.Relations; +using Microsoft.AspNetCore.Mvc; +using static System.Net.HttpStatusCode; +using Folder = InnovEnergy.App.Backend.DataTypes.Folder; +using Installation = InnovEnergy.App.Backend.DataTypes.Installation; +using Object = System.Object; +using User = InnovEnergy.App.Backend.DataTypes.User; + +namespace InnovEnergy.App.Backend.Controllers; + +[ApiController] +[Route("api/")] +public class Controller +{ + private static readonly HttpResponseMessage _Unauthorized = new HttpResponseMessage(Unauthorized); + private static readonly HttpResponseMessage _Ok = new HttpResponseMessage(OK); + private static readonly HttpResponseMessage _BadRequest = new HttpResponseMessage(BadRequest); + + [Returns] + [Returns(Unauthorized)] + [Returns(BadRequest)] + [HttpPost($"{nameof(Login)}")] + public Object Login(Credentials credentials) + { + var session = credentials.Login(); + + return session is null + ? _Unauthorized + : session; + } + + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPost($"{nameof(Logout)}")] + public Object Logout() + { + var session = GetSession(); + + return session.Logout() + ? _Ok + : _Unauthorized; + } + + + [Returns] + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetUserById)}")] + public Object GetUserById(Int64 id) + { + var caller = GetSession()?.User; + if (caller == null) + return _Unauthorized; + + var user = Db.GetUserById(id); + + if (user is null || !caller.HasAccessTo(user)) + return _Unauthorized; + + user.Password = ""; + return user; + } + + + [Returns] + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetInstallationById)}")] + public Object GetInstallationById(Int64 id) + { + var user = GetSession()?.User; + if (user == null) + return _Unauthorized; + + var installation = Db.GetInstallationById(id); + + if (installation is null || !user.HasAccessTo(installation)) + return _Unauthorized; + + return installation; + } + + [Returns] + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetUsersWithAccessToInstallation)}")] + public Object GetUsersWithAccessToInstallation(Int64 id) + { + var user = GetSession()?.User; + if (user == null) + return _Unauthorized; + + var installation = Db.GetInstallationById(id); + + if (installation is null || !user.HasAccessTo(installation)) + return _Unauthorized; + + var usersWithInheritedAccess = installation + .Ancestors() + .SelectMany(f => f.UsersWithDirectAccess() + .Where(u => u.IsDescendantOf(user)) + .Select(u => new { folderId = f.Id, user = u })) + .OfType(); + + var usersWithDirectAccess = installation.UsersWithDirectAccess() + .Where(u => u.IsDescendantOf(user)) + .Select(u => new { installationId = installation.Id, user = u }) + .OfType(); + + return usersWithInheritedAccess.Concat(usersWithDirectAccess); + } + + [Returns] + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetUsersWithAccessToFolder)}")] + public Object GetUsersWithAccessToFolder(Int64 id) + { + var user = GetSession()?.User; + if (user == null) + return _Unauthorized; + + var folder = Db.GetFolderById(id); + + if (folder is null || !user.HasAccessTo(folder)) + return _Unauthorized; + + return folder + .Ancestors() + .Append(folder) + .SelectMany(f => f.UsersWithDirectAccess() + .Where(u => u.IsDescendantOf(user)) + .Select(u => new { folderId = f.Id, user = u })); + } + + [Returns] + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetFolderById)}")] + public Object GetFolderById(Int64 id) + { + var user = GetSession()?.User; + if (user == null) + return _Unauthorized; + + var folder = Db.GetFolderById(id); + + if (folder is null || !user.HasAccessTo(folder)) + return _Unauthorized; + + return folder; + } + + + [Returns] // assuming swagger knows about arrays but not lists (JSON) + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetAllInstallations)}/")] + public Object GetAllInstallations() + { + var user = GetSession()?.User; + + return user is null + ? _Unauthorized + : user.AccessibleInstallations(); + } + + + [Returns] // assuming swagger knows about arrays but not lists (JSON) + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetAllFolders)}/")] + public Object GetAllFolders() + { + var user = GetSession()?.User; + + return user is null + ? _Unauthorized + : user.AccessibleFolders(); + } + + // [Returns] // assuming swagger knows about arrays but not lists (JSON) + // [Returns(Unauthorized)] + // [HttpGet($"{nameof(GetUsersOfFolder)}/")] + // public Object GetUsersOfFolder(Int64 folderId) + // { + // var caller = GetCaller(); + // if (caller == null) + // return new HttpResponseMessage(Unauthorized); + // + // var folder = Db.GetFolderById(folderId); + // + // if (folder is null || !caller.HasAccessTo(folder)) + // return new HttpResponseMessage(Unauthorized); + // + // return descendantUsers; + // } + + [Returns] // assuming swagger knows about arrays but not lists (JSON) + [Returns(Unauthorized)] + [HttpGet($"{nameof(GetAllFoldersAndInstallations)}/")] + public Object GetAllFoldersAndInstallations() + { + var user = GetSession()?.User; + + return user is null + ? _Unauthorized + : user.AccessibleFoldersAndInstallations(); + } + + + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPost($"{nameof(CreateUser)}/")] + public Object CreateUser(User newUser) + { + var session = GetSession(); + + return session.Create(newUser) + ? newUser + : _Unauthorized ; + } + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPost($"{nameof(CreateInstallation)}/")] + public Object CreateInstallation(Installation installation) + { + var session = GetSession(); + + return session.Create(installation) + ? installation + : _Unauthorized; + } + + [Returns(OK)] + [Returns(Unauthorized)] + [Returns(InternalServerError)] + [HttpPost($"{nameof(CreateFolder)}/")] + public Object CreateFolder(Folder folder) + { + var session = GetSession(); + + return session.Create(folder) + ? folder + : _Unauthorized; + } + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPost($"{nameof(GrantUserAccessToFolder)}/")] + public Object GrantUserAccessToFolder([FromQuery] Int64 folderId, [FromQuery] Int64? id) + { + var session = GetSession(); + var user = id is not null ? Db.GetUserById(id) : session?.User; + + return session.GrantUserAccessTo(user, Db.GetFolderById(folderId)) + ? _Ok + : _Unauthorized; + } + + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPost($"{nameof(GrantUserAccessToInstallation)}/")] + public Object GrantUserAccessToInstallation([FromQuery] Int64 installationId, [FromQuery] Int64? id) + { + var session = GetSession(); + + var user = id is not null ? Db.GetUserById(id) : session?.User; + + return session.GrantUserAccessTo(user, Db.GetInstallationById(installationId)) + ? _Ok + : _Unauthorized; + } + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPost($"{nameof(RevokeUserAccessToInstallation)}/")] + public Object RevokeUserAccessToInstallation([FromQuery] Int64 installationId, [FromQuery] Int64? id) + { + var session = GetSession(); + var user = id is not null ? Db.GetUserById(id) : session?.User; + + + return session.RevokeAccessTo(user, Db.GetInstallationById(installationId)) + ? _Ok + : _Unauthorized; + } + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPost($"{nameof(RevokeUserAccessToFolder)}/")] + public Object RevokeUserAccessToFolder([FromQuery] Int64 folderId, [FromQuery] Int64? id) + { + var session = GetSession(); + var user = id is not null ? Db.GetUserById(id) : session?.User; + + + return session.RevokeAccessTo(user, Db.GetFolderById(folderId)) + ? _Ok + : _Unauthorized; + } + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPut($"{nameof(UpdateUser)}/")] + public Object UpdateUser(User updatedUser) + { + var session = GetSession(); + + if (!session.Update(updatedUser)) return _Unauthorized; + updatedUser.Password = ""; + return updatedUser; + } + + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPut($"{nameof(UpdateInstallation)}/")] + public Object UpdateInstallation(Installation installation) + { + var session = GetSession(); + + return session.Update(installation) + ? installation + : _Unauthorized; + } + + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpPut($"{nameof(UpdateFolder)}/")] + public Object UpdateFolder(Folder folder) + { + var session = GetSession(); + + return session.Update(folder) + ? folder + : _Unauthorized; + } + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpDelete($"{nameof(DeleteUser)}/")] + public Object DeleteUser(Int64 userId) + { + var session = GetSession(); + var user = Db.GetUserById(userId); + + return session.Delete(user) + ? _Ok + : _Unauthorized; + } + + [Returns(OK)] + [Returns(Unauthorized)] + [HttpDelete($"{nameof(DeleteInstallation)}/")] + public Object DeleteInstallation(Int64 installationId) + { + var session = GetSession(); + var installation = Db.GetInstallationById(installationId); + + return session.Delete(installation) + ? _Ok + : _Unauthorized; + } + + [ProducesResponseType(200)] + [ProducesResponseType(401)] + [HttpDelete($"{nameof(DeleteFolder)}/")] + public Object DeleteFolder(Int64 folderId) + { + var session = GetSession(); + + var folder = Db.GetFolderById(folderId); + + return session.Delete(folder) + ? _Ok + : _Unauthorized; + + } + + private static Session? GetSession() + { + var ctxAccessor = new HttpContextAccessor(); + return ctxAccessor.HttpContext?.Items["Session"] as Session; + } +} + + + diff --git a/csharp/App/Backend/Controllers/ReturnsAttribute.cs b/csharp/App/Backend/Controllers/ReturnsAttribute.cs new file mode 100644 index 000000000..2ef924055 --- /dev/null +++ b/csharp/App/Backend/Controllers/ReturnsAttribute.cs @@ -0,0 +1,22 @@ +using System.Net; +using Microsoft.AspNetCore.Mvc; + +namespace InnovEnergy.App.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/DataTypes/Credentials.cs b/csharp/App/Backend/DataTypes/Credentials.cs new file mode 100644 index 000000000..d17c3f063 --- /dev/null +++ b/csharp/App/Backend/DataTypes/Credentials.cs @@ -0,0 +1,6 @@ +using System.Diagnostics.CodeAnalysis; + +namespace InnovEnergy.App.Backend.DataTypes; + +[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] +public record Credentials(String Username, String Password); \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/Folder.cs b/csharp/App/Backend/DataTypes/Folder.cs new file mode 100644 index 000000000..f383ef483 --- /dev/null +++ b/csharp/App/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/Backend/Model/Installation.cs b/csharp/App/Backend/DataTypes/Installation.cs similarity index 80% rename from csharp/app/Backend/Model/Installation.cs rename to csharp/App/Backend/DataTypes/Installation.cs index 9c1f23104..f149135a3 100644 --- a/csharp/app/Backend/Model/Installation.cs +++ b/csharp/App/Backend/DataTypes/Installation.cs @@ -1,6 +1,4 @@ -using SQLite; - -namespace Backend.Model; +namespace InnovEnergy.App.Backend.DataTypes; public class Installation : TreeNode @@ -16,6 +14,6 @@ public class Installation : TreeNode public Double Long { get; set; } public String S3Bucket { get; set; } = ""; + public String S3Url { get; set; } = ""; -} - +} \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/Methods/Credentials.cs b/csharp/App/Backend/DataTypes/Methods/Credentials.cs new file mode 100644 index 000000000..2e29149b1 --- /dev/null +++ b/csharp/App/Backend/DataTypes/Methods/Credentials.cs @@ -0,0 +1,27 @@ +using InnovEnergy.App.Backend.Database; +using InnovEnergy.App.Backend.Relations; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.App.Backend.DataTypes.Methods; + +public static class CredentialsMethods +{ + public static Session? Login(this Credentials credentials) + { + var (username, password) = credentials; + + if (username.IsNullOrEmpty() || password.IsNullOrEmpty()) + return null; + + var user = Db.GetUserByEmail(username); + + if (user is null || !user.VerifyPassword(password)) + return null; + + var session = new Session(user); + + return Db.Create(session) + ? session + : null; + } +} \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/Methods/Folder.cs b/csharp/App/Backend/DataTypes/Methods/Folder.cs new file mode 100644 index 000000000..404410997 --- /dev/null +++ b/csharp/App/Backend/DataTypes/Methods/Folder.cs @@ -0,0 +1,97 @@ +using System.Collections; +using InnovEnergy.App.Backend.Database; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.App.Backend.DataTypes.Methods; + +public static class FolderMethods +{ + + public static IEnumerable UsersWithAccess(this Folder folder) + { + return UsersWithDirectAccess(folder).Concat(UsersWithInheritedAccess(folder)); + } + + public static IEnumerable UsersWithDirectAccess(this Folder folder) + { + return Db.FolderAccess + .Where(access => access.FolderId == folder.Id) + .Select(access => Db.GetUserById(access.UserId)) + .NotNull(); + } + + public static IEnumerable UsersWithInheritedAccess(this Folder folder) + { + return folder.Ancestors().SelectMany(f => f.UsersWithDirectAccess()).NotNull(); + } + + public static IEnumerable ChildFolders(this Folder parent) + { + return Db + .Folders + .Where(f => f.ParentId == parent.Id); + } + + public static IEnumerable ChildInstallations(this Folder parent) + { + return Db + .Installations + .Where(f => f.ParentId == parent.Id); + } + + public static IEnumerable DescendantFolders(this Folder parent) + { + return parent + .TraverseDepthFirstPreOrder(ChildFolders) + .Skip(1); // skip self + } + + public static Boolean IsDescendantOf(this Folder folder, Folder ancestor) + { + return folder + .Ancestors() + .Any(u => u.Id == ancestor.Id); + } + + public static IEnumerable Ancestors(this Folder folder) + { + return folder + .Unfold(Parent) + .Skip(1); // skip self + } + + public static Folder? Parent(this Folder folder) + { + return IsAbsoluteRoot(folder) + ? null + : Db.GetFolderById(folder.ParentId); + } + + public static Boolean IsAbsoluteRoot(this Folder folder) + { + return folder.ParentId == 0; // root has ParentId 0 by definition + } + + public static Boolean IsRelativeRoot(this Folder folder) + { + return folder.ParentId < 0; + } + + public static Boolean WasMoved(this Folder folder) + { + if (folder.IsRelativeRoot()) + return false; + + var existingFolder = Db.GetFolderById(folder.Id); + + return existingFolder is not null + && existingFolder.ParentId != folder.ParentId; + } + + public static Boolean Exists(this Folder folder) + { + return Db.Folders.Any(f => f.Id == folder.Id); + } + +} + diff --git a/csharp/App/Backend/DataTypes/Methods/Installation.cs b/csharp/App/Backend/DataTypes/Methods/Installation.cs new file mode 100644 index 000000000..608827041 --- /dev/null +++ b/csharp/App/Backend/DataTypes/Methods/Installation.cs @@ -0,0 +1,145 @@ +using CliWrap; +using CliWrap.Buffered; +using InnovEnergy.App.Backend.Database; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.App.Backend.DataTypes.Methods; + + +public static class InstallationMethods +{ + public static async Task RenewS3BucketUrl(this Installation installation) + { + await RenewS3BucketUrl(installation, TimeSpan.FromDays(1)); + } + + public static async Task RenewS3BucketUrl(this Installation installation, TimeSpan validity) + { + const String secret = "55MAqyO_FqUmh7O64VIO0egq50ERn_WIAWuc2QC44QU"; + const String apiKey = "EXO44d2979c8e570eae81ead564"; + const String salt = "3e5b3069-214a-43ee-8d85-57d72000c19d"; + var cmd = Cli + .Wrap("python3") + .WithArguments(new[] + { + "Resources/s3cmd.py", "signurl", $"s3://{installation.Id}-{salt}", validity.TotalSeconds.ToString(), "--access_key", + apiKey, "--secret_key", secret + }); + var x = await cmd.ExecuteBufferedAsync(); + installation.S3Url = x.StandardOutput.Replace("\n", "").Replace(" ", ""); + + Db.Update(installation); + } + + + public static async Task CreateBucket(this Installation installation) + { + const String secret = "-T9TAqy9a3-0-xj7HKsFFJOCcxfRpcnL6OW5oOrOcWU"; + + const String apiKey = "EXO87ca85e29dd412f1238f1cf0"; + const String salt = "3e5b3069-214a-43ee-8d85-57d72000c19d"; + + var cmd = Cli + .Wrap("python3") + .WithArguments(new[] + { + "Resources/s3cmd.py", "mb", $"s3://{installation.Id}-{salt}", "--access_key", + apiKey, "--secret_key", secret + }); + var x = await cmd.ExecuteBufferedAsync(); + + //Updating the url in the db as not wait until the next bi-daily update + var cmd2 = Cli + .Wrap("python3") + .WithArguments(new[] + { + "Resources/s3cmd.py", "signurl", $"s3://{installation.Id}-{salt}", + TimeSpan.FromDays(1).TotalSeconds.ToString(), "--access_key", + apiKey, "--secret_key", secret + }); + + var y = await cmd2.ExecuteBufferedAsync(); + installation.S3Url = y.StandardOutput.Replace("\n", "").Replace(" ", ""); + + Db.Update(installation); + + return x.ExitCode == 0; + } + + public static async Task DeleteBucket(this Installation installation) + { + const String secret = "-T9TAqy9a3-0-xj7HKsFFJOCcxfRpcnL6OW5oOrOcWU"; + const String apiKey = "EXO87ca85e29dd412f1238f1cf0"; + const String salt = "3e5b3069-214a-43ee-8d85-57d72000c19d"; + var cmd = Cli + .Wrap("python3") + .WithArguments(new[] + { + "Resources/s3cmd.py", "rb", $"s3://{installation.Id}-{salt}", "--access_key", + apiKey, "--secret_key", secret + }); + var x = await cmd.ExecuteBufferedAsync(); + return x.ExitCode == 0; + } + + public static IEnumerable UsersWithAccess(this Installation installation) + { + return UsersWithDirectAccess(installation).Concat(UsersWithInheritedAccess(installation)); + } + + public static IEnumerable UsersWithDirectAccess(this Installation installation) + { + return Db.InstallationAccess + .Where(access => access.InstallationId == installation.Id) + .Select(access => Db.GetUserById(access.UserId)) + .NotNull(); + } + + public static IEnumerable UsersWithInheritedAccess(this Installation installation) + { + return installation.Ancestors().SelectMany(f => f.UsersWithDirectAccess()).NotNull(); + } + + public static IEnumerable Ancestors(this Installation installation) + { + var parentFolder = Parent(installation); + + if (parentFolder is null) + return Enumerable.Empty(); + + return parentFolder + .Ancestors() + .Prepend(parentFolder); + } + + public static Folder? Parent(this Installation installation) + { + return installation.IsRelativeRoot() + ? null + : Db.GetFolderById(installation.ParentId); + } + + public static Boolean IsRelativeRoot(this Installation i) + { + return i.ParentId < 0; + } + + public static Boolean WasMoved(this Installation installation) + { + if (installation.IsRelativeRoot()) + return false; + + 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); + } + + +} + diff --git a/csharp/App/Backend/DataTypes/Methods/Session.cs b/csharp/App/Backend/DataTypes/Methods/Session.cs new file mode 100644 index 000000000..5b8dfae0a --- /dev/null +++ b/csharp/App/Backend/DataTypes/Methods/Session.cs @@ -0,0 +1,186 @@ +using InnovEnergy.App.Backend.Database; +using InnovEnergy.App.Backend.Relations; + +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.HasWriteAccess + && user.HasAccessTo(folder.Parent()) + && Db.Create(folder) + && Db.Create(new FolderAccess() { UserId = user.Id, FolderId = folder.Id }); + } + + public static Boolean Update(this Session? session, Folder? folder) + { + var user = session?.User; + + return user is not null + && folder is not null + && user.HasWriteAccess + && user.HasAccessTo(folder) + && (folder.IsRelativeRoot() || user.HasAccessTo(folder.Parent())) + && Db.Update(folder); + } + + public static Boolean Delete(this Session? session, Folder? folder) + { + var user = session?.User; + + return user is not null + && folder is not null + && user.HasWriteAccess + && user.HasAccessTo(folder) + && Db.Delete(folder); + } + + + public static Boolean Create(this Session? session, Installation? installation) + { + var user = session?.User; + + //Note: keep generation of access _after_ generation of object to prevent "zombie" access-rights. + + return user is not null + && installation is not null + && user.HasWriteAccess + && user.HasAccessTo(installation.Parent()) + && Db.Create(installation) + && installation.CreateBucket().Result + && Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id }); + } + + public static Boolean Update(this Session? session, Installation? installation) + { + var user = session?.User; + + return user is not null + && installation is not null + && user.HasWriteAccess + && installation.Exists() + && user.HasAccessTo(installation) + && (installation.IsRelativeRoot() || user.HasAccessTo(installation.Parent())) // TODO: triple check this + && Db.Update(installation); + } + + public static Boolean Delete(this Session? session, Installation? installation) + { + var user = session?.User; + + return user is not null + && installation is not null + && user.HasWriteAccess + && user.HasAccessTo(installation) + && Db.Delete(installation); + } + + public static Boolean Create(this Session? session, User? newUser) + { + var sessionUser = session?.User; + + if (sessionUser is null || newUser is null || !sessionUser.HasWriteAccess) + return false; + + newUser.ParentId = sessionUser.Id; // Important! + + return Db.Create(newUser); + } + + public static Boolean Update(this Session? session, User? editedUser) + { + var sessionUser = session?.User; + if (editedUser == null || sessionUser == null) return false; + + + //Password change is only allowed for oneself + if ( editedUser.Id != sessionUser.Id) editedUser.Password = sessionUser.Password; + else + { + editedUser.Password = sessionUser.SaltAndHashPassword(editedUser.Password); + } + + return sessionUser.HasWriteAccess + && sessionUser.HasAccessTo(editedUser) + && (editedUser.IsRelativeRoot() || sessionUser.HasAccessTo(editedUser.Parent()) || editedUser.Id == sessionUser.Id) // TODO: triple check this + && Db.Update(editedUser); + } + + public static Boolean Delete(this Session? session, User? userToDelete) + { + var sessionUser = session?.User; + + return sessionUser is not null + && userToDelete is not null + && sessionUser.HasWriteAccess + && sessionUser.HasAccessTo(userToDelete) // TODO: && user.HasAccessTo(installation.Parent()) ??? + && Db.Delete(userToDelete); + } + + + public static Boolean GrantUserAccessTo(this Session? session, User? user, Installation? installation) + { + var sessionUser = session?.User; + + return sessionUser is not null + && user is not null + && installation 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 + && user is not null + && folder 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 RevokeAccessTo(this Session? session, User? user, Installation? installation) + { + var sessionUser = session?.User; + + return sessionUser is not null + && user is not null + && installation is not null + && user.IsDescendantOf(sessionUser) + && sessionUser.HasAccessTo(installation) + && user.HasAccessTo(installation) + && Db.InstallationAccess.Delete(access => + access.UserId == user.Id && access.InstallationId == installation.Id) > 0; + } + + public static Boolean RevokeAccessTo(this Session? session, User? user, Folder? folder) + { + var sessionUser = session?.User; + + return sessionUser is not null + && user is not null + && folder is not null + && user.IsDescendantOf(sessionUser) + && sessionUser.HasAccessTo(folder) + && user.HasAccessTo(folder) + && Db.FolderAccess.Delete(access => + access.UserId == user.Id && access.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/Backend/DataTypes/Methods/User.cs b/csharp/App/Backend/DataTypes/Methods/User.cs new file mode 100644 index 000000000..f0d2c347f --- /dev/null +++ b/csharp/App/Backend/DataTypes/Methods/User.cs @@ -0,0 +1,205 @@ +using System.Net.Mail; +using System.Security.Cryptography; +using InnovEnergy.App.Backend.Database; +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 AccessibleInstallations(this User user) + { + var direct = user.DirectlyAccessibleInstallations(); + var fromFolders = user + .AccessibleFolders() + .SelectMany(u => u.ChildInstallations()); + + return direct + .Concat(fromFolders) + .Distinct(); + } + + public static IEnumerable AccessibleFolders(this User user) + { + return user + .DirectlyAccessibleFolders() + .SelectMany(f => f.DescendantFolders().Prepend(f)) + .Distinct(); + + // Distinct because the user might have direct access + // to a child folder of a folder he has already access to + // ---TODO shouldn't we prevent doubling permissions? -K" + } + + public static IEnumerable AccessibleFoldersAndInstallations(this User user) + { + var folders = user.AccessibleFolders() as IEnumerable; + var installations = user.AccessibleInstallations(); + + return folders.Concat(installations); + } + + public static IEnumerable DirectlyAccessibleInstallations(this User user) + { + return Db + .InstallationAccess + .Where(r => r.UserId == user.Id) + .Select(r => r.InstallationId) + .Select(Db.GetInstallationById) + .NotNull() + .Do(i => i.ParentId = 0); // hide inaccessible parents from calling user + } + + public static IEnumerable DirectlyAccessibleFolders(this User user) + { + return Db + .FolderAccess + .Where(r => r.UserId == user.Id) + .Select(r => r.FolderId) + .Select(Db.GetFolderById) + .NotNull() + .Do(i => i.ParentId = 0); // hide inaccessible parents from calling user; + } + + public static IEnumerable ChildUsers(this User parent) + { + return Db + .Users + .Where(f => f.ParentId == parent.Id); + } + + public static IEnumerable DescendantUsers(this User parent) + { + return parent + .TraverseDepthFirstPreOrder(ChildUsers) + .Skip(1); // skip self + } + + public static Boolean IsDescendantOf(this User user, User ancestor) + { + // if (user.Id == ancestor.Id) return true; + return user + .Ancestors() + .Any(u => u.Id == ancestor.Id); + } + + private static IEnumerable Ancestors(this User user) + { + return user + .Unfold(Parent) + .Skip(1); // skip self + } + + public static Boolean VerifyPassword(this User user, String password) + { + return user.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.IsAbsoluteRoot() + ? null + : Db.GetUserById(u.ParentId); + } + + public static Boolean IsAbsoluteRoot(this User u) + { + return u.ParentId == 0; + } + + public static Boolean IsRelativeRoot(this User u) + { + return u.ParentId < 0; + } + + 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; + + if (other.Id == user.Id) + return true; + + return other + .Ancestors() + .Contains(user); + } + + public static Boolean IsRelativeRoot(this User user, Installation i) + { + // TODO: determine not by id but by accessibility + return i.ParentId < 0; + } + + public static String Salt(this User user) + { + // + id => salt unique per user + // + InnovEnergy => globally unique + + return $"{user.Id}InnovEnergy"; + } + + + // 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/DataTypes/TreeNode.Equality.cs b/csharp/App/Backend/DataTypes/TreeNode.Equality.cs new file mode 100644 index 000000000..e9c6767df --- /dev/null +++ b/csharp/App/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/Backend/DataTypes/TreeNode.cs b/csharp/App/Backend/DataTypes/TreeNode.cs new file mode 100644 index 000000000..de24da0b8 --- /dev/null +++ b/csharp/App/Backend/DataTypes/TreeNode.cs @@ -0,0 +1,18 @@ +using SQLite; + +namespace InnovEnergy.App.Backend.DataTypes; + +public abstract partial class TreeNode +{ + [PrimaryKey, AutoIncrement] + public Int64 Id { get; set; } + public String Name { get; set; } = ""; + public String Information { get; set; } = ""; // unstructured random info + + [Indexed] // parent/child relation + public Int64 ParentId { get; set; } + + [Ignore] + public String Type => GetType().Name; + +} \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/User.cs b/csharp/App/Backend/DataTypes/User.cs new file mode 100644 index 000000000..70ea9e034 --- /dev/null +++ b/csharp/App/Backend/DataTypes/User.cs @@ -0,0 +1,14 @@ +using SQLite; + +namespace InnovEnergy.App.Backend.DataTypes; + +public class User : TreeNode +{ + [Indexed] + public String Email { get; set; } = null!; + public Boolean HasWriteAccess { get; set; } = false; + public String Language { get; set; } = null!; + public String Password { get; set; } = null!; + + // TODO: must reset pwd +} \ No newline at end of file diff --git a/csharp/App/Backend/Database/Create.cs b/csharp/App/Backend/Database/Create.cs new file mode 100644 index 000000000..d1b6d8318 --- /dev/null +++ b/csharp/App/Backend/Database/Create.cs @@ -0,0 +1,46 @@ +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 +{ + 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; + } + + public static Boolean Create(Folder folder) + { + return Connection.Insert(folder) > 0; + } + + public static Boolean Create(User user) + { + if (GetUserByEmail(user.Email) is not null) + return false; + + user.Password = user.SaltAndHashPassword(user.Password); + + return Connection.Insert(user) > 0; + } + + public static Boolean Create(Session session) + { + return Connection.Insert(session) > 0; + } + + public static Boolean Create(InstallationAccess installationAccess) + { + return Connection.Insert(installationAccess) > 0; + } + + public static Boolean Create(FolderAccess folderAccess) + { + return Connection.Insert(folderAccess) > 0; + } +} \ No newline at end of file diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs new file mode 100644 index 000000000..f7eaafd7f --- /dev/null +++ b/csharp/App/Backend/Database/Db.cs @@ -0,0 +1,91 @@ +using System.Reactive.Linq; +using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.App.Backend.DataTypes.Methods; +using InnovEnergy.App.Backend.Relations; +using InnovEnergy.Lib.Utils; +using SQLite; + + + +namespace InnovEnergy.App.Backend.Database; + + +public static partial class Db +{ + internal const String DbPath = "./db.sqlite"; + + private static SQLiteConnection Connection { get; } = new SQLiteConnection(DbPath); + + public static TableQuery Sessions => Connection.Table(); + public static TableQuery Folders => Connection.Table(); + public static TableQuery Installations => Connection.Table(); + public static TableQuery Users => Connection.Table(); + public static TableQuery FolderAccess => Connection.Table(); + public static TableQuery InstallationAccess => Connection.Table(); + + + static Db() + { + // on startup create/migrate tables + + Connection.RunInTransaction(() => + { + Connection.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); + Connection.CreateTable(); + }); + + + + + Observable.Interval(TimeSpan.FromDays(0.5)) + .StartWith(0) // Do it right away (on startup) + .SelectMany(Cleanup) + .Subscribe(); // and then daily + } + + + + private static Boolean RunTransaction(Func 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 Cleanup(Int64 _) + { + await UpdateS3Urls(); + DeleteStaleSessions(); + return true; + } + + private static void DeleteStaleSessions() + { + var deadline = DateTime.Now - Session.MaxAge; + Sessions.Delete(s => s.LastSeen < deadline); + } + + private static Task UpdateS3Urls() + { + var renewTasks = Installations.Select(i => i.RenewS3BucketUrl()).ToArray(); + return Task.WhenAll(renewTasks); + } +} \ No newline at end of file diff --git a/csharp/App/Backend/Database/Delete.cs b/csharp/App/Backend/Database/Delete.cs new file mode 100644 index 000000000..200b63855 --- /dev/null +++ b/csharp/App/Backend/Database/Delete.cs @@ -0,0 +1,63 @@ +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 +{ + public static Boolean Delete(Folder folder) + { + return RunTransaction(DeleteFolderAndAllItsDependencies); + + Boolean DeleteFolderAndAllItsDependencies() + { + return folder + .DescendantFolders() + .All(DeleteDescendantFolderAndItsDependencies); + } + + 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; + } + } + + public static Boolean Delete(Installation installation) + { + return RunTransaction(DeleteInstallationAndItsDependencies); + + Boolean DeleteInstallationAndItsDependencies() + { + InstallationAccess.Delete(i => i.InstallationId == installation.Id); + return Installations.Delete(i => i.Id == installation.Id) > 0; + } + } + + public static Boolean Delete(User user) + { + return RunTransaction(DeleteUserAndHisDependencies); + + 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) + { + return Sessions.Delete(s => s.Id == session.Id) > 0; + } +} \ No newline at end of file diff --git a/csharp/app/Backend/Database/Fake.cs b/csharp/App/Backend/Database/Fake.cs similarity index 58% rename from csharp/app/Backend/Database/Fake.cs rename to csharp/App/Backend/Database/Fake.cs index 94325df6d..af7a4e7ee 100644 --- a/csharp/app/Backend/Database/Fake.cs +++ b/csharp/App/Backend/Database/Fake.cs @@ -1,12 +1,12 @@ -using Backend.Model.Relations; +using InnovEnergy.App.Backend.Relations; -namespace Backend.Database; +namespace InnovEnergy.App.Backend.Database; -public partial class Db +public static partial class Db { - public void CreateFakeRelations() + public static void CreateFakeRelations() { - _Db.RunInTransaction(() => + Connection.RunInTransaction(() => { CreateFakeUserTree(); CreateFakeFolderTree(); @@ -16,9 +16,9 @@ public partial class Db }); } - private void CreateFakeUserTree() + private static void CreateFakeUserTree() { - foreach (var userId in Enumerable.Range(1, NbUsers)) + foreach (var userId in Enumerable.Range(1, Users.Count())) { var user = GetUserById(userId); if (user is null) @@ -32,9 +32,9 @@ public partial class Db } } - private void CreateFakeFolderTree() + private static void CreateFakeFolderTree() { - foreach (var folderId in Enumerable.Range(1, NbFolders)) + foreach (var folderId in Enumerable.Range(1, Folders.Count())) { var folder = GetFolderById(folderId); if (folder is null) @@ -48,9 +48,9 @@ public partial class Db } } - private void LinkFakeInstallationsToFolders() + private static void LinkFakeInstallationsToFolders() { - var nFolders = NbFolders; + var nFolders = Folders.Count(); foreach (var installation in Installations) { @@ -59,42 +59,42 @@ public partial class Db } } - private void GiveFakeUsersAccessToFolders() + private static void GiveFakeUsersAccessToFolders() { - foreach (var uf in User2Folder) // remove existing relations - _Db.Delete(uf); + foreach (var uf in FolderAccess) // remove existing relations + Connection.Delete(uf); - var nFolders = NbFolders; - var nUsers = NbUsers; + var nFolders = Folders.Count(); + var nUsers = Users.Count(); foreach (var user in Users) while (Random.Shared.Next((Int32)(nUsers - user.Id + 1)) != 0) { - var relation = new User2Folder + var relation = new FolderAccess { UserId = user.Id, FolderId = Random.Shared.Next(nFolders) + 1 }; - _Db.Insert(relation); + Connection.Insert(relation); } } - private void GiveFakeUsersAccessToInstallations() + private static void GiveFakeUsersAccessToInstallations() { - foreach (var ui in User2Installation) // remove existing relations - _Db.Delete(ui); + foreach (var ui in InstallationAccess) // remove existing relations + Connection.Delete(ui); - var nbInstallations = NbInstallations; + var nbInstallations = Installations.Count(); foreach (var user in Users) while (Random.Shared.Next(5) != 0) { - var relation = new User2Installation + var relation = new InstallationAccess { UserId = user.Id, InstallationId = Random.Shared.Next(nbInstallations) + 1 }; - _Db.Insert(relation); + Connection.Insert(relation); } } } \ No newline at end of file diff --git a/csharp/App/Backend/Database/Read.cs b/csharp/App/Backend/Database/Read.cs new file mode 100644 index 000000000..53c0586de --- /dev/null +++ b/csharp/App/Backend/Database/Read.cs @@ -0,0 +1,86 @@ +using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.App.Backend.Relations; + + +namespace InnovEnergy.App.Backend.Database; + + +public static partial class Db +{ + 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 User? GetUserById(Int64? id) + { + return Users + .FirstOrDefault(u => u.Id == id); + } + + // private!! + private static Session? GetSessionById(Int64 id) + { + #pragma warning disable CS0618 + + return Sessions + .FirstOrDefault(u => u.Id == id); + + #pragma warning restore CS0618 + } + + + public static User? GetUserByEmail(String email) + { + return Users + .FirstOrDefault(u => u.Email == email); + } + + public static Session? GetSession(String token) + { + var session = Sessions + .FirstOrDefault(s => s.Token == token); + + // cannot use session.Valid in the DB query above. + // It does not exist in the db (IgnoreAttribute) + + if (session is null) + return null; + + if (!session.Valid) + { + Delete(session); + return null; + } + + return session; + } + + public static User? GetUserBySessionToken(String token) + { + var session = Sessions + .FirstOrDefault(s => s.Token == token); + + // cannot user session.Expired in the DB query above. + // It does not exist in the db (IgnoreAttribute) + + if (session is null) + return null; + + if (!session.Valid) + { + Delete(session); + return null; + } + + return GetUserById(session.UserId); + } + +} \ No newline at end of file diff --git a/csharp/App/Backend/Database/Update.cs b/csharp/App/Backend/Database/Update.cs new file mode 100644 index 000000000..72aef3015 --- /dev/null +++ b/csharp/App/Backend/Database/Update.cs @@ -0,0 +1,63 @@ +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 +{ + public static Boolean Update(Folder folder) + { + if (folder.IsRelativeRoot()) // TODO: triple check + { + var original = GetFolderById(folder.Id); + if (original is null) + return false; + + folder.ParentId = original.ParentId; + } + + return Connection.InsertOrReplace(folder) > 0; + } + + public static Boolean Update(Installation installation) + { + if (installation.IsRelativeRoot()) // TODO: triple check + { + var original = GetInstallationById(installation.Id); + if (original is null) + return false; + + installation.ParentId = original.ParentId; + } + + return Connection.InsertOrReplace(installation) > 0; + } + + + public static Boolean Update(User user) + { + var originalUser = GetUserById(user.Id); + + return originalUser is not null + && user.Id == originalUser.Id // these columns must not be modified! + && user.ParentId == originalUser.ParentId + && user.Email == originalUser.Email + && Connection.InsertOrReplace(user) > 0; + } + + public static Boolean Update(this Session session) + { + #pragma warning disable CS0618 + var originalSession = GetSessionById(session.Id); + #pragma warning restore CS0618 + + return originalSession is not null + && session.Token == originalSession.Token // these columns must not be modified! + && session.UserId == originalSession.UserId + && Connection.InsertOrReplace(session) > 0; + } + +} \ No newline at end of file diff --git a/csharp/app/Backend/FakeFolders.csv b/csharp/App/Backend/FakeFolders.csv similarity index 100% rename from csharp/app/Backend/FakeFolders.csv rename to csharp/App/Backend/FakeFolders.csv diff --git a/csharp/app/Backend/FakeInstallations.csv b/csharp/App/Backend/FakeInstallations.csv similarity index 100% rename from csharp/app/Backend/FakeInstallations.csv rename to csharp/App/Backend/FakeInstallations.csv diff --git a/csharp/app/Backend/FakePasswords.csv b/csharp/App/Backend/FakePasswords.csv similarity index 100% rename from csharp/app/Backend/FakePasswords.csv rename to csharp/App/Backend/FakePasswords.csv diff --git a/csharp/app/Backend/FakeUsers.csv b/csharp/App/Backend/FakeUsers.csv similarity index 100% rename from csharp/app/Backend/FakeUsers.csv rename to csharp/App/Backend/FakeUsers.csv diff --git a/csharp/App/Backend/HeaderFilter.cs b/csharp/App/Backend/HeaderFilter.cs new file mode 100644 index 000000000..f3520b75e --- /dev/null +++ b/csharp/App/Backend/HeaderFilter.cs @@ -0,0 +1,24 @@ +using Microsoft.OpenApi.Models; +using Swashbuckle.AspNetCore.SwaggerGen; + +namespace InnovEnergy.App.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/Program.cs b/csharp/App/Backend/Program.cs new file mode 100644 index 000000000..e7500ff1a --- /dev/null +++ b/csharp/App/Backend/Program.cs @@ -0,0 +1,63 @@ +using InnovEnergy.App.Backend.Database; +using Microsoft.OpenApi.Models; + +namespace InnovEnergy.App.Backend; + +public static class Program +{ + public static void Main(String[] args) + { + 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(c => + { + c.SwaggerDoc("v1", new OpenApiInfo { Title = "InnovEnergy Backend API", Version = "v1" }); + c.UseAllOfToExtendReferenceSchemas(); + c.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) + { + var session = Db.GetSession(token); + + if (session is not null) + ctx.Items["Session"] = session; + } + + await next(ctx); + } +} \ No newline at end of file diff --git a/csharp/app/Backend/Properties/launchSettings.json b/csharp/App/Backend/Properties/launchSettings.json similarity index 100% rename from csharp/app/Backend/Properties/launchSettings.json rename to csharp/App/Backend/Properties/launchSettings.json diff --git a/csharp/App/Backend/Relations/FolderAccess.cs b/csharp/App/Backend/Relations/FolderAccess.cs new file mode 100644 index 000000000..545647a46 --- /dev/null +++ b/csharp/App/Backend/Relations/FolderAccess.cs @@ -0,0 +1,9 @@ +using SQLite; + +namespace InnovEnergy.App.Backend.Relations; + +public class FolderAccess : Relation +{ + [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/Backend/Relations/InstallationAccess.cs b/csharp/App/Backend/Relations/InstallationAccess.cs new file mode 100644 index 000000000..c233ca953 --- /dev/null +++ b/csharp/App/Backend/Relations/InstallationAccess.cs @@ -0,0 +1,9 @@ +using SQLite; + +namespace InnovEnergy.App.Backend.Relations; + +public class InstallationAccess : Relation +{ + [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/Backend/Model/Relations/Relation.cs b/csharp/App/Backend/Relations/Relation.cs similarity index 96% rename from csharp/app/Backend/Model/Relations/Relation.cs rename to csharp/App/Backend/Relations/Relation.cs index 388892872..7e37b87ab 100644 --- a/csharp/app/Backend/Model/Relations/Relation.cs +++ b/csharp/App/Backend/Relations/Relation.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using SQLite; -namespace Backend.Model.Relations; +namespace InnovEnergy.App.Backend.Relations; public abstract class Relation { diff --git a/csharp/App/Backend/Relations/Session.cs b/csharp/App/Backend/Relations/Session.cs new file mode 100644 index 000000000..fa9a993d4 --- /dev/null +++ b/csharp/App/Backend/Relations/Session.cs @@ -0,0 +1,43 @@ +using InnovEnergy.App.Backend.Database; +using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.Lib.Utils; +using SQLite; + +namespace InnovEnergy.App.Backend.Relations; + +public class Session : Relation +{ + public static TimeSpan MaxAge { get; } = TimeSpan.FromDays(7); + + [Unique ] public String Token { get => Left ; init => Left = value;} + [Indexed] public Int64 UserId { get => Right; init => Right = value;} + [Indexed] public DateTime LastSeen { get; set; } + + [Ignore] public Boolean Valid => DateTime.Now - LastSeen < MaxAge + && !User.Email.IsNullOrEmpty(); + + [Ignore] public User User => _User ??= Db.GetUserById(UserId)!; + + + private User? _User; + + [Obsolete("To be used only by deserializer")] + public Session() + {} + + public Session(User user) + { + _User = user; + Token = CreateToken(); + UserId = user.Id; + LastSeen = DateTime.Now; + } + + private static String CreateToken() + { + var token = new Byte[24]; + Random.Shared.NextBytes(token); + return Convert.ToBase64String(token); + } + +} \ No newline at end of file diff --git a/csharp/App/Backend/Resources/s3cmd.py b/csharp/App/Backend/Resources/s3cmd.py new file mode 100755 index 000000000..6691d8797 --- /dev/null +++ b/csharp/App/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 = "" + 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 = "" + 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'' + 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("(.*?)", 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 ", "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(" ", "") + key_inval = key_inval.replace("\t", "") + 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/Backend/db.sqlite b/csharp/App/Backend/db.sqlite new file mode 100644 index 000000000..ea45e7dbd Binary files /dev/null and b/csharp/App/Backend/db.sqlite differ diff --git a/csharp/app/BmsTunnel/BatteryConnection.cs b/csharp/App/BmsTunnel/BatteryConnection.cs similarity index 97% rename from csharp/app/BmsTunnel/BatteryConnection.cs rename to csharp/App/BmsTunnel/BatteryConnection.cs index 1dd0378a6..28dd3ee8a 100644 --- a/csharp/app/BmsTunnel/BatteryConnection.cs +++ b/csharp/App/BmsTunnel/BatteryConnection.cs @@ -1,9 +1,9 @@ using CliWrap; using CliWrap.Buffered; using InnovEnergy.Lib.Utils; -using static InnovEnergy.BmsTunnel.CliPrograms; +using static InnovEnergy.App.BmsTunnel.CliPrograms; -namespace InnovEnergy.BmsTunnel; +namespace InnovEnergy.App.BmsTunnel; using Nodes = IReadOnlyList; diff --git a/csharp/app/BmsTunnel/BmsTunnel.cs b/csharp/App/BmsTunnel/BmsTunnel.cs similarity index 99% rename from csharp/app/BmsTunnel/BmsTunnel.cs rename to csharp/App/BmsTunnel/BmsTunnel.cs index 2af527047..85cefdf14 100644 --- a/csharp/app/BmsTunnel/BmsTunnel.cs +++ b/csharp/App/BmsTunnel/BmsTunnel.cs @@ -3,7 +3,7 @@ using System.Text; using CliWrap.Buffered; using InnovEnergy.Lib.Utils; -namespace InnovEnergy.BmsTunnel; +namespace InnovEnergy.App.BmsTunnel; public class BmsTunnel : IDisposable diff --git a/csharp/app/BmsTunnel/BmsTunnel.csproj b/csharp/App/BmsTunnel/BmsTunnel.csproj similarity index 53% rename from csharp/app/BmsTunnel/BmsTunnel.csproj rename to csharp/App/BmsTunnel/BmsTunnel.csproj index 6078a616a..0466a4173 100644 --- a/csharp/app/BmsTunnel/BmsTunnel.csproj +++ b/csharp/App/BmsTunnel/BmsTunnel.csproj @@ -1,9 +1,5 @@ - - - - InnovEnergy.BmsTunnel - + @@ -11,7 +7,7 @@ - + diff --git a/csharp/app/BmsTunnel/CliPrograms.cs b/csharp/App/BmsTunnel/CliPrograms.cs similarity index 91% rename from csharp/app/BmsTunnel/CliPrograms.cs rename to csharp/App/BmsTunnel/CliPrograms.cs index 98588b622..c27df92df 100644 --- a/csharp/app/BmsTunnel/CliPrograms.cs +++ b/csharp/App/BmsTunnel/CliPrograms.cs @@ -1,6 +1,6 @@ using CliWrap; -namespace InnovEnergy.BmsTunnel; +namespace InnovEnergy.App.BmsTunnel; public static class CliPrograms { diff --git a/csharp/app/BmsTunnel/Program.cs b/csharp/App/BmsTunnel/Program.cs similarity index 98% rename from csharp/app/BmsTunnel/Program.cs rename to csharp/App/BmsTunnel/Program.cs index 5f2d42de7..c3ce8f71e 100644 --- a/csharp/app/BmsTunnel/Program.cs +++ b/csharp/App/BmsTunnel/Program.cs @@ -5,7 +5,7 @@ using InnovEnergy.Lib.Utils; using static System.String; -namespace InnovEnergy.BmsTunnel; +namespace InnovEnergy.App.BmsTunnel; public static class Program { diff --git a/csharp/app/BmsTunnel/debug.sh b/csharp/App/BmsTunnel/debug.sh similarity index 100% rename from csharp/app/BmsTunnel/debug.sh rename to csharp/App/BmsTunnel/debug.sh diff --git a/csharp/app/BmsTunnel/parameters.txt b/csharp/App/BmsTunnel/parameters.txt similarity index 100% rename from csharp/app/BmsTunnel/parameters.txt rename to csharp/App/BmsTunnel/parameters.txt diff --git a/csharp/App/Collector/Collector.csproj b/csharp/App/Collector/Collector.csproj new file mode 100644 index 000000000..27b560c8d --- /dev/null +++ b/csharp/App/Collector/Collector.csproj @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/csharp/app/Collector/Collector.sln b/csharp/App/Collector/Collector.sln similarity index 100% rename from csharp/app/Collector/Collector.sln rename to csharp/App/Collector/Collector.sln diff --git a/csharp/app/Collector/src/BatteryDataParser.cs b/csharp/App/Collector/src/BatteryDataParser.cs similarity index 98% rename from csharp/app/Collector/src/BatteryDataParser.cs rename to csharp/App/Collector/src/BatteryDataParser.cs index 81c84313e..23e0c2bfb 100644 --- a/csharp/app/Collector/src/BatteryDataParser.cs +++ b/csharp/App/Collector/src/BatteryDataParser.cs @@ -1,14 +1,14 @@ using System.Net; using System.Text; -using InnovEnergy.Collector.Influx; -using InnovEnergy.Collector.Records; -using InnovEnergy.Collector.Utils; +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.Collector; +namespace InnovEnergy.App.Collector; using Data = IReadOnlyList; diff --git a/csharp/app/Collector/src/BatteryDataParserV4.cs b/csharp/App/Collector/src/BatteryDataParserV4.cs similarity index 98% rename from csharp/app/Collector/src/BatteryDataParserV4.cs rename to csharp/App/Collector/src/BatteryDataParserV4.cs index dc3064f0c..8d8ae5bd0 100644 --- a/csharp/app/Collector/src/BatteryDataParserV4.cs +++ b/csharp/App/Collector/src/BatteryDataParserV4.cs @@ -1,15 +1,15 @@ using System.Net; using System.Text; -using InnovEnergy.Collector.Influx; -using InnovEnergy.Collector.Records; -using InnovEnergy.Collector.Utils; +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.Collector; +namespace InnovEnergy.App.Collector; using Data = IReadOnlyList; diff --git a/csharp/app/Collector/src/Influx/FieldAttribute.cs b/csharp/App/Collector/src/Influx/FieldAttribute.cs similarity index 86% rename from csharp/app/Collector/src/Influx/FieldAttribute.cs rename to csharp/App/Collector/src/Influx/FieldAttribute.cs index 9f8438ac7..58dc3d0ba 100644 --- a/csharp/app/Collector/src/Influx/FieldAttribute.cs +++ b/csharp/App/Collector/src/Influx/FieldAttribute.cs @@ -2,7 +2,7 @@ using static System.AttributeTargets; #nullable disable -namespace InnovEnergy.Collector.Influx; +namespace InnovEnergy.App.Collector.Influx; [AttributeUsage(Property)] public class FieldAttribute : Attribute diff --git a/csharp/app/Collector/src/Influx/InfluxRecord.cs b/csharp/App/Collector/src/Influx/InfluxRecord.cs similarity index 93% rename from csharp/app/Collector/src/Influx/InfluxRecord.cs rename to csharp/App/Collector/src/Influx/InfluxRecord.cs index 4043f0837..6406ea876 100644 --- a/csharp/app/Collector/src/Influx/InfluxRecord.cs +++ b/csharp/App/Collector/src/Influx/InfluxRecord.cs @@ -1,10 +1,10 @@ using System.Text; -using InnovEnergy.Collector.Utils; +using InnovEnergy.App.Collector.Utils; using InnovEnergy.Lib.Utils; using static System.Globalization.CultureInfo; -using static InnovEnergy.Collector.Influx.LineProtocolSyntax; +using static InnovEnergy.App.Collector.Influx.LineProtocolSyntax; -namespace InnovEnergy.Collector.Influx; +namespace InnovEnergy.App.Collector.Influx; public static class InfluxRecord { diff --git a/csharp/app/Collector/src/Influx/LineProtocolSyntax.cs b/csharp/App/Collector/src/Influx/LineProtocolSyntax.cs similarity index 98% rename from csharp/app/Collector/src/Influx/LineProtocolSyntax.cs rename to csharp/App/Collector/src/Influx/LineProtocolSyntax.cs index 95b1a2bd6..2c55b3725 100644 --- a/csharp/app/Collector/src/Influx/LineProtocolSyntax.cs +++ b/csharp/App/Collector/src/Influx/LineProtocolSyntax.cs @@ -1,7 +1,7 @@ using System.Diagnostics.CodeAnalysis; using static System.Globalization.CultureInfo; -namespace InnovEnergy.Collector.Influx; +namespace InnovEnergy.App.Collector.Influx; internal static class LineProtocolSyntax { diff --git a/csharp/app/Collector/src/Influx/TagAttribute.cs b/csharp/App/Collector/src/Influx/TagAttribute.cs similarity index 70% rename from csharp/app/Collector/src/Influx/TagAttribute.cs rename to csharp/App/Collector/src/Influx/TagAttribute.cs index 3db4ee3a1..db028d6a8 100644 --- a/csharp/app/Collector/src/Influx/TagAttribute.cs +++ b/csharp/App/Collector/src/Influx/TagAttribute.cs @@ -1,6 +1,6 @@ using static System.AttributeTargets; -namespace InnovEnergy.Collector.Influx; +namespace InnovEnergy.App.Collector.Influx; [AttributeUsage(Property)] public class TagAttribute : Attribute diff --git a/csharp/app/Collector/src/Program.cs b/csharp/App/Collector/src/Program.cs similarity index 97% rename from csharp/app/Collector/src/Program.cs rename to csharp/App/Collector/src/Program.cs index 01d02ce43..28b58dbcf 100644 --- a/csharp/app/Collector/src/Program.cs +++ b/csharp/App/Collector/src/Program.cs @@ -2,17 +2,17 @@ using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Subjects; -using InnovEnergy.Collector.Influx; -using InnovEnergy.Collector.Records; -using InnovEnergy.Lib.Utils; -using static System.Text.Encoding; -using static InnovEnergy.Lib.Utils.ExceptionHandling; 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.Collector; +namespace InnovEnergy.App.Collector; // TODO: net6 diff --git a/csharp/app/Collector/src/Records/Alarms.cs b/csharp/App/Collector/src/Records/Alarms.cs similarity index 95% rename from csharp/app/Collector/src/Records/Alarms.cs rename to csharp/App/Collector/src/Records/Alarms.cs index 959855519..793680e82 100644 --- a/csharp/app/Collector/src/Records/Alarms.cs +++ b/csharp/App/Collector/src/Records/Alarms.cs @@ -1,10 +1,10 @@ -using InnovEnergy.Collector.Influx; +using InnovEnergy.App.Collector.Influx; // ReSharper disable IdentifierTypo // ReSharper disable UnusedAutoPropertyAccessor.Global // ReSharper disable InconsistentNaming -namespace InnovEnergy.Collector.Records; +namespace InnovEnergy.App.Collector.Records; public class Alarms : BatteryRecord { diff --git a/csharp/App/Collector/src/Records/BatteryRecord.cs b/csharp/App/Collector/src/Records/BatteryRecord.cs new file mode 100644 index 000000000..49cfcc00c --- /dev/null +++ b/csharp/App/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/Collector/src/Records/BatteryStatus.cs b/csharp/App/Collector/src/Records/BatteryStatus.cs similarity index 93% rename from csharp/app/Collector/src/Records/BatteryStatus.cs rename to csharp/App/Collector/src/Records/BatteryStatus.cs index 25f2a6ab3..2816b7545 100644 --- a/csharp/app/Collector/src/Records/BatteryStatus.cs +++ b/csharp/App/Collector/src/Records/BatteryStatus.cs @@ -1,9 +1,9 @@ -using InnovEnergy.Collector.Influx; +using InnovEnergy.App.Collector.Influx; // ReSharper disable UnusedAutoPropertyAccessor.Global // ReSharper disable MemberCanBePrivate.Global -namespace InnovEnergy.Collector.Records; +namespace InnovEnergy.App.Collector.Records; public class BatteryStatus : BatteryRecord { diff --git a/csharp/app/Collector/src/Records/Error.cs b/csharp/App/Collector/src/Records/Error.cs similarity index 69% rename from csharp/app/Collector/src/Records/Error.cs rename to csharp/App/Collector/src/Records/Error.cs index 3d151af2e..9ffa399ef 100644 --- a/csharp/app/Collector/src/Records/Error.cs +++ b/csharp/App/Collector/src/Records/Error.cs @@ -1,8 +1,8 @@ -using InnovEnergy.Collector.Influx; +using InnovEnergy.App.Collector.Influx; // ReSharper disable UnusedAutoPropertyAccessor.Global -namespace InnovEnergy.Collector.Records; +namespace InnovEnergy.App.Collector.Records; public class Error : BatteryRecord { diff --git a/csharp/app/Collector/src/Records/InstallationStatus.cs b/csharp/App/Collector/src/Records/InstallationStatus.cs similarity index 97% rename from csharp/app/Collector/src/Records/InstallationStatus.cs rename to csharp/App/Collector/src/Records/InstallationStatus.cs index 99937ddb7..a93a4374c 100644 --- a/csharp/app/Collector/src/Records/InstallationStatus.cs +++ b/csharp/App/Collector/src/Records/InstallationStatus.cs @@ -1,6 +1,6 @@ -using InnovEnergy.Collector.Influx; +using InnovEnergy.App.Collector.Influx; -namespace InnovEnergy.Collector.Records; +namespace InnovEnergy.App.Collector.Records; public class InstallationStatus : BatteryRecord { diff --git a/csharp/app/Collector/src/Records/IoStatus.cs b/csharp/App/Collector/src/Records/IoStatus.cs similarity index 89% rename from csharp/app/Collector/src/Records/IoStatus.cs rename to csharp/App/Collector/src/Records/IoStatus.cs index 4d0791219..a9c2c7247 100644 --- a/csharp/app/Collector/src/Records/IoStatus.cs +++ b/csharp/App/Collector/src/Records/IoStatus.cs @@ -1,8 +1,8 @@ -using InnovEnergy.Collector.Influx; +using InnovEnergy.App.Collector.Influx; // ReSharper disable UnusedAutoPropertyAccessor.Global -namespace InnovEnergy.Collector.Records; +namespace InnovEnergy.App.Collector.Records; public class IoStatus : BatteryRecord { diff --git a/csharp/app/Collector/src/Records/Leds.cs b/csharp/App/Collector/src/Records/Leds.cs similarity index 86% rename from csharp/app/Collector/src/Records/Leds.cs rename to csharp/App/Collector/src/Records/Leds.cs index 39a7e97be..6dc3170cb 100644 --- a/csharp/app/Collector/src/Records/Leds.cs +++ b/csharp/App/Collector/src/Records/Leds.cs @@ -1,9 +1,9 @@ -using InnovEnergy.Collector.Influx; +using InnovEnergy.App.Collector.Influx; // ReSharper disable UnusedAutoPropertyAccessor.Global // ReSharper disable UnusedMember.Global -namespace InnovEnergy.Collector.Records; +namespace InnovEnergy.App.Collector.Records; public class Leds : BatteryRecord { diff --git a/csharp/app/Collector/src/Records/Temperatures.cs b/csharp/App/Collector/src/Records/Temperatures.cs similarity index 87% rename from csharp/app/Collector/src/Records/Temperatures.cs rename to csharp/App/Collector/src/Records/Temperatures.cs index 6b621084d..34d53a92e 100644 --- a/csharp/app/Collector/src/Records/Temperatures.cs +++ b/csharp/App/Collector/src/Records/Temperatures.cs @@ -1,7 +1,6 @@ -using InnovEnergy.Collector.Influx; +using InnovEnergy.App.Collector.Influx; - -namespace InnovEnergy.Collector.Records; +namespace InnovEnergy.App.Collector.Records; #pragma warning disable CS8618 diff --git a/csharp/app/Collector/src/Records/Warnings.cs b/csharp/App/Collector/src/Records/Warnings.cs similarity index 92% rename from csharp/app/Collector/src/Records/Warnings.cs rename to csharp/App/Collector/src/Records/Warnings.cs index c74aee213..3d5d2a176 100644 --- a/csharp/app/Collector/src/Records/Warnings.cs +++ b/csharp/App/Collector/src/Records/Warnings.cs @@ -1,7 +1,6 @@ -using InnovEnergy.Collector.Influx; +using InnovEnergy.App.Collector.Influx; - -namespace InnovEnergy.Collector.Records; +namespace InnovEnergy.App.Collector.Records; #pragma warning disable CS8618 diff --git a/csharp/app/Collector/src/Settings.cs b/csharp/App/Collector/src/Settings.cs similarity index 91% rename from csharp/app/Collector/src/Settings.cs rename to csharp/App/Collector/src/Settings.cs index 10502f536..7708dc819 100644 --- a/csharp/app/Collector/src/Settings.cs +++ b/csharp/App/Collector/src/Settings.cs @@ -1,6 +1,6 @@ using System.Net; -namespace InnovEnergy.Collector; +namespace InnovEnergy.App.Collector; public static class Settings { diff --git a/csharp/app/Collector/src/Utils/Extensions.cs b/csharp/App/Collector/src/Utils/Extensions.cs similarity index 95% rename from csharp/app/Collector/src/Utils/Extensions.cs rename to csharp/App/Collector/src/Utils/Extensions.cs index 3371ec718..9b79e4ab4 100644 --- a/csharp/app/Collector/src/Utils/Extensions.cs +++ b/csharp/App/Collector/src/Utils/Extensions.cs @@ -1,6 +1,6 @@ using System.Globalization; -namespace InnovEnergy.Collector.Utils; +namespace InnovEnergy.App.Collector.Utils; public static class Extensions { diff --git a/csharp/app/Collector/src/Utils/Log.cs b/csharp/App/Collector/src/Utils/Log.cs similarity index 94% rename from csharp/app/Collector/src/Utils/Log.cs rename to csharp/App/Collector/src/Utils/Log.cs index 316f41af3..8e32f0174 100644 --- a/csharp/app/Collector/src/Utils/Log.cs +++ b/csharp/App/Collector/src/Utils/Log.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Collector.Utils; +namespace InnovEnergy.App.Collector.Utils; internal static class Log { diff --git a/csharp/app/Collector/src/Utils/Property.cs b/csharp/App/Collector/src/Utils/Property.cs similarity index 97% rename from csharp/app/Collector/src/Utils/Property.cs rename to csharp/App/Collector/src/Utils/Property.cs index 89d0aa012..ccf4b45ac 100644 --- a/csharp/app/Collector/src/Utils/Property.cs +++ b/csharp/App/Collector/src/Utils/Property.cs @@ -1,7 +1,7 @@ using System.Reflection; using InnovEnergy.Lib.Utils; -namespace InnovEnergy.Collector.Utils; +namespace InnovEnergy.App.Collector.Utils; public readonly struct Property { diff --git a/csharp/app/Collector/src/Utils/ReadOnlyListExtensions.cs b/csharp/App/Collector/src/Utils/ReadOnlyListExtensions.cs similarity index 95% rename from csharp/app/Collector/src/Utils/ReadOnlyListExtensions.cs rename to csharp/App/Collector/src/Utils/ReadOnlyListExtensions.cs index ec53ff191..7c8d915b3 100644 --- a/csharp/app/Collector/src/Utils/ReadOnlyListExtensions.cs +++ b/csharp/App/Collector/src/Utils/ReadOnlyListExtensions.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Collector.Utils; +namespace InnovEnergy.App.Collector.Utils; public static class ReadOnlyListExtensions { diff --git a/csharp/app/Collector/src/Utils/Utils.cs b/csharp/App/Collector/src/Utils/Utils.cs similarity index 90% rename from csharp/app/Collector/src/Utils/Utils.cs rename to csharp/App/Collector/src/Utils/Utils.cs index fd47cc583..51c9d3fbb 100644 --- a/csharp/app/Collector/src/Utils/Utils.cs +++ b/csharp/App/Collector/src/Utils/Utils.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Collector.Utils; +namespace InnovEnergy.App.Collector.Utils; public static class Utils { diff --git a/csharp/App/EmuMeterDriver/Config.cs b/csharp/App/EmuMeterDriver/Config.cs new file mode 100644 index 000000000..0b5182735 --- /dev/null +++ b/csharp/App/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 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.0m, "/Ac/Voltage", "0.0 A"), + + new(s => s.Ac.L1.ActivePower, "/Ac/L1/Power", "0 W"), + new(s => s.Ac.L2.ActivePower, "/Ac/L2/Power", "0 W"), + new(s => s.Ac.L3.ActivePower, "/Ac/L3/Power", "0 W"), + new(s => s.Ac.ActivePower, "/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/EmuMeterDriver/EmuMeterDriver.cs b/csharp/App/EmuMeterDriver/EmuMeterDriver.cs similarity index 98% rename from csharp/app/EmuMeterDriver/EmuMeterDriver.cs rename to csharp/App/EmuMeterDriver/EmuMeterDriver.cs index 6e923678a..9c57f2c7a 100644 --- a/csharp/app/EmuMeterDriver/EmuMeterDriver.cs +++ b/csharp/App/EmuMeterDriver/EmuMeterDriver.cs @@ -5,8 +5,7 @@ using InnovEnergy.Lib.Protocols.Modbus.Clients; using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Victron.VeDBus; - -namespace InnovEnergy.EmuMeter; +namespace InnovEnergy.App.EmuMeterDriver; public static class EmuMeterDriver { diff --git a/csharp/App/EmuMeterDriver/EmuMeterDriver.csproj b/csharp/App/EmuMeterDriver/EmuMeterDriver.csproj new file mode 100644 index 000000000..b2f8696ed --- /dev/null +++ b/csharp/App/EmuMeterDriver/EmuMeterDriver.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/csharp/app/EmuMeterDriver/Nic.cs b/csharp/App/EmuMeterDriver/Nic.cs similarity index 98% rename from csharp/app/EmuMeterDriver/Nic.cs rename to csharp/App/EmuMeterDriver/Nic.cs index 9ddb9096e..0b81b0843 100644 --- a/csharp/app/EmuMeterDriver/Nic.cs +++ b/csharp/App/EmuMeterDriver/Nic.cs @@ -2,7 +2,7 @@ using System.Text.Json.Nodes; using CliWrap; using CliWrap.Buffered; -namespace InnovEnergy.EmuMeter; +namespace InnovEnergy.App.EmuMeterDriver; public readonly struct Nic { diff --git a/csharp/app/EmuMeterDriver/Program.cs b/csharp/App/EmuMeterDriver/Program.cs similarity index 96% rename from csharp/app/EmuMeterDriver/Program.cs rename to csharp/App/EmuMeterDriver/Program.cs index 8a8c70561..6ad63d04d 100644 --- a/csharp/app/EmuMeterDriver/Program.cs +++ b/csharp/App/EmuMeterDriver/Program.cs @@ -1,4 +1,4 @@ -using InnovEnergy.EmuMeter; +using InnovEnergy.App.EmuMeterDriver; using InnovEnergy.Lib.Protocols.DBus; using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Utils.Net; diff --git a/csharp/app/EmuMeterDriver/Signal.cs b/csharp/App/EmuMeterDriver/Signal.cs similarity index 90% rename from csharp/app/EmuMeterDriver/Signal.cs rename to csharp/App/EmuMeterDriver/Signal.cs index e606e2f53..ef375bb91 100644 --- a/csharp/app/EmuMeterDriver/Signal.cs +++ b/csharp/App/EmuMeterDriver/Signal.cs @@ -2,7 +2,7 @@ using InnovEnergy.Lib.Devices.EmuMeter; using InnovEnergy.Lib.Protocols.DBus.Protocol.DataTypes; using InnovEnergy.Lib.Victron.VeDBus; -namespace InnovEnergy.EmuMeter; +namespace InnovEnergy.App.EmuMeterDriver; public record Signal(Func Source, ObjectPath Path, String Format = "") { diff --git a/csharp/app/EmuMeterDriver/Utils.cs b/csharp/App/EmuMeterDriver/Utils.cs similarity index 95% rename from csharp/app/EmuMeterDriver/Utils.cs rename to csharp/App/EmuMeterDriver/Utils.cs index cab67bd3c..65f02de6a 100644 --- a/csharp/app/EmuMeterDriver/Utils.cs +++ b/csharp/App/EmuMeterDriver/Utils.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.EmuMeter; +namespace InnovEnergy.App.EmuMeterDriver; public static class Utils { diff --git a/csharp/app/EmuMeterDriver/debug.sh b/csharp/App/EmuMeterDriver/debug.sh similarity index 100% rename from csharp/app/EmuMeterDriver/debug.sh rename to csharp/App/EmuMeterDriver/debug.sh diff --git a/csharp/app/EmuMeterDriver/service/log/run b/csharp/App/EmuMeterDriver/service/log/run similarity index 100% rename from csharp/app/EmuMeterDriver/service/log/run rename to csharp/App/EmuMeterDriver/service/log/run diff --git a/csharp/app/EmuMeterDriver/service/run b/csharp/App/EmuMeterDriver/service/run similarity index 100% rename from csharp/app/EmuMeterDriver/service/run rename to csharp/App/EmuMeterDriver/service/run diff --git a/csharp/app/InnovEnergy.app.props b/csharp/App/InnovEnergy.App.props similarity index 97% rename from csharp/app/InnovEnergy.app.props rename to csharp/App/InnovEnergy.App.props index beb4a7a4a..fe6d1e334 100644 --- a/csharp/app/InnovEnergy.app.props +++ b/csharp/App/InnovEnergy.App.props @@ -11,7 +11,6 @@ true true true - @@ -23,3 +22,4 @@ + \ No newline at end of file diff --git a/csharp/app/OpenVpnCertificatesServer/Files.cs b/csharp/App/OpenVpnCertificatesServer/Files.cs similarity index 90% rename from csharp/app/OpenVpnCertificatesServer/Files.cs rename to csharp/App/OpenVpnCertificatesServer/Files.cs index 35f78f44b..00fbf58d5 100644 --- a/csharp/app/OpenVpnCertificatesServer/Files.cs +++ b/csharp/App/OpenVpnCertificatesServer/Files.cs @@ -1,6 +1,4 @@ -namespace InnovEnergy.OpenVpnCertificatesServer; - -using System; +namespace InnovEnergy.App.OpenVpnCertificatesServer; public static class Files { diff --git a/csharp/app/OpenVpnCertificatesServer/Http.cs b/csharp/App/OpenVpnCertificatesServer/Http.cs similarity index 94% rename from csharp/app/OpenVpnCertificatesServer/Http.cs rename to csharp/App/OpenVpnCertificatesServer/Http.cs index 3f72fb5da..5442047ae 100644 --- a/csharp/app/OpenVpnCertificatesServer/Http.cs +++ b/csharp/App/OpenVpnCertificatesServer/Http.cs @@ -1,9 +1,8 @@ -namespace InnovEnergy.OpenVpnCertificatesServer; - -using System; using System.Net; using Flurl; +namespace InnovEnergy.App.OpenVpnCertificatesServer; + public static class Http { // TODO: use worker thread diff --git a/csharp/app/OpenVpnCertificatesServer/OpenVpnCertificatesServer.csproj b/csharp/App/OpenVpnCertificatesServer/OpenVpnCertificatesServer.csproj similarity index 53% rename from csharp/app/OpenVpnCertificatesServer/OpenVpnCertificatesServer.csproj rename to csharp/App/OpenVpnCertificatesServer/OpenVpnCertificatesServer.csproj index c2110f3cb..e44b0a11e 100644 --- a/csharp/app/OpenVpnCertificatesServer/OpenVpnCertificatesServer.csproj +++ b/csharp/App/OpenVpnCertificatesServer/OpenVpnCertificatesServer.csproj @@ -1,10 +1,6 @@ - + - - InnovEnergy.OpenVpnCertificatesServer - - @@ -12,8 +8,9 @@ - - + + + diff --git a/csharp/app/OpenVpnCertificatesServer/OpenVpnCertificatesServer.sln b/csharp/App/OpenVpnCertificatesServer/OpenVpnCertificatesServer.sln similarity index 100% rename from csharp/app/OpenVpnCertificatesServer/OpenVpnCertificatesServer.sln rename to csharp/App/OpenVpnCertificatesServer/OpenVpnCertificatesServer.sln diff --git a/csharp/app/OpenVpnCertificatesServer/PKI/CertificateAuthority.cs b/csharp/App/OpenVpnCertificatesServer/PKI/CertificateAuthority.cs similarity index 99% rename from csharp/app/OpenVpnCertificatesServer/PKI/CertificateAuthority.cs rename to csharp/App/OpenVpnCertificatesServer/PKI/CertificateAuthority.cs index ec35a7836..b782c8165 100644 --- a/csharp/app/OpenVpnCertificatesServer/PKI/CertificateAuthority.cs +++ b/csharp/App/OpenVpnCertificatesServer/PKI/CertificateAuthority.cs @@ -7,7 +7,7 @@ using Org.BouncyCastle.Math; using Org.BouncyCastle.Security; using Org.BouncyCastle.X509; -namespace InnovEnergy.OpenVpnCertificatesServer.PKI; +namespace InnovEnergy.App.OpenVpnCertificatesServer.PKI; public static class CertificateAuthority { diff --git a/csharp/app/OpenVpnCertificatesServer/PKI/Pem.cs b/csharp/App/OpenVpnCertificatesServer/PKI/Pem.cs similarity index 95% rename from csharp/app/OpenVpnCertificatesServer/PKI/Pem.cs rename to csharp/App/OpenVpnCertificatesServer/PKI/Pem.cs index 3698135ee..8015c4e71 100644 --- a/csharp/app/OpenVpnCertificatesServer/PKI/Pem.cs +++ b/csharp/App/OpenVpnCertificatesServer/PKI/Pem.cs @@ -1,6 +1,6 @@ using Org.BouncyCastle.OpenSsl; -namespace InnovEnergy.OpenVpnCertificatesServer.PKI; +namespace InnovEnergy.App.OpenVpnCertificatesServer.PKI; public static class Pem { diff --git a/csharp/app/OpenVpnCertificatesServer/PKI/PwdFinder.cs b/csharp/App/OpenVpnCertificatesServer/PKI/PwdFinder.cs similarity index 81% rename from csharp/app/OpenVpnCertificatesServer/PKI/PwdFinder.cs rename to csharp/App/OpenVpnCertificatesServer/PKI/PwdFinder.cs index 70d54fd51..3bbe9354a 100644 --- a/csharp/app/OpenVpnCertificatesServer/PKI/PwdFinder.cs +++ b/csharp/App/OpenVpnCertificatesServer/PKI/PwdFinder.cs @@ -1,6 +1,6 @@ using Org.BouncyCastle.OpenSsl; -namespace InnovEnergy.OpenVpnCertificatesServer.PKI; +namespace InnovEnergy.App.OpenVpnCertificatesServer.PKI; public class PwdFinder : IPasswordFinder { diff --git a/csharp/app/OpenVpnCertificatesServer/Program.cs b/csharp/App/OpenVpnCertificatesServer/Program.cs similarity index 97% rename from csharp/app/OpenVpnCertificatesServer/Program.cs rename to csharp/App/OpenVpnCertificatesServer/Program.cs index 3d765b2cd..c9bff3a03 100644 --- a/csharp/app/OpenVpnCertificatesServer/Program.cs +++ b/csharp/App/OpenVpnCertificatesServer/Program.cs @@ -1,15 +1,14 @@ -using InnovEnergy.Lib.Utils; -using InnovEnergy.Lib.Victron.VictronVRM; -using InnovEnergy.OpenVpnCertificatesServer.PKI; -using System.Diagnostics; +using System.Diagnostics; using System.Text; 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; -using static InnovEnergy.OpenVpnCertificatesServer.PKI.CertificateAuthority; - -namespace InnovEnergy.OpenVpnCertificatesServer; +namespace InnovEnergy.App.OpenVpnCertificatesServer; // 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 diff --git a/csharp/app/OpenVpnCertificatesServer/Utils.cs b/csharp/App/OpenVpnCertificatesServer/Utils.cs similarity index 95% rename from csharp/app/OpenVpnCertificatesServer/Utils.cs rename to csharp/App/OpenVpnCertificatesServer/Utils.cs index 9ed8734f7..aea6faf59 100644 --- a/csharp/app/OpenVpnCertificatesServer/Utils.cs +++ b/csharp/App/OpenVpnCertificatesServer/Utils.cs @@ -1,9 +1,8 @@ -namespace InnovEnergy.OpenVpnCertificatesServer; - -using System; 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) diff --git a/csharp/app/RemoteSupportConsole/Login.cs b/csharp/App/RemoteSupportConsole/Login.cs similarity index 77% rename from csharp/app/RemoteSupportConsole/Login.cs rename to csharp/App/RemoteSupportConsole/Login.cs index 901fd7175..7e3edbe35 100644 --- a/csharp/app/RemoteSupportConsole/Login.cs +++ b/csharp/App/RemoteSupportConsole/Login.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.RemoteSupportConsole; +namespace InnovEnergy.App.RemoteSupportConsole; public static class Login { diff --git a/csharp/app/RemoteSupportConsole/ObservablePipeSource.cs b/csharp/App/RemoteSupportConsole/ObservablePipeSource.cs similarity index 91% rename from csharp/app/RemoteSupportConsole/ObservablePipeSource.cs rename to csharp/App/RemoteSupportConsole/ObservablePipeSource.cs index 281ee75f4..a5e1af9d7 100644 --- a/csharp/app/RemoteSupportConsole/ObservablePipeSource.cs +++ b/csharp/App/RemoteSupportConsole/ObservablePipeSource.cs @@ -2,7 +2,7 @@ using System.Reactive.Linq; using System.Reactive.Threading.Tasks; using CliWrap; -namespace InnovEnergy.RemoteSupportConsole; +namespace InnovEnergy.App.RemoteSupportConsole; public static class ObservablePipeSource { diff --git a/csharp/app/RemoteSupportConsole/Program.cs b/csharp/App/RemoteSupportConsole/Program.cs similarity index 99% rename from csharp/app/RemoteSupportConsole/Program.cs rename to csharp/App/RemoteSupportConsole/Program.cs index bd21d71d0..7ce5cbdcd 100644 --- a/csharp/app/RemoteSupportConsole/Program.cs +++ b/csharp/App/RemoteSupportConsole/Program.cs @@ -4,7 +4,7 @@ using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Victron.VictronVRM; using static System.Globalization.CompareOptions; -namespace InnovEnergy.RemoteSupportConsole; +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 diff --git a/csharp/App/RemoteSupportConsole/RemoteSupportConsole.csproj b/csharp/App/RemoteSupportConsole/RemoteSupportConsole.csproj new file mode 100644 index 000000000..1f571c26d --- /dev/null +++ b/csharp/App/RemoteSupportConsole/RemoteSupportConsole.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/csharp/app/RemoteSupportConsole/RemoteSupportConsole.csproj.DotSettings b/csharp/App/RemoteSupportConsole/RemoteSupportConsole.csproj.DotSettings similarity index 100% rename from csharp/app/RemoteSupportConsole/RemoteSupportConsole.csproj.DotSettings rename to csharp/App/RemoteSupportConsole/RemoteSupportConsole.csproj.DotSettings diff --git a/csharp/app/RemoteSupportConsole/Ssh.cs b/csharp/App/RemoteSupportConsole/Ssh.cs similarity index 96% rename from csharp/app/RemoteSupportConsole/Ssh.cs rename to csharp/App/RemoteSupportConsole/Ssh.cs index 55502ecb7..d9acc9662 100644 --- a/csharp/app/RemoteSupportConsole/Ssh.cs +++ b/csharp/App/RemoteSupportConsole/Ssh.cs @@ -1,7 +1,7 @@ using CliWrap; using InnovEnergy.Lib.Utils; -namespace InnovEnergy.RemoteSupportConsole; +namespace InnovEnergy.App.RemoteSupportConsole; public static class Ssh { diff --git a/csharp/app/RemoteSupportConsole/VpnConnection.cs b/csharp/App/RemoteSupportConsole/VpnConnection.cs similarity index 94% rename from csharp/app/RemoteSupportConsole/VpnConnection.cs rename to csharp/App/RemoteSupportConsole/VpnConnection.cs index 29f52b40c..b2442eea1 100644 --- a/csharp/app/RemoteSupportConsole/VpnConnection.cs +++ b/csharp/App/RemoteSupportConsole/VpnConnection.cs @@ -2,7 +2,7 @@ using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Victron.VictronVRM; using static System.ConsoleColor; -namespace InnovEnergy.RemoteSupportConsole; +namespace InnovEnergy.App.RemoteSupportConsole; public static class VpnConnection { diff --git a/csharp/app/RemoteSupportConsole/VpnInfo.cs b/csharp/App/RemoteSupportConsole/VpnInfo.cs similarity index 95% rename from csharp/app/RemoteSupportConsole/VpnInfo.cs rename to csharp/App/RemoteSupportConsole/VpnInfo.cs index d7f39fbbc..0137a3b50 100644 --- a/csharp/app/RemoteSupportConsole/VpnInfo.cs +++ b/csharp/App/RemoteSupportConsole/VpnInfo.cs @@ -2,7 +2,7 @@ using Flurl; using Flurl.Http; using InnovEnergy.Lib.Utils; -namespace InnovEnergy.RemoteSupportConsole; +namespace InnovEnergy.App.RemoteSupportConsole; public static class VpnInfo { diff --git a/csharp/app/RemoteSupportConsole/VrmConnection.cs b/csharp/App/RemoteSupportConsole/VrmConnection.cs similarity index 98% rename from csharp/app/RemoteSupportConsole/VrmConnection.cs rename to csharp/App/RemoteSupportConsole/VrmConnection.cs index 52515a025..03dd1e252 100644 --- a/csharp/app/RemoteSupportConsole/VrmConnection.cs +++ b/csharp/App/RemoteSupportConsole/VrmConnection.cs @@ -7,7 +7,7 @@ using InnovEnergy.Lib.Victron.VictronVRM; using static System.ConsoleColor; using static System.StringSplitOptions; -namespace InnovEnergy.RemoteSupportConsole; +namespace InnovEnergy.App.RemoteSupportConsole; public static class VrmConnection { diff --git a/csharp/app/RemoteSupportConsole/VrmInfo.cs b/csharp/App/RemoteSupportConsole/VrmInfo.cs similarity index 96% rename from csharp/app/RemoteSupportConsole/VrmInfo.cs rename to csharp/App/RemoteSupportConsole/VrmInfo.cs index 9b37d00fc..32568ae23 100644 --- a/csharp/app/RemoteSupportConsole/VrmInfo.cs +++ b/csharp/App/RemoteSupportConsole/VrmInfo.cs @@ -3,7 +3,7 @@ using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Victron.VictronVRM; using static System.ConsoleColor; -namespace InnovEnergy.RemoteSupportConsole; +namespace InnovEnergy.App.RemoteSupportConsole; public static class VrmInfo diff --git a/csharp/app/RemoteSupportConsole/VrmProxy.cs b/csharp/App/RemoteSupportConsole/VrmProxy.cs similarity index 77% rename from csharp/app/RemoteSupportConsole/VrmProxy.cs rename to csharp/App/RemoteSupportConsole/VrmProxy.cs index cc00370c5..c629c5476 100644 --- a/csharp/app/RemoteSupportConsole/VrmProxy.cs +++ b/csharp/App/RemoteSupportConsole/VrmProxy.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.RemoteSupportConsole; +namespace InnovEnergy.App.RemoteSupportConsole; public readonly record struct VrmProxy(IDisposable Connection, String User, String Host, String Port) : IDisposable { diff --git a/csharp/App/SaliMax/SaliMax.csproj b/csharp/App/SaliMax/SaliMax.csproj new file mode 100644 index 000000000..c647ee0d5 --- /dev/null +++ b/csharp/App/SaliMax/SaliMax.csproj @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/csharp/app/SaliMax/run (BeagleBone Meiringen).sh b/csharp/App/SaliMax/run (BeagleBone Meiringen).sh similarity index 100% rename from csharp/app/SaliMax/run (BeagleBone Meiringen).sh rename to csharp/App/SaliMax/run (BeagleBone Meiringen).sh diff --git a/csharp/app/SaliMax/src/AsciiArt.cs b/csharp/App/SaliMax/src/AsciiArt.cs similarity index 98% rename from csharp/app/SaliMax/src/AsciiArt.cs rename to csharp/App/SaliMax/src/AsciiArt.cs index c8ffc4596..7485a1363 100644 --- a/csharp/app/SaliMax/src/AsciiArt.cs +++ b/csharp/App/SaliMax/src/AsciiArt.cs @@ -1,6 +1,6 @@ using InnovEnergy.Lib.Utils; -namespace InnovEnergy.SaliMax; +namespace InnovEnergy.App.SaliMax; public static class AsciiArt { diff --git a/csharp/app/SaliMax/src/BusPort.cs b/csharp/App/SaliMax/src/BusPort.cs similarity index 66% rename from csharp/app/SaliMax/src/BusPort.cs rename to csharp/App/SaliMax/src/BusPort.cs index 20fc1c9a6..e8cbd649b 100644 --- a/csharp/app/SaliMax/src/BusPort.cs +++ b/csharp/App/SaliMax/src/BusPort.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.SaliMax; +namespace InnovEnergy.App.SaliMax; public enum BusPort { diff --git a/csharp/app/SaliMax/src/Controller/AvgBatteriesStatus.cs b/csharp/App/SaliMax/src/Controller/AvgBatteriesStatus.cs similarity index 98% rename from csharp/app/SaliMax/src/Controller/AvgBatteriesStatus.cs rename to csharp/App/SaliMax/src/Controller/AvgBatteriesStatus.cs index 7b90a6640..b62316d85 100644 --- a/csharp/app/SaliMax/src/Controller/AvgBatteriesStatus.cs +++ b/csharp/App/SaliMax/src/Controller/AvgBatteriesStatus.cs @@ -1,6 +1,6 @@ using InnovEnergy.Lib.Devices.Battery48TL; -namespace InnovEnergy.SaliMax.Controller; +namespace InnovEnergy.App.SaliMax.Controller; public class AvgBatteriesStatus { diff --git a/csharp/app/SaliMax/src/Controller/Control.cs b/csharp/App/SaliMax/src/Controller/Control.cs similarity index 94% rename from csharp/app/SaliMax/src/Controller/Control.cs rename to csharp/App/SaliMax/src/Controller/Control.cs index 144e2b166..08589edb5 100644 --- a/csharp/app/SaliMax/src/Controller/Control.cs +++ b/csharp/App/SaliMax/src/Controller/Control.cs @@ -1,6 +1,4 @@ -using static InnovEnergy.SaliMax.SystemConfig.SalimaxConfig; - -namespace InnovEnergy.SaliMax.Controller; +namespace InnovEnergy.App.SaliMax.Controller; public static class Control { diff --git a/csharp/app/SaliMax/src/Controller/ControlRecord.cs b/csharp/App/SaliMax/src/Controller/ControlRecord.cs similarity index 78% rename from csharp/app/SaliMax/src/Controller/ControlRecord.cs rename to csharp/App/SaliMax/src/Controller/ControlRecord.cs index c00373efb..ce7e4fc8f 100644 --- a/csharp/app/SaliMax/src/Controller/ControlRecord.cs +++ b/csharp/App/SaliMax/src/Controller/ControlRecord.cs @@ -1,9 +1,9 @@ +using InnovEnergy.App.SaliMax.SaliMaxRelays; +using InnovEnergy.App.SaliMax.SystemConfig; using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; -using InnovEnergy.SaliMax.SaliMaxRelays; -using InnovEnergy.SaliMax.SystemConfig; -namespace InnovEnergy.SaliMax.Controller; +namespace InnovEnergy.App.SaliMax.Controller; public class ControlRecord { diff --git a/csharp/app/SaliMax/src/Controller/ControlTarget.cs b/csharp/App/SaliMax/src/Controller/ControlTarget.cs similarity index 76% rename from csharp/app/SaliMax/src/Controller/ControlTarget.cs rename to csharp/App/SaliMax/src/Controller/ControlTarget.cs index d08fa47ab..1e279f8d3 100644 --- a/csharp/app/SaliMax/src/Controller/ControlTarget.cs +++ b/csharp/App/SaliMax/src/Controller/ControlTarget.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.SaliMax.Controller; +namespace InnovEnergy.App.SaliMax.Controller; public enum ControlTarget // TODO to delete { diff --git a/csharp/app/SaliMax/src/Controller/Controller.cs b/csharp/App/SaliMax/src/Controller/Controller.cs similarity index 98% rename from csharp/app/SaliMax/src/Controller/Controller.cs rename to csharp/App/SaliMax/src/Controller/Controller.cs index e578c5b2d..cadb47477 100644 --- a/csharp/app/SaliMax/src/Controller/Controller.cs +++ b/csharp/App/SaliMax/src/Controller/Controller.cs @@ -1,14 +1,14 @@ +using InnovEnergy.App.SaliMax.SaliMaxRelays; +using InnovEnergy.App.SaliMax.SystemConfig; using InnovEnergy.Lib.Devices.Trumpf.TruConvert; using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; +using InnovEnergy.Lib.Time.Unix; using InnovEnergy.Lib.Utils; -using InnovEnergy.SaliMax.SaliMaxRelays; -using InnovEnergy.SaliMax.SystemConfig; -using InnovEnergy.Time.Unix; -using static InnovEnergy.SaliMax.SaliMaxRelays.RelayState; +using static InnovEnergy.App.SaliMax.SaliMaxRelays.RelayState; -namespace InnovEnergy.SaliMax.Controller; +namespace InnovEnergy.App.SaliMax.Controller; public static class Controller { diff --git a/csharp/app/SaliMax/src/Controller/SaliMaxState.cs b/csharp/App/SaliMax/src/Controller/SaliMaxState.cs similarity index 84% rename from csharp/app/SaliMax/src/Controller/SaliMaxState.cs rename to csharp/App/SaliMax/src/Controller/SaliMaxState.cs index cd81b24b5..be7470fae 100644 --- a/csharp/app/SaliMax/src/Controller/SaliMaxState.cs +++ b/csharp/App/SaliMax/src/Controller/SaliMaxState.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.SaliMax.Controller; +namespace InnovEnergy.App.SaliMax.Controller; public struct SaliMaxState { diff --git a/csharp/app/SaliMax/src/Controller/State.cs b/csharp/App/SaliMax/src/Controller/State.cs similarity index 92% rename from csharp/app/SaliMax/src/Controller/State.cs rename to csharp/App/SaliMax/src/Controller/State.cs index a8ad8d11b..7634d1d66 100644 --- a/csharp/app/SaliMax/src/Controller/State.cs +++ b/csharp/App/SaliMax/src/Controller/State.cs @@ -1,5 +1,5 @@ -namespace InnovEnergy.SaliMax.Controller; +namespace InnovEnergy.App.SaliMax.Controller; public enum State : Int16 { diff --git a/csharp/app/SaliMax/src/Controller/StateConfig.cs b/csharp/App/SaliMax/src/Controller/StateConfig.cs similarity index 79% rename from csharp/app/SaliMax/src/Controller/StateConfig.cs rename to csharp/App/SaliMax/src/Controller/StateConfig.cs index be40aafc0..ce356d311 100644 --- a/csharp/app/SaliMax/src/Controller/StateConfig.cs +++ b/csharp/App/SaliMax/src/Controller/StateConfig.cs @@ -1,5 +1,5 @@ -namespace InnovEnergy.SaliMax.Controller; +namespace InnovEnergy.App.SaliMax.Controller; public static class StateConfig { diff --git a/csharp/app/SaliMax/src/Controller/StatusRecord.cs b/csharp/App/SaliMax/src/Controller/StatusRecord.cs similarity index 84% rename from csharp/app/SaliMax/src/Controller/StatusRecord.cs rename to csharp/App/SaliMax/src/Controller/StatusRecord.cs index 0969b841b..53de658b8 100644 --- a/csharp/app/SaliMax/src/Controller/StatusRecord.cs +++ b/csharp/App/SaliMax/src/Controller/StatusRecord.cs @@ -1,12 +1,12 @@ -using InnovEnergy.Lib.Devices.Ampt; +using InnovEnergy.App.SaliMax.SaliMaxRelays; +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; -using InnovEnergy.SaliMax.SaliMaxRelays; -using InnovEnergy.SaliMax.SystemConfig; -namespace InnovEnergy.SaliMax.Controller; +namespace InnovEnergy.App.SaliMax.Controller; public record StatusRecord { diff --git a/csharp/app/SaliMax/src/Log/Ampt.cs b/csharp/App/SaliMax/src/Log/Ampt.cs similarity index 90% rename from csharp/app/SaliMax/src/Log/Ampt.cs rename to csharp/App/SaliMax/src/Log/Ampt.cs index 1a63b9eab..efaa6e388 100644 --- a/csharp/app/SaliMax/src/Log/Ampt.cs +++ b/csharp/App/SaliMax/src/Log/Ampt.cs @@ -1,8 +1,8 @@ using System.Text.Json.Nodes; -using InnovEnergy.Lib.Devices.Ampt; +using InnovEnergy.Lib.Devices.AMPT; using InnovEnergy.Lib.StatusApi; -namespace InnovEnergy.SaliMax.Log; +namespace InnovEnergy.App.SaliMax.Log; public static class Ampt { diff --git a/csharp/app/SaliMax/src/Log/Battery48Tl.cs b/csharp/App/SaliMax/src/Log/Battery48Tl.cs similarity index 95% rename from csharp/app/SaliMax/src/Log/Battery48Tl.cs rename to csharp/App/SaliMax/src/Log/Battery48Tl.cs index db755c78d..f2c85bcbf 100644 --- a/csharp/app/SaliMax/src/Log/Battery48Tl.cs +++ b/csharp/App/SaliMax/src/Log/Battery48Tl.cs @@ -2,7 +2,7 @@ using System.Text.Json.Nodes; using InnovEnergy.Lib.Devices.Battery48TL; using InnovEnergy.Lib.StatusApi; -namespace InnovEnergy.SaliMax.Log; +namespace InnovEnergy.App.SaliMax.Log; public static class Battery48Tl { diff --git a/csharp/app/SaliMax/src/Log/EmuMeter.cs b/csharp/App/SaliMax/src/Log/EmuMeter.cs similarity index 94% rename from csharp/app/SaliMax/src/Log/EmuMeter.cs rename to csharp/App/SaliMax/src/Log/EmuMeter.cs index 8b61199cf..bb5dbbf29 100644 --- a/csharp/app/SaliMax/src/Log/EmuMeter.cs +++ b/csharp/App/SaliMax/src/Log/EmuMeter.cs @@ -3,9 +3,9 @@ using InnovEnergy.Lib.Devices.EmuMeter; using InnovEnergy.Lib.StatusApi; using InnovEnergy.Lib.Utils; using static DecimalMath.DecimalEx; -using static InnovEnergy.SaliMax.Log.JsonUtil; +using static InnovEnergy.App.SaliMax.Log.JsonUtil; -namespace InnovEnergy.SaliMax.Log; +namespace InnovEnergy.App.SaliMax.Log; public static class EmuMeter { diff --git a/csharp/app/SaliMax/src/Log/JsonUtil.cs b/csharp/App/SaliMax/src/Log/JsonUtil.cs similarity index 98% rename from csharp/app/SaliMax/src/Log/JsonUtil.cs rename to csharp/App/SaliMax/src/Log/JsonUtil.cs index 3201cb9b5..e82f2af01 100644 --- a/csharp/app/SaliMax/src/Log/JsonUtil.cs +++ b/csharp/App/SaliMax/src/Log/JsonUtil.cs @@ -1,7 +1,7 @@ using System.Text.Json.Nodes; using InnovEnergy.Lib.StatusApi; -namespace InnovEnergy.SaliMax.Log; +namespace InnovEnergy.App.SaliMax.Log; public static class JsonUtil { diff --git a/csharp/app/SaliMax/src/Log/Salimax.cs b/csharp/App/SaliMax/src/Log/Salimax.cs similarity index 96% rename from csharp/app/SaliMax/src/Log/Salimax.cs rename to csharp/App/SaliMax/src/Log/Salimax.cs index 76bd25648..09a7de12f 100644 --- a/csharp/app/SaliMax/src/Log/Salimax.cs +++ b/csharp/App/SaliMax/src/Log/Salimax.cs @@ -1,9 +1,9 @@ using System.Text.Json.Nodes; +using InnovEnergy.App.SaliMax.Controller; using InnovEnergy.Lib.StatusApi; -using InnovEnergy.SaliMax.Controller; -using InnovEnergy.Time.Unix; +using InnovEnergy.Lib.Time.Unix; -namespace InnovEnergy.SaliMax.Log; +namespace InnovEnergy.App.SaliMax.Log; public static class Salimax { diff --git a/csharp/app/SaliMax/src/Log/TruConvertAc.cs b/csharp/App/SaliMax/src/Log/TruConvertAc.cs similarity index 96% rename from csharp/app/SaliMax/src/Log/TruConvertAc.cs rename to csharp/App/SaliMax/src/Log/TruConvertAc.cs index eeb56ea4c..af49cbc66 100644 --- a/csharp/app/SaliMax/src/Log/TruConvertAc.cs +++ b/csharp/App/SaliMax/src/Log/TruConvertAc.cs @@ -2,9 +2,9 @@ using System.Text.Json.Nodes; using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; using InnovEnergy.Lib.Utils; using static DecimalMath.DecimalEx; -using static InnovEnergy.SaliMax.Log.JsonUtil; +using static InnovEnergy.App.SaliMax.Log.JsonUtil; -namespace InnovEnergy.SaliMax.Log; +namespace InnovEnergy.App.SaliMax.Log; public static class TruConvertAc { diff --git a/csharp/app/SaliMax/src/Log/TruConvertDc.cs b/csharp/App/SaliMax/src/Log/TruConvertDc.cs similarity index 89% rename from csharp/app/SaliMax/src/Log/TruConvertDc.cs rename to csharp/App/SaliMax/src/Log/TruConvertDc.cs index 09e39d1f2..34c23942b 100644 --- a/csharp/app/SaliMax/src/Log/TruConvertDc.cs +++ b/csharp/App/SaliMax/src/Log/TruConvertDc.cs @@ -1,8 +1,8 @@ using System.Text.Json.Nodes; using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; -using static InnovEnergy.SaliMax.Log.JsonUtil; +using static InnovEnergy.App.SaliMax.Log.JsonUtil; -namespace InnovEnergy.SaliMax.Log; +namespace InnovEnergy.App.SaliMax.Log; using JO = JsonObject; diff --git a/csharp/app/SaliMax/src/Program.cs b/csharp/App/SaliMax/src/Program.cs similarity index 53% rename from csharp/app/SaliMax/src/Program.cs rename to csharp/App/SaliMax/src/Program.cs index 1bbbc8883..229e3f0b8 100644 --- a/csharp/app/SaliMax/src/Program.cs +++ b/csharp/App/SaliMax/src/Program.cs @@ -1,29 +1,23 @@ -#undef BatteriesAllowed - - using System.Diagnostics; using System.Text.Json; using System.Text.Json.Nodes; using System.Text.Json.Serialization; using Flurl.Http; +using InnovEnergy.App.SaliMax.Controller; +using InnovEnergy.App.SaliMax.Log; +using InnovEnergy.App.SaliMax.SaliMaxRelays; +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; -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; - +using InnovEnergy.Lib.Time.Unix; #pragma warning disable IL2026 -namespace InnovEnergy.SaliMax; +namespace InnovEnergy.App.SaliMax; internal static class Program { @@ -148,7 +142,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 +151,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/S3Config.cs b/csharp/App/SaliMax/src/S3Config.cs similarity index 98% rename from csharp/app/SaliMax/src/S3Config.cs rename to csharp/App/SaliMax/src/S3Config.cs index 92238fb96..2e04acd81 100644 --- a/csharp/app/SaliMax/src/S3Config.cs +++ b/csharp/App/SaliMax/src/S3Config.cs @@ -5,7 +5,7 @@ using InnovEnergy.Lib.Utils; using static System.Text.Encoding; using Convert = System.Convert; -namespace InnovEnergy.SaliMax; +namespace InnovEnergy.App.SaliMax; public record S3Config { diff --git a/csharp/app/SaliMax/src/SaliMaxRelays/RelayMapBoolean.cs b/csharp/App/SaliMax/src/SaliMaxRelays/RelayMapBoolean.cs similarity index 53% rename from csharp/app/SaliMax/src/SaliMaxRelays/RelayMapBoolean.cs rename to csharp/App/SaliMax/src/SaliMaxRelays/RelayMapBoolean.cs index c1408c2ff..bafd76793 100644 --- a/csharp/app/SaliMax/src/SaliMaxRelays/RelayMapBoolean.cs +++ b/csharp/App/SaliMax/src/SaliMaxRelays/RelayMapBoolean.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.SaliMax.SaliMaxRelays; +namespace InnovEnergy.App.SaliMax.SaliMaxRelays; public enum RelayState { diff --git a/csharp/app/SaliMax/src/SaliMaxRelays/SaliMaxRelaysDevice.cs b/csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysDevice.cs similarity index 95% rename from csharp/app/SaliMax/src/SaliMaxRelays/SaliMaxRelaysDevice.cs rename to csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysDevice.cs index 5fb1b8a69..4b5db1e06 100644 --- a/csharp/app/SaliMax/src/SaliMaxRelays/SaliMaxRelaysDevice.cs +++ b/csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysDevice.cs @@ -1,7 +1,7 @@ using InnovEnergy.Lib.Devices.Adam6060; using InnovEnergy.Lib.Utils; -namespace InnovEnergy.SaliMax.SaliMaxRelays; +namespace InnovEnergy.App.SaliMax.SaliMaxRelays; public class SaliMaxRelaysDevice { diff --git a/csharp/app/SaliMax/src/SaliMaxRelays/SaliMaxRelaysStatus.cs b/csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysStatus.cs similarity index 86% rename from csharp/app/SaliMax/src/SaliMaxRelays/SaliMaxRelaysStatus.cs rename to csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysStatus.cs index 64c08b992..915896d54 100644 --- a/csharp/app/SaliMax/src/SaliMaxRelays/SaliMaxRelaysStatus.cs +++ b/csharp/App/SaliMax/src/SaliMaxRelays/SaliMaxRelaysStatus.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.SaliMax.SaliMaxRelays; +namespace InnovEnergy.App.SaliMax.SaliMaxRelays; public record SaliMaxRelayStatus { diff --git a/csharp/app/SaliMax/src/SystemConfig/Config.cs b/csharp/App/SaliMax/src/SystemConfig/Config.cs similarity index 73% rename from csharp/app/SaliMax/src/SystemConfig/Config.cs rename to csharp/App/SaliMax/src/SystemConfig/Config.cs index c11f7d6f7..73e42897a 100644 --- a/csharp/app/SaliMax/src/SystemConfig/Config.cs +++ b/csharp/App/SaliMax/src/SystemConfig/Config.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.SaliMax.SystemConfig; +namespace InnovEnergy.App.SaliMax.SystemConfig; public static class Config { diff --git a/csharp/app/SaliMax/src/SystemConfig/Defaults.cs b/csharp/App/SaliMax/src/SystemConfig/Defaults.cs similarity index 99% rename from csharp/app/SaliMax/src/SystemConfig/Defaults.cs rename to csharp/App/SaliMax/src/SystemConfig/Defaults.cs index 6b11b6ad1..1fcb95d25 100644 --- a/csharp/app/SaliMax/src/SystemConfig/Defaults.cs +++ b/csharp/App/SaliMax/src/SystemConfig/Defaults.cs @@ -1,7 +1,7 @@ using InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; using InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; -namespace InnovEnergy.SaliMax.SystemConfig; +namespace InnovEnergy.App.SaliMax.SystemConfig; public static class Defaults { diff --git a/csharp/app/SaliMax/src/SystemConfig/SalimaxConfig.cs b/csharp/App/SaliMax/src/SystemConfig/SalimaxConfig.cs similarity index 96% rename from csharp/app/SaliMax/src/SystemConfig/SalimaxConfig.cs rename to csharp/App/SaliMax/src/SystemConfig/SalimaxConfig.cs index 79d01fdd5..2170783e3 100644 --- a/csharp/app/SaliMax/src/SystemConfig/SalimaxConfig.cs +++ b/csharp/App/SaliMax/src/SystemConfig/SalimaxConfig.cs @@ -1,9 +1,9 @@ using System.Text.Json; +using InnovEnergy.Lib.Time.Unix; using InnovEnergy.Lib.Utils; -using InnovEnergy.Time.Unix; using static System.Text.Json.JsonSerializer; -namespace InnovEnergy.SaliMax.SystemConfig; +namespace InnovEnergy.App.SaliMax.SystemConfig; // shut up trim warnings #pragma warning disable IL2026 diff --git a/csharp/App/SaliMax/src/Topology.cs b/csharp/App/SaliMax/src/Topology.cs new file mode 100644 index 000000000..f9db2b68b --- /dev/null +++ b/csharp/App/SaliMax/src/Topology.cs @@ -0,0 +1,186 @@ +#undef BatteriesAllowed + +using InnovEnergy.App.SaliMax.Controller; +using InnovEnergy.App.SaliMax.Log; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.App.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 diff --git a/csharp/lib/StatusApi/Utils.cs b/csharp/App/SaliMax/src/Utils.cs similarity index 55% rename from csharp/lib/StatusApi/Utils.cs rename to csharp/App/SaliMax/src/Utils.cs index b86db8478..1cf608f84 100644 --- a/csharp/lib/StatusApi/Utils.cs +++ b/csharp/App/SaliMax/src/Utils.cs @@ -1,11 +1,11 @@ using InnovEnergy.Lib.Utils; -namespace InnovEnergy.Lib.StatusApi; +namespace InnovEnergy.App.SaliMax; public static class Utils { public static Decimal Round3(this Decimal d) { - return d.RoundToSignificantFigures(3); + return DecimalUtils.RoundToSignificantDigits(d, 3); } } \ No newline at end of file diff --git a/csharp/app/SaliMax/tunnels.html b/csharp/App/SaliMax/tunnels.html similarity index 100% rename from csharp/app/SaliMax/tunnels.html rename to csharp/App/SaliMax/tunnels.html diff --git a/csharp/app/SaliMax/tunnels.sh b/csharp/App/SaliMax/tunnels.sh similarity index 100% rename from csharp/app/SaliMax/tunnels.sh rename to csharp/App/SaliMax/tunnels.sh diff --git a/csharp/InnovEnergy.props b/csharp/InnovEnergy.props index 57ac5931e..7315812ef 100644 --- a/csharp/InnovEnergy.props +++ b/csharp/InnovEnergy.props @@ -3,13 +3,14 @@ InnovEnergy enable - default + preview true - InnovEnergy enable net6.0 true false + $(Company).$(MSBuildProjectDirectory.Replace($(SolutionDir), "").Replace("src/", "").Replace("/",".").Replace("\",".")) + $(Company) Team diff --git a/csharp/InnovEnergy.sln b/csharp/InnovEnergy.sln index 9247b7144..8d368b9cb 100644 --- a/csharp/InnovEnergy.sln +++ b/csharp/InnovEnergy.sln @@ -1,34 +1,28 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Collector", "app/Collector/Collector.csproj", "{E3A5F3A3-72A5-47CC-85C6-2D8E962A0EC1}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Collector", "App/Collector/Collector.csproj", "{E3A5F3A3-72A5-47CC-85C6-2D8E962A0EC1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenVpnCertificatesServer", "app/OpenVpnCertificatesServer/OpenVpnCertificatesServer.csproj", "{CF4834CB-91B7-4172-AC13-ECDA8613CD17}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenVpnCertificatesServer", "App/OpenVpnCertificatesServer/OpenVpnCertificatesServer.csproj", "{CF4834CB-91B7-4172-AC13-ECDA8613CD17}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteSupportConsole", "app/RemoteSupportConsole/RemoteSupportConsole.csproj", "{B1268C03-66EB-4486-8BFC-B439225D9D54}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RemoteSupportConsole", "App/RemoteSupportConsole/RemoteSupportConsole.csproj", "{B1268C03-66EB-4486-8BFC-B439225D9D54}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysTools", "lib/SysTools/SysTools.csproj", "{4A67D79F-F0C9-4BBC-9601-D5948E6C05D3}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SysTools", "Lib/SysTools/SysTools.csproj", "{4A67D79F-F0C9-4BBC-9601-D5948E6C05D3}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebServer", "lib/WebServer/WebServer.csproj", "{B2627B9F-41DF-44F7-A0D1-CA71FF4A007A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebServer", "Lib/WebServer/WebServer.csproj", "{B2627B9F-41DF-44F7-A0D1-CA71FF4A007A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Time", "lib/Time/Time.csproj", "{442A8366-C177-48FE-84A7-BDF6470A09FF}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Time", "Lib/Time/Time.csproj", "{442A8366-C177-48FE-84A7-BDF6470A09FF}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmuMeterDriver", "app/EmuMeterDriver/EmuMeterDriver.csproj", "{F65F33B0-3522-4008-8D1E-47EF8E4C7AC7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmuMeterDriver", "App/EmuMeterDriver/EmuMeterDriver.csproj", "{F65F33B0-3522-4008-8D1E-47EF8E4C7AC7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BmsTunnel", "app/BmsTunnel/BmsTunnel.csproj", "{40B45363-BE34-420B-8F87-775EE6EE3513}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BmsTunnel", "App/BmsTunnel/BmsTunnel.csproj", "{40B45363-BE34-420B-8F87-775EE6EE3513}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "app", "app", "{145597B4-3E30-45E6-9F72-4DD43194539A}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "App", "App", "{145597B4-3E30-45E6-9F72-4DD43194539A}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "lib", "lib", "{AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Lib", "Lib", "{AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3", "lib/S3/S3.csproj", "{C3639841-13F4-4F24-99C6-7D965593BF89}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SaliMax", "App/SaliMax/SaliMax.csproj", "{25073794-D859-4824-9984-194C7E928496}" 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}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StatusApi", "Lib/StatusApi/StatusApi.csproj", "{9D17E78C-8A70-43DB-A619-DC12D20D023D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Devices", "Devices", "{4931A385-24DC-4E78-BFF4-356F8D6D5183}" EndProject @@ -38,37 +32,47 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Victron", "Victron", "{BD8C EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Trumpf", "Trumpf", "{DDDBEFD0-5DEA-4C7C-A9F2-FDB4636CF092}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TruConvert", "lib/Devices/Trumpf/TruConvert/TruConvert.csproj", "{EF46CF7B-823E-4CB7-966F-EDDC144C7954}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TruConvert", "Lib/Devices/Trumpf/TruConvert/TruConvert.csproj", "{EF46CF7B-823E-4CB7-966F-EDDC144C7954}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TruConvertAc", "lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj", "{1F4B445E-459E-44CD-813E-6D725EBB81E8}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TruConvertAc", "Lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj", "{1F4B445E-459E-44CD-813E-6D725EBB81E8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TruConvertDc", "lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj", "{F6F29829-C31A-4994-A698-E441BEA631C6}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TruConvertDc", "Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj", "{F6F29829-C31A-4994-A698-E441BEA631C6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DBus", "lib/Protocols/DBus/DBus.csproj", "{8C3C620A-087D-4DD6-B493-A47FC643F8DC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DBus", "Lib/Protocols/DBus/DBus.csproj", "{8C3C620A-087D-4DD6-B493-A47FC643F8DC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modbus", "lib/Protocols/Modbus/Modbus.csproj", "{E4AE6A33-0DEB-48EB-9D57-C0C7C63FC267}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Modbus", "Lib/Protocols/Modbus/Modbus.csproj", "{E4AE6A33-0DEB-48EB-9D57-C0C7C63FC267}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VeDBus", "lib/Victron/VeDBus/VeDBus.csproj", "{50B26E29-1B99-4D07-BCA5-359CD550BBAA}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VeDBus", "Lib/Victron/VeDBus/VeDBus.csproj", "{50B26E29-1B99-4D07-BCA5-359CD550BBAA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VictronVRM", "lib/Victron/VictronVRM/VictronVRM.csproj", "{FE05DF69-B5C7-4C2E-8FB9-7776441A7622}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VictronVRM", "Lib/Victron/VictronVRM/VictronVRM.csproj", "{FE05DF69-B5C7-4C2E-8FB9-7776441A7622}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ampt", "lib/Devices/AMPT/Ampt.csproj", "{77AF3A64-2878-4150-BCD0-F16530783165}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ampt", "Lib/Devices/AMPT/Ampt.csproj", "{77AF3A64-2878-4150-BCD0-F16530783165}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Battery48TL", "lib/Devices/Battery48TL/Battery48TL.csproj", "{1C3F443A-B339-4B08-80E6-8A84817FFEC9}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Battery48TL", "Lib/Devices/Battery48TL/Battery48TL.csproj", "{1C3F443A-B339-4B08-80E6-8A84817FFEC9}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmuMeter", "lib/Devices/EmuMeter/EmuMeter.csproj", "{152A4168-F612-493C-BBEA-8EB26E6E2D34}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EmuMeter", "Lib/Devices/EmuMeter/EmuMeter.csproj", "{152A4168-F612-493C-BBEA-8EB26E6E2D34}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils", "lib/Utils/Utils.csproj", "{89A3E29C-4E57-47FE-A800-12AC68418264}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Utils", "Lib/Utils/Utils.csproj", "{89A3E29C-4E57-47FE-A800-12AC68418264}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adam6060", "lib/Devices/Adam6060/Adam6060.csproj", "{4AFDB799-E6A4-4DCA-8B6D-8C0F98398461}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Adam6060", "Lib/Devices/Adam6060/Adam6060.csproj", "{4AFDB799-E6A4-4DCA-8B6D-8C0F98398461}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Channels", "lib/Channels/Channels.csproj", "{AF7E8DCA-8D48-498E-AB3D-208061B244DC}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Channels", "Lib/Channels/Channels.csproj", "{AF7E8DCA-8D48-498E-AB3D-208061B244DC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend", "app/Backend/Backend.csproj", "{A56F58C2-B265-435B-A985-53B4D6F49B1A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Backend", "App/Backend/Backend.csproj", "{A56F58C2-B265-435B-A985-53B4D6F49B1A}" EndProject EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Units", "lib/Units/Units.csproj", "{C04FB6DA-23C6-46BB-9B21-8F4FBA32FFF7}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Units", "Lib\Units\Units.csproj", "{C04FB6DA-23C6-46BB-9B21-8F4FBA32FFF7}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{AED84693-C389-44C9-B2C0-ACB560189CF2}" + ProjectSection(SolutionItems) = preProject + InnovEnergy.props = InnovEnergy.props + App\InnovEnergy.App.props = App\InnovEnergy.App.props + Lib\InnovEnergy.Lib.props = Lib\InnovEnergy.Lib.props + InnovEnergy.sln.DotSettings = InnovEnergy.sln.DotSettings + ..\.gitignore = ..\.gitignore + EndProjectSection +EndProject + Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -108,14 +112,6 @@ Global {40B45363-BE34-420B-8F87-775EE6EE3513}.Debug|Any CPU.Build.0 = Debug|Any CPU {40B45363-BE34-420B-8F87-775EE6EE3513}.Release|Any CPU.ActiveCfg = Release|Any CPU {40B45363-BE34-420B-8F87-775EE6EE3513}.Release|Any CPU.Build.0 = Release|Any CPU - {C3639841-13F4-4F24-99C6-7D965593BF89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {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 @@ -193,12 +189,8 @@ Global {B2627B9F-41DF-44F7-A0D1-CA71FF4A007A} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} {F65F33B0-3522-4008-8D1E-47EF8E4C7AC7} = {145597B4-3E30-45E6-9F72-4DD43194539A} {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} {4931A385-24DC-4E78-BFF4-356F8D6D5183} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} {794FD07C-93E9-4803-982E-1CA261504AB5} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} {BD8CBC5C-0B9E-48A3-BC4E-725E3FAB2348} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} @@ -218,5 +210,6 @@ Global {AF7E8DCA-8D48-498E-AB3D-208061B244DC} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} {A56F58C2-B265-435B-A985-53B4D6F49B1A} = {145597B4-3E30-45E6-9F72-4DD43194539A} {C04FB6DA-23C6-46BB-9B21-8F4FBA32FFF7} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} + {4A67D79F-F0C9-4BBC-9601-D5948E6C05D3} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854} EndGlobalSection EndGlobal diff --git a/csharp/InnovEnergy.sln.DotSettings b/csharp/InnovEnergy.sln.DotSettings index b1ba507d9..0584ce254 100644 --- a/csharp/InnovEnergy.sln.DotSettings +++ b/csharp/InnovEnergy.sln.DotSettings @@ -1,7 +1,10 @@  - False + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + False + True + True True True True @@ -33,4 +36,6 @@ True True True - True \ No newline at end of file + True + + \ No newline at end of file diff --git a/csharp/Lib/Channels/Channels.csproj b/csharp/Lib/Channels/Channels.csproj new file mode 100644 index 000000000..9eb898d4e --- /dev/null +++ b/csharp/Lib/Channels/Channels.csproj @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/csharp/lib/Channels/CloseAfter.cs b/csharp/Lib/Channels/CloseAfter.cs similarity index 100% rename from csharp/lib/Channels/CloseAfter.cs rename to csharp/Lib/Channels/CloseAfter.cs diff --git a/csharp/lib/Channels/Connection.cs b/csharp/Lib/Channels/Connection.cs similarity index 100% rename from csharp/lib/Channels/Connection.cs rename to csharp/Lib/Channels/Connection.cs diff --git a/csharp/lib/Channels/Framed/Channel.cs b/csharp/Lib/Channels/Framed/Channel.cs similarity index 100% rename from csharp/lib/Channels/Framed/Channel.cs rename to csharp/Lib/Channels/Framed/Channel.cs diff --git a/csharp/lib/Channels/Stages/Channel.cs b/csharp/Lib/Channels/Stages/Channel.cs similarity index 100% rename from csharp/lib/Channels/Stages/Channel.cs rename to csharp/Lib/Channels/Stages/Channel.cs diff --git a/csharp/lib/Channels/Stages/ConnectedChannel.cs b/csharp/Lib/Channels/Stages/ConnectedChannel.cs similarity index 100% rename from csharp/lib/Channels/Stages/ConnectedChannel.cs rename to csharp/Lib/Channels/Stages/ConnectedChannel.cs diff --git a/csharp/lib/Channels/Stages/Stage.cs b/csharp/Lib/Channels/Stages/Stage.cs similarity index 100% rename from csharp/lib/Channels/Stages/Stage.cs rename to csharp/Lib/Channels/Stages/Stage.cs diff --git a/csharp/lib/Channels/V2/Bak/Connections/Connection.cs b/csharp/Lib/Channels/V2/Bak/Connections/Connection.cs similarity index 90% rename from csharp/lib/Channels/V2/Bak/Connections/Connection.cs rename to csharp/Lib/Channels/V2/Bak/Connections/Connection.cs index 82d0b9f67..ecd18b168 100644 --- a/csharp/lib/Channels/V2/Bak/Connections/Connection.cs +++ b/csharp/Lib/Channels/V2/Bak/Connections/Connection.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Lib.Channels.V2.Connections; +namespace InnovEnergy.Lib.Channels.V2.Bak.Connections; public abstract class Connection : IConnection where C : IDisposable { diff --git a/csharp/lib/Channels/V2/Bak/Connections/Connections.cs b/csharp/Lib/Channels/V2/Bak/Connections/Connections.cs similarity index 95% rename from csharp/lib/Channels/V2/Bak/Connections/Connections.cs rename to csharp/Lib/Channels/V2/Bak/Connections/Connections.cs index 1af0c31a1..12e2f7207 100644 --- a/csharp/lib/Channels/V2/Bak/Connections/Connections.cs +++ b/csharp/Lib/Channels/V2/Bak/Connections/Connections.cs @@ -1,6 +1,6 @@ using System.Net.Sockets; -namespace InnovEnergy.Lib.Channels.V2.Connections; +namespace InnovEnergy.Lib.Channels.V2.Bak.Connections; public class TcpClientConnection : Connection { diff --git a/csharp/lib/Channels/V2/Bak/Connections/Extensions.cs b/csharp/Lib/Channels/V2/Bak/Connections/Extensions.cs similarity index 83% rename from csharp/lib/Channels/V2/Bak/Connections/Extensions.cs rename to csharp/Lib/Channels/V2/Bak/Connections/Extensions.cs index 1dd40eb78..d681564ce 100644 --- a/csharp/lib/Channels/V2/Bak/Connections/Extensions.cs +++ b/csharp/Lib/Channels/V2/Bak/Connections/Extensions.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Lib.Channels.V2.Connections; +namespace InnovEnergy.Lib.Channels.V2.Bak.Connections; public static class Connection { diff --git a/csharp/lib/Channels/V2/Bak/Connections/IConnection.cs b/csharp/Lib/Channels/V2/Bak/Connections/IConnection.cs similarity index 76% rename from csharp/lib/Channels/V2/Bak/Connections/IConnection.cs rename to csharp/Lib/Channels/V2/Bak/Connections/IConnection.cs index 8490accec..7463f3132 100644 --- a/csharp/lib/Channels/V2/Bak/Connections/IConnection.cs +++ b/csharp/Lib/Channels/V2/Bak/Connections/IConnection.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Lib.Channels.V2.Connections; +namespace InnovEnergy.Lib.Channels.V2.Bak.Connections; public interface IConnection { diff --git a/csharp/lib/Channels/V2/Bak/GenericChannel.cs b/csharp/Lib/Channels/V2/Bak/GenericChannel.cs similarity index 95% rename from csharp/lib/Channels/V2/Bak/GenericChannel.cs rename to csharp/Lib/Channels/V2/Bak/GenericChannel.cs index 6cad9ba22..151bc8aea 100644 --- a/csharp/lib/Channels/V2/Bak/GenericChannel.cs +++ b/csharp/Lib/Channels/V2/Bak/GenericChannel.cs @@ -1,6 +1,6 @@ -using InnovEnergy.Lib.Channels.V2.Connections; +using InnovEnergy.Lib.Channels.V2.Bak.Connections; -namespace InnovEnergy.Lib.Channels.V2; +namespace InnovEnergy.Lib.Channels.V2.Bak; public abstract class GenericChannel2 : IChannel, IConnection { diff --git a/csharp/lib/Channels/V2/Bak/StreamChannel.cs b/csharp/Lib/Channels/V2/Bak/StreamChannel.cs similarity index 100% rename from csharp/lib/Channels/V2/Bak/StreamChannel.cs rename to csharp/Lib/Channels/V2/Bak/StreamChannel.cs diff --git a/csharp/lib/Channels/V2/CommandChannel.cs b/csharp/Lib/Channels/V2/CommandChannel.cs similarity index 100% rename from csharp/lib/Channels/V2/CommandChannel.cs rename to csharp/Lib/Channels/V2/CommandChannel.cs diff --git a/csharp/lib/Channels/V2/IChannel.cs b/csharp/Lib/Channels/V2/IChannel.cs similarity index 100% rename from csharp/lib/Channels/V2/IChannel.cs rename to csharp/Lib/Channels/V2/IChannel.cs diff --git a/csharp/lib/Channels/V2/Pipes/AsyncPipeSource.cs b/csharp/Lib/Channels/V2/Pipes/AsyncPipeSource.cs similarity index 100% rename from csharp/lib/Channels/V2/Pipes/AsyncPipeSource.cs rename to csharp/Lib/Channels/V2/Pipes/AsyncPipeSource.cs diff --git a/csharp/lib/Channels/V2/Pipes/AsyncPipeTarget.cs b/csharp/Lib/Channels/V2/Pipes/AsyncPipeTarget.cs similarity index 100% rename from csharp/lib/Channels/V2/Pipes/AsyncPipeTarget.cs rename to csharp/Lib/Channels/V2/Pipes/AsyncPipeTarget.cs diff --git a/csharp/lib/Channels/V2/Pipes/ChannelPipeSource.cs b/csharp/Lib/Channels/V2/Pipes/ChannelPipeSource.cs similarity index 100% rename from csharp/lib/Channels/V2/Pipes/ChannelPipeSource.cs rename to csharp/Lib/Channels/V2/Pipes/ChannelPipeSource.cs diff --git a/csharp/lib/Channels/V2/Pipes/ChannelPipeTarget.cs b/csharp/Lib/Channels/V2/Pipes/ChannelPipeTarget.cs similarity index 100% rename from csharp/lib/Channels/V2/Pipes/ChannelPipeTarget.cs rename to csharp/Lib/Channels/V2/Pipes/ChannelPipeTarget.cs diff --git a/csharp/lib/Channels/V2/Stage.cs b/csharp/Lib/Channels/V2/Stage.cs similarity index 100% rename from csharp/lib/Channels/V2/Stage.cs rename to csharp/Lib/Channels/V2/Stage.cs diff --git a/csharp/lib/Channels/V2/StreamChannel.cs b/csharp/Lib/Channels/V2/StreamChannel.cs similarity index 100% rename from csharp/lib/Channels/V2/StreamChannel.cs rename to csharp/Lib/Channels/V2/StreamChannel.cs diff --git a/csharp/lib/Channels/V2/TcpChannel.cs b/csharp/Lib/Channels/V2/TcpChannel.cs similarity index 100% rename from csharp/lib/Channels/V2/TcpChannel.cs rename to csharp/Lib/Channels/V2/TcpChannel.cs diff --git a/csharp/lib/Devices/AMPT/Ampt.csproj b/csharp/Lib/Devices/AMPT/Ampt.csproj similarity index 59% rename from csharp/lib/Devices/AMPT/Ampt.csproj rename to csharp/Lib/Devices/AMPT/Ampt.csproj index 5f3e41475..ea8cbf21c 100644 --- a/csharp/lib/Devices/AMPT/Ampt.csproj +++ b/csharp/Lib/Devices/AMPT/Ampt.csproj @@ -1,11 +1,5 @@ - - - - InnovEnergy.Lib.Devices.Ampt - InnovEnergy.Lib.Devices.Ampt - - + diff --git a/csharp/Lib/Devices/AMPT/AmptCommunicationUnit.cs b/csharp/Lib/Devices/AMPT/AmptCommunicationUnit.cs new file mode 100644 index 000000000..6491f4e8b --- /dev/null +++ b/csharp/Lib/Devices/AMPT/AmptCommunicationUnit.cs @@ -0,0 +1,118 @@ +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Protocols.Modbus.Connections; +using InnovEnergy.Lib.Units.Composite; +using static DecimalMath.DecimalEx; + +namespace InnovEnergy.Lib.Devices.AMPT; + +public class AmptCommunicationUnit +{ + private ModbusTcpClient? Modbus { get; set; } + + private const UInt16 RegistersPerDevice = 16; + private const UInt16 FirstDeviceOffset = 85; + + public String Hostname { get; } + public UInt16 Port { get; } + public Byte SlaveAddress { get; } + + public AmptCommunicationUnit(String hostname, UInt16 port = 502, Byte slaveAddress = 1) + { + Hostname = hostname; + Port = port; + SlaveAddress = slaveAddress; + } + + public AmptCommunicationUnitStatus? ReadStatus() + { + try + { + OpenConnection(); + return TryReadStatus(); + } + catch + { + CloseConnection(); + return null; + } + } + + private void CloseConnection() + { + try + { + Modbus?.CloseConnection(); + } + catch + { + // ignored + } + + Modbus = null; + } + + private void OpenConnection() + { + if (Modbus is null) + { + var connection = new ModbusTcpConnection(Hostname, Port); + Modbus = new ModbusTcpClient(connection, SlaveAddress); + } + } + + private AmptCommunicationUnitStatus TryReadStatus() + { + var r = Modbus!.ReadHoldingRegisters(1, 116); + + var currentFactor = Pow(10.0m, r.GetInt16(73)); + var voltageFactor = Pow(10.0m, r.GetInt16(74)); + var energyFactor = Pow(10.0m, r.GetInt16(76) + 3); // +3 => converted from Wh to kWh + var nbrOfDevices = r.GetUInt16(78); + + var devices = Enumerable + .Range(0, nbrOfDevices) + .Select(ReadDeviceStatus) + .ToList(); + + return new AmptCommunicationUnitStatus + { + Sid = r.GetUInt32(1), + IdSunSpec = r.GetUInt16(3), + Manufacturer = r.GetString(5, 16), + Model = r.GetString(21, 16), + Version = r.GetString(45, 8), + SerialNumber = r.GetString(53, 16), + DeviceAddress = r.GetInt16(69), + IdVendor = r.GetUInt16(71), + Devices = devices + }; + + AmptStatus ReadDeviceStatus(Int32 deviceNumber) + { + var baseAddress = (UInt16)(FirstDeviceOffset + deviceNumber * RegistersPerDevice); // base address + + return new AmptStatus + { + Dc = new DcBus + { + Voltage = r.GetUInt32((UInt16)(baseAddress + 6)) * voltageFactor, + Current = r.GetUInt16((UInt16)(baseAddress + 5)) * currentFactor + }, + Strings = new DcBus[] + { + new() + { + Voltage = r.GetUInt32((UInt16)(baseAddress + 8)) * voltageFactor, + Current = r.GetUInt16((UInt16)(baseAddress + 14)) * currentFactor + }, + new() + { + Voltage = r.GetUInt32((UInt16)(baseAddress + 9)) * voltageFactor, + Current = r.GetUInt16((UInt16)(baseAddress + 15)) * currentFactor + } + }, + ProductionToday = r.GetUInt32((UInt16)(baseAddress + 12)) * energyFactor, + }; + } + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/AMPT/AmptCommunicationUnitStatus.cs b/csharp/Lib/Devices/AMPT/AmptCommunicationUnitStatus.cs new file mode 100644 index 000000000..757d3592f --- /dev/null +++ b/csharp/Lib/Devices/AMPT/AmptCommunicationUnitStatus.cs @@ -0,0 +1,16 @@ +namespace InnovEnergy.Lib.Devices.AMPT; + +public record AmptCommunicationUnitStatus +{ + public UInt32 Sid { get; init; } // A well-known value 0x53756e53, uniquely identifies this as a SunSpec Modbus Map + public UInt16 IdSunSpec { get; init; } // A well-known value 1, uniquely identifies this as a SunSpec Common Model + + public String Manufacturer { get; init; } = "undefined"; // A well-known value registered with SunSpec for compliance: "Ampt" + public String Model { get; init; } = "undefined"; // Manufacturer specific value "Communication Unit" + public String Version { get; init; } = "undefined"; // Software Version + public String SerialNumber { get; init; } = "undefined"; // Manufacturer specific value + public Int16 DeviceAddress { get; init; } // Modbus Device ID + public UInt16 IdVendor { get; init; } // Ampt SunSpec Vendor Code 64050 + + public IReadOnlyList Devices { get; init; } = Array.Empty(); +} \ No newline at end of file diff --git a/csharp/Lib/Devices/AMPT/AmptStatus.cs b/csharp/Lib/Devices/AMPT/AmptStatus.cs new file mode 100644 index 000000000..8e2140bdc --- /dev/null +++ b/csharp/Lib/Devices/AMPT/AmptStatus.cs @@ -0,0 +1,9 @@ +using InnovEnergy.Lib.StatusApi; +using InnovEnergy.Lib.Units; + +namespace InnovEnergy.Lib.Devices.AMPT; + +public record AmptStatus : MpptStatus +{ + public Energy ProductionToday { get; init; } // converted to kW in AmptCU class +} diff --git a/csharp/Lib/Devices/Adam6060/Adam6060.csproj b/csharp/Lib/Devices/Adam6060/Adam6060.csproj new file mode 100644 index 000000000..1a36e43e1 --- /dev/null +++ b/csharp/Lib/Devices/Adam6060/Adam6060.csproj @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/csharp/lib/Devices/Adam6060/Adam6060Control.cs b/csharp/Lib/Devices/Adam6060/Adam6060Control.cs similarity index 100% rename from csharp/lib/Devices/Adam6060/Adam6060Control.cs rename to csharp/Lib/Devices/Adam6060/Adam6060Control.cs diff --git a/csharp/Lib/Devices/Adam6060/Adam6060Device.cs b/csharp/Lib/Devices/Adam6060/Adam6060Device.cs new file mode 100644 index 000000000..d910baf41 --- /dev/null +++ b/csharp/Lib/Devices/Adam6060/Adam6060Device.cs @@ -0,0 +1,103 @@ +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Protocols.Modbus.Connections; +using static InnovEnergy.Lib.Devices.Adam6060.Adam6060Status; +using static InnovEnergy.Lib.Devices.Adam6060.Adam6060Control; + +namespace InnovEnergy.Lib.Devices.Adam6060; + +public class Adam6060Device +{ + public String Hostname { get; } + public UInt16 Port { get; } + public Byte SlaveAddress { get; } + + private ModbusTcpClient? Modbus { get; set; } + + public Adam6060Device(String hostname, UInt16 port = 5004, Byte slaveAddress = 2) + { + Hostname = hostname; + Port = port; + SlaveAddress = slaveAddress; + } + + private void OpenConnection() + { + if (Modbus is null) + { + var connection = new ModbusTcpConnection(Hostname, Port); + Modbus = new ModbusTcpClient(connection, SlaveAddress); + } + } + + public Adam6060Status? ReadStatus() + { + try + { + OpenConnection(); + return TryReadStatus(); + } + catch + { + CloseConnection(); + return null; + } + } + + private void CloseConnection() + { + try + { + Modbus?.CloseConnection(); + } + catch + { + // ignored + } + + Modbus = null; + } + + private Adam6060Status TryReadStatus() + { + var inputs = Modbus!.ReadDiscreteInputs(DigitalInputsStartRegister, NbDigitalInputs); + var relays = Modbus!.ReadDiscreteInputs(RelaysStartRegister, NbRelays); + + return new Adam6060Status + { + DigitalInput0 = inputs[0], + DigitalInput1 = inputs[1], + DigitalInput2 = inputs[2], + DigitalInput3 = inputs[3], + DigitalInput4 = inputs[4], + DigitalInput5 = inputs[5], + + Relay0 = relays[0], + Relay1 = relays[1], + Relay2 = relays[2], + Relay3 = relays[3], + Relay4 = relays[4], + Relay5 = relays[5], + }; + } + + public Boolean WriteControl(Adam6060Control control) + { + try + { + OpenConnection(); + + Modbus!.WriteMultipleCoils(RelaysStartRegister, control.Relay0, + control.Relay1, + control.Relay2, + control.Relay3, + control.Relay4, + control.Relay5); + return true; + } + catch + { + CloseConnection(); + return false; + } + } +} \ No newline at end of file diff --git a/csharp/lib/Devices/Adam6060/Adam6060Status.cs b/csharp/Lib/Devices/Adam6060/Adam6060Status.cs similarity index 100% rename from csharp/lib/Devices/Adam6060/Adam6060Status.cs rename to csharp/Lib/Devices/Adam6060/Adam6060Status.cs diff --git a/csharp/lib/Devices/Battery48TL/Battery48TL.csproj b/csharp/Lib/Devices/Battery48TL/Battery48TL.csproj similarity index 54% rename from csharp/lib/Devices/Battery48TL/Battery48TL.csproj rename to csharp/Lib/Devices/Battery48TL/Battery48TL.csproj index 51feacfe1..ce3b83de6 100644 --- a/csharp/lib/Devices/Battery48TL/Battery48TL.csproj +++ b/csharp/Lib/Devices/Battery48TL/Battery48TL.csproj @@ -1,10 +1,5 @@ - - - - InnovEnergy.Lib.Devices.Battery48TL - InnovEnergy.Lib.Devices.Battery48TL - + diff --git a/csharp/Lib/Devices/Battery48TL/Battery48TLDevice.cs b/csharp/Lib/Devices/Battery48TL/Battery48TLDevice.cs new file mode 100644 index 000000000..6507ac32d --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/Battery48TLDevice.cs @@ -0,0 +1,47 @@ +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Protocols.Modbus.Connections; + +namespace InnovEnergy.Lib.Devices.Battery48TL; + +public class Battery48TlDevice +{ + private ModbusClient Modbus { get; } + + public Battery48TlDevice(String device, Byte nodeId) + { + var serialConnection = new ModbusSerialConnection(device, + Constants.BaudRate, + Constants.Parity, + Constants.DataBits, + Constants.StopBits, + Constants.Timeout); + + Modbus = new ModbusRtuClient(serialConnection, nodeId); + } + + private Battery48TlDevice(ModbusClient modbus) // TODO : remove nullable + { + Modbus = modbus; + } + + public static Battery48TlDevice Fake() // TODO : remove nullable + { + return new Battery48TlDevice(null!); + } + + public Battery48TLStatus? ReadStatus() //Already try catch is implemented + { + try + { + return Modbus + .ReadInputRegisters(Constants.BaseAddress, Constants.NoOfRegisters) + .ParseBatteryStatus(); + } + catch (Exception e) + { + Console.WriteLine(e.Message + " Battery "); + Modbus.CloseConnection(); + return null; + } + } +} \ No newline at end of file diff --git a/csharp/Lib/Devices/Battery48TL/Battery48TLStatusRecord.cs b/csharp/Lib/Devices/Battery48TL/Battery48TLStatusRecord.cs new file mode 100644 index 000000000..b9feb07d0 --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/Battery48TLStatusRecord.cs @@ -0,0 +1,52 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.StatusApi; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.Battery48TL; + +using T = Battery48TLStatus; + +[SuppressMessage("ReSharper", "InconsistentNaming")] +public record Battery48TLStatus : BatteryStatus +{ + public Voltage CellsVoltage { get; init; } + + public Power MaxChargingPower { get; init; } + public Power MaxDischargingPower { get; init; } + + public State GreenLed { get; init; } + public State AmberLed { get; init; } + public State BlueLed { get; init; } + public State RedLed { get; init; } + + public State Warnings { get; init; } + public State Alarms { get; init; } + + public State MainSwitchState { get; init; } // connected to bus | disconnected from bus + public State HeaterState { get; init; } // heating | not heating + public State EocState { get; init; } // EOC reached | EOC not reached + public State TemperatureState { get; init; } // cold | operating temperature | overheated + + + public static T operator |(T left, T right) => OpParallel(left, right); + private static readonly Func OpParallel = "|".CreateBinaryOpForProps(); + + + + // TODO: strings + // TODO + // public State LimitedBy { get; init; } + + // TODO + // public Boolean AlarmOutActive { get; init; } + // public Boolean InternalFanActive { get; init; } + // public Boolean VoltMeasurementAllowed { get; init; } + // public Boolean AuxRelay { get; init; } + // public Boolean RemoteState { get; init; } + +} + + + + diff --git a/csharp/lib/Devices/Battery48TL/Constants.cs b/csharp/Lib/Devices/Battery48TL/Constants.cs similarity index 100% rename from csharp/lib/Devices/Battery48TL/Constants.cs rename to csharp/Lib/Devices/Battery48TL/Constants.cs diff --git a/csharp/lib/Devices/Battery48TL/LedColor.cs b/csharp/Lib/Devices/Battery48TL/LedColor.cs similarity index 100% rename from csharp/lib/Devices/Battery48TL/LedColor.cs rename to csharp/Lib/Devices/Battery48TL/LedColor.cs diff --git a/csharp/lib/Devices/Battery48TL/LedState.cs b/csharp/Lib/Devices/Battery48TL/LedState.cs similarity index 100% rename from csharp/lib/Devices/Battery48TL/LedState.cs rename to csharp/Lib/Devices/Battery48TL/LedState.cs diff --git a/csharp/Lib/Devices/Battery48TL/ModbusParser.cs b/csharp/Lib/Devices/Battery48TL/ModbusParser.cs new file mode 100644 index 000000000..d16c3dc5d --- /dev/null +++ b/csharp/Lib/Devices/Battery48TL/ModbusParser.cs @@ -0,0 +1,272 @@ +using System.Diagnostics.CodeAnalysis; +using InnovEnergy.Lib.Protocols.Modbus.Conversions; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.Battery48TL; + +public static class ModbusParser +{ + internal static Battery48TLStatus ParseBatteryStatus(this ModbusRegisters data) + { + return new Battery48TLStatus + { + Dc = data.ParseDcBus(), + Alarms = data.ParseAlarms().ToList(), + Warnings = data.ParseWarnings().ToList(), + Soc = data.ParseSoc(), + Temperature = data.ParseTemperature(), + GreenLed = data.ParseGreenLed(), + AmberLed = data.ParseAmberLed(), + BlueLed = data.ParseBlueLed(), + RedLed = data.ParseRedLed(), + MainSwitchState = data.ParseMainSwitchState(), + HeaterState = data.ParseHeaterState(), + EocState = data.ParseEocState(), + TemperatureState = data.ParseTemperatureState(), + MaxChargingPower = data.CalcMaxChargePower(), + MaxDischargingPower = data.CalcMaxDischargePower(), + CellsVoltage = data.ParseCellsVoltage(), + }; + } + + + public static Decimal ParseDecimal(this ModbusRegisters data, Int32 register, Decimal scaleFactor = 1.0m, Double offset = 0.0) + { + var value = data[register].ConvertTo(); // widen to 32bit signed + + if (value >= 0x8000) + value -= 0x10000; // Fiamm stores their integers signed AND with sign-offset @#%^&! + + return (Decimal)(value + offset) * scaleFactor; + } + + internal static Decimal ParseCurrent(this ModbusRegisters data) + { + return data.ParseDecimal(register: 1001, scaleFactor: 0.01m, offset: -10000); + } + + internal static Decimal ParseCellsVoltage(this ModbusRegisters data) + { + return data.ParseDecimal(register: 1000, scaleFactor: 0.01m); + } + + internal static Decimal ParseBusVoltage(this ModbusRegisters data) + { + return data.ParseDecimal(register: 1002, scaleFactor: 0.01m); + } + + internal static Boolean ParseBool(this ModbusRegisters data, Int32 baseRegister, Int16 bit) + { + var x = bit / 16; + var y = bit % 16; + + var value = (UInt32)data[baseRegister + x]; + + return (value & (1 << y)) > 0; + } + + internal static LedState ParseLedState(this ModbusRegisters data, Int32 register, LedColor led) + { + var lo = data.ParseBool(register, (led.ConvertTo() * 2 ).ConvertTo()); + var hi = data.ParseBool(register, (led.ConvertTo() * 2 + 1).ConvertTo()); + + return (hi, lo) switch + { + (false, false) => LedState.Off, + (false, true) => LedState.On, + (true, false) => LedState.BlinkingSlow, + (true, true) => LedState.BlinkingFast, + }; + } + + + private static Boolean ParseEocReached(this ModbusRegisters data) + { + return ParseLedState(data, 1005, LedColor.Green) == LedState.On && + ParseLedState(data, 1005, LedColor.Amber) == LedState.Off && + ParseLedState(data, 1005, LedColor.Blue) == LedState.Off; + } + + internal static State ParseTemperatureState(this ModbusRegisters data) + { + return data.ParseBatteryCold() ? "cold" : "operating temperature"; // TODO: overheated, + } + + internal static Decimal ParseTemperature(this ModbusRegisters data) + { + return data.ParseDecimal(register: 1004, scaleFactor: 0.1m, offset: -400); + } + + internal static Decimal ParseSoc(this ModbusRegisters data) + { + return data.ParseDecimal(register: 1054, scaleFactor: 0.1m); + } + + internal static State ParseEocState(this ModbusRegisters data) + { + return data.ParseEocReached() ? "EOC reached" : "EOC not reached"; + } + + internal static State ParseHeaterState(this ModbusRegisters data) + { + return data.ParseBool(baseRegister: 1014, bit: 6) ? "heating" : "not heating"; + } + + internal static State ParseMainSwitchState(this ModbusRegisters data) + { + return data.ParseBool(baseRegister: 1014, bit: 0) ? "connected to bus" : "disconnected from bus"; + } + + internal static Boolean ParseBatteryCold(this ModbusRegisters data) + { + return ParseLedState(data, 1005, LedColor.Green) >= LedState.BlinkingSlow && + ParseLedState(data, 1005, LedColor.Blue) >= LedState.BlinkingSlow; + } + + private static Decimal CalcPowerLimitImposedByVoltageLimit(Decimal v,Decimal i,Decimal vLimit,Decimal rInt) + { + var dv = vLimit - v; + var di = dv / rInt; + var pLimit = vLimit * (i + di); + + return pLimit; + } + + private static Decimal CalcPowerLimitImposedByCurrentLimit(Decimal v, Decimal i, Decimal iLimit, Decimal rInt) + { + var di = iLimit - i; + var dv = di * rInt; + var pLimit = iLimit * (v + dv); + + return pLimit; + } + + + private static Decimal CalcPowerLimitImposedByTempLimit(Decimal t, Decimal maxAllowedTemp, Decimal power , Decimal setpoint) + { + // const Int32 holdZone = 300; + // const Int32 maxAllowedTemp = 315; + + var kp = 0.05m; + var error = setpoint - power; + var controlOutput = (kp * error) *(1 - Math.Abs((t-307.5m)/7.5m)); + + return controlOutput; + + // var a = holdZone - maxAllowedTemp; + // var b = -a * maxAllowedTemp; + } + + internal static Decimal CalcMaxChargePower(this ModbusRegisters data) + { + var v = ParseCellsVoltage(data); + var i = ParseCurrent(data); + + var pLimits = new[] + { + CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMax, Constants.RIntMin), + CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMax, Constants.RIntMax), + CalcPowerLimitImposedByCurrentLimit(v, i, Constants.IMax, Constants.RIntMin), + CalcPowerLimitImposedByCurrentLimit(v, i, Constants.IMax, Constants.RIntMax) + }; + + var pLimit = pLimits.Min(); + + return Math.Max(pLimit, 0); + } + + internal static DcBus ParseDcBus(this ModbusRegisters data) + { + return new() + { + Current = data.ParseCurrent(), + Voltage = data.ParseBusVoltage(), + }; + } + + internal static Decimal CalcMaxDischargePower(this ModbusRegisters data) + { + var v = ParseCellsVoltage(data); + var i = ParseCurrent(data); + var t = data.ParseDecimal(register: 1004, scaleFactor: 0.1m, offset: -400); + + var pLimits = new[] + { + CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMin, Constants.RIntMin), + CalcPowerLimitImposedByVoltageLimit(v, i, Constants.VMin, Constants.RIntMax), + CalcPowerLimitImposedByCurrentLimit(v, i, -Constants.IMax, Constants.RIntMin), + CalcPowerLimitImposedByCurrentLimit(v, i, -Constants.IMax, Constants.RIntMax), + // CalcPowerLimitImposedByTempLimit(t,315,300) + }; + + var pLimit = pLimits.Max(); + + return Math.Min(pLimit, 0); + } + + + internal static LedState ParseGreenLed(this ModbusRegisters data) => data.ParseLedState(register: 1005, led: LedColor.Green); + internal static LedState ParseAmberLed(this ModbusRegisters data) => data.ParseLedState(register: 1006, led: LedColor.Amber); + internal static LedState ParseBlueLed (this ModbusRegisters data) => data.ParseLedState(register: 1005, led: LedColor.Blue); + internal static LedState ParseRedLed (this ModbusRegisters data) => data.ParseLedState(register: 1005, led: LedColor.Red); + + + [SuppressMessage("ReSharper", "StringLiteralTypo")] + internal static IEnumerable ParseAlarms(this ModbusRegisters data) + { + if (data.ParseBool(1010, 0)) yield return "Tam : BMS temperature too low"; + if (data.ParseBool(1010, 2)) yield return "TaM2 : BMS temperature too high"; + if (data.ParseBool(1010, 3)) yield return "Tbm : Battery temperature too low"; + if (data.ParseBool(1010, 5)) yield return "TbM2 : Battery temperature too high"; + if (data.ParseBool(1010, 7)) yield return "VBm2 : Bus voltage too low"; + if (data.ParseBool(1010, 9)) yield return "VBM2 : Bus voltage too high"; + if (data.ParseBool(1010, 11)) yield return "IDM2 : Discharge current too high"; + if (data.ParseBool(1010, 12)) yield return "ISOB : Electrical insulation failure"; + if (data.ParseBool(1010, 13)) yield return "MSWE : Main switch failure"; + if (data.ParseBool(1010, 14)) yield return "FUSE : Main fuse blown"; + if (data.ParseBool(1010, 15)) yield return "HTRE : Battery failed to warm up"; + if (data.ParseBool(1010, 16)) yield return "TCPE : Temperature sensor failure"; + if (data.ParseBool(1010, 17)) yield return "STRE :"; + if (data.ParseBool(1010, 18)) yield return "CME : Current sensor failure"; + if (data.ParseBool(1010, 19)) yield return "HWFL : BMS hardware failure"; + if (data.ParseBool(1010, 20)) yield return "HWEM : Hardware protection tripped"; + if (data.ParseBool(1010, 21)) yield return "ThM : Heatsink temperature too high"; + if (data.ParseBool(1010, 22)) yield return "vsm1 : String voltage too low"; + if (data.ParseBool(1010, 23)) yield return "vsm2 : Low string voltage failure"; + if (data.ParseBool(1010, 25)) yield return "vsM2 : String voltage too high"; + if (data.ParseBool(1010, 27)) yield return "iCM2 : Charge current too high"; + if (data.ParseBool(1010, 29)) yield return "iDM2 : Discharge current too high"; + if (data.ParseBool(1010, 31)) yield return "MID2 : String voltage unbalance too high"; + if (data.ParseBool(1010, 33)) yield return "CCBF : Internal charger hardware failure"; + if (data.ParseBool(1010, 34)) yield return "AhFL :"; + if (data.ParseBool(1010, 36)) yield return "TbCM :"; + if (data.ParseBool(1010, 37)) yield return "BRNF :"; + if (data.ParseBool(1010, 42)) yield return "HTFS : If Heaters Fuse Blown"; + if (data.ParseBool(1010, 43)) yield return "DATA : Parameters out of range"; + if (data.ParseBool(1010, 45)) yield return "CELL2:"; + } + + [SuppressMessage("ReSharper", "StringLiteralTypo")] + internal static IEnumerable ParseWarnings(this ModbusRegisters data) + { + if (data.ParseBool(1006, 1)) yield return "TaM1: BMS temperature high"; + if (data.ParseBool(1006, 4)) yield return "TbM1: Battery temperature high"; + if (data.ParseBool(1006, 6)) yield return "VBm1: Bus voltage low"; + if (data.ParseBool(1006, 8)) yield return "VBM1: Bus voltage high"; + if (data.ParseBool(1006, 10)) yield return "IDM1: Discharge current high"; + if (data.ParseBool(1006, 24)) yield return "vsM1: String voltage high"; + if (data.ParseBool(1006, 26)) yield return "iCM1: Charge current high"; + if (data.ParseBool(1006, 28)) yield return "iDM1: Discharge current high"; + if (data.ParseBool(1006, 30)) yield return "MID1: String voltages unbalanced"; + if (data.ParseBool(1006, 32)) yield return "BLPW: Not enough charging power on bus"; + if (data.ParseBool(1006, 35)) yield return "Ah_W: String SOC low"; + if (data.ParseBool(1006, 38)) yield return "MPMM: Midpoint wiring problem"; + if (data.ParseBool(1006, 39)) yield return "TCMM:"; + if (data.ParseBool(1006, 40)) yield return "TCdi: Temperature difference between strings high"; + if (data.ParseBool(1006, 41)) yield return "WMTO:"; + if (data.ParseBool(1006, 44)) yield return "bit44:"; + if (data.ParseBool(1006, 46)) yield return "CELL1:"; + } +} \ No newline at end of file diff --git a/csharp/lib/Devices/EmuMeter/Conversions.cs b/csharp/Lib/Devices/EmuMeter/Conversions.cs similarity index 100% rename from csharp/lib/Devices/EmuMeter/Conversions.cs rename to csharp/Lib/Devices/EmuMeter/Conversions.cs diff --git a/csharp/lib/Devices/EmuMeter/EmuMeter.csproj b/csharp/Lib/Devices/EmuMeter/EmuMeter.csproj similarity index 55% rename from csharp/lib/Devices/EmuMeter/EmuMeter.csproj rename to csharp/Lib/Devices/EmuMeter/EmuMeter.csproj index c05adbe6b..ce3b83de6 100644 --- a/csharp/lib/Devices/EmuMeter/EmuMeter.csproj +++ b/csharp/Lib/Devices/EmuMeter/EmuMeter.csproj @@ -1,10 +1,5 @@ - - - - InnovEnergy.Lib.Devices.EmuMeter - InnovEnergy.Lib.Devices.EmuMeter - + diff --git a/csharp/Lib/Devices/EmuMeter/EmuMeterDevice.cs b/csharp/Lib/Devices/EmuMeter/EmuMeterDevice.cs new file mode 100644 index 000000000..f26117c3f --- /dev/null +++ b/csharp/Lib/Devices/EmuMeter/EmuMeterDevice.cs @@ -0,0 +1,97 @@ +using InnovEnergy.Lib.Protocols.Modbus.Clients; +using InnovEnergy.Lib.Protocols.Modbus.Connections; +using InnovEnergy.Lib.Units.Composite; +using static DecimalMath.DecimalEx; + +namespace InnovEnergy.Lib.Devices.EmuMeter; + +public class EmuMeterDevice +{ + private ModbusTcpClient Modbus { get; } + + public EmuMeterDevice(String hostname, UInt16 port = 502, Byte slaveId = 1) + { + var connection = new ModbusTcpConnection(hostname, port); + Modbus = new ModbusTcpClient(connection, slaveId); + } + + public EmuMeterStatus? ReadStatus() + { + try + { + return TryReadStatus(); + } + catch (Exception) + { + Modbus.CloseConnection(); + return null; + } + } + + + //private static Decimal GetPhi(Decimal cosPhi) => cosPhi.Clamp(-1m, 1m).Apply(ACos); + + private EmuMeterStatus TryReadStatus() + { + // Console.WriteLine("Reading Emu Meter Data"); + + + // TODO: get SerialNb, depends on Little/Big Endian support in Modbus Lib + // var registers = Modbus.ReadHoldingRegisters(5001, 4); + // var id = registers.GetInt32(5001); + + var powerCurrent = Modbus.ReadHoldingRegisters(9000, 108).ToDecimals(); // TODO "ModbusRegisters" + var voltageFreq = Modbus.ReadHoldingRegisters(9200, 112).ToDecimals(); // To check with Ivo + + // var energyPhases = Modbus.ReadHoldingRegisters(6100, 104).ToUInt64s(); + + var activePowerL1 = powerCurrent[1]; + var activePowerL2 = powerCurrent[2]; + var activePowerL3 = powerCurrent[3]; + var reactivePowerL1 = powerCurrent[6]; + var reactivePowerL2 = powerCurrent[7]; + var reactivePowerL3 = powerCurrent[8]; + + var currentL1 = powerCurrent[51]; + var currentL2 = powerCurrent[52]; + var currentL3 = powerCurrent[53]; + + var voltageL1N = voltageFreq[0]; + var voltageL2N = voltageFreq[1]; + var voltageL3N = voltageFreq[2]; + var frequency = voltageFreq[55]; + + + var l1 = new AcPhase + { + Current = currentL1, + Voltage = voltageL1N, + Phi = ATan2(reactivePowerL1, activePowerL1) // TODO: check that this works + }; + var l2 = new AcPhase + { + Current = currentL2, + Voltage = voltageL2N, + Phi = ATan2(reactivePowerL2, activePowerL2) + }; + var l3 = new AcPhase + { + Current = currentL3, + Voltage = voltageL3N, + Phi = ATan2(reactivePowerL3, activePowerL3) + }; + + return new EmuMeterStatus + { + Ac = new Ac3Bus + { + Frequency = frequency, + L1 = l1, + L2 = l2, + L3 = l3 + } + }; + + } + +} \ No newline at end of file diff --git a/csharp/Lib/Devices/EmuMeter/EmuMeterStatus.cs b/csharp/Lib/Devices/EmuMeter/EmuMeterStatus.cs new file mode 100644 index 000000000..1290843af --- /dev/null +++ b/csharp/Lib/Devices/EmuMeter/EmuMeterStatus.cs @@ -0,0 +1,10 @@ +using InnovEnergy.Lib.StatusApi; + +namespace InnovEnergy.Lib.Devices.EmuMeter; + +public record EmuMeterStatus : PowerMeterStatus +{ + // TODO: additional Measurements, device id +} + + diff --git a/csharp/Lib/Devices/EmuMeter/doc/ModbusRegisters.pdf b/csharp/Lib/Devices/EmuMeter/doc/ModbusRegisters.pdf new file mode 100644 index 000000000..77bd4acf6 Binary files /dev/null and b/csharp/Lib/Devices/EmuMeter/doc/ModbusRegisters.pdf differ diff --git a/csharp/lib/Devices/Trumpf/TruConvert/AlarmState.cs b/csharp/Lib/Devices/Trumpf/TruConvert/AlarmState.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvert/AlarmState.cs rename to csharp/Lib/Devices/Trumpf/TruConvert/AlarmState.cs diff --git a/csharp/lib/Devices/Trumpf/TruConvert/MainState.cs b/csharp/Lib/Devices/Trumpf/TruConvert/MainState.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvert/MainState.cs rename to csharp/Lib/Devices/Trumpf/TruConvert/MainState.cs diff --git a/csharp/lib/Devices/Trumpf/TruConvert/Slave.cs b/csharp/Lib/Devices/Trumpf/TruConvert/Slave.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvert/Slave.cs rename to csharp/Lib/Devices/Trumpf/TruConvert/Slave.cs diff --git a/csharp/lib/Devices/Trumpf/TruConvert/SystemConfig.cs b/csharp/Lib/Devices/Trumpf/TruConvert/SystemConfig.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvert/SystemConfig.cs rename to csharp/Lib/Devices/Trumpf/TruConvert/SystemConfig.cs diff --git a/csharp/Lib/Devices/Trumpf/TruConvert/TruConvert.csproj b/csharp/Lib/Devices/Trumpf/TruConvert/TruConvert.csproj new file mode 100644 index 000000000..ca3eb7a2a --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvert/TruConvert.csproj @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/csharp/lib/Devices/Trumpf/TruConvert/Utils.cs b/csharp/Lib/Devices/Trumpf/TruConvert/Utils.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvert/Utils.cs rename to csharp/Lib/Devices/Trumpf/TruConvert/Utils.cs diff --git a/csharp/lib/Devices/Trumpf/TruConvertAc/AcControlRegisters.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/AcControlRegisters.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvertAc/AcControlRegisters.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/AcControlRegisters.cs diff --git a/csharp/lib/Devices/Trumpf/TruConvertAc/AcEnums.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/AcEnums.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvertAc/AcEnums.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/AcEnums.cs diff --git a/csharp/lib/Devices/Trumpf/TruConvertAc/AlarmMessage.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/AlarmMessage.cs similarity index 99% rename from csharp/lib/Devices/Trumpf/TruConvertAc/AlarmMessage.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/AlarmMessage.cs index aceada544..df5535008 100644 --- a/csharp/lib/Devices/Trumpf/TruConvertAc/AlarmMessage.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/AlarmMessage.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvert; +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; [SuppressMessage("ReSharper", "IdentifierTypo")] [SuppressMessage("ReSharper", "UnusedMember.Global")] diff --git a/csharp/lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj similarity index 50% rename from csharp/lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj rename to csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj index 31669d385..3a70b12a0 100644 --- a/csharp/lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAc.csproj @@ -1,11 +1,7 @@ - + - - InnovEnergy.Lib.Devices.Trumpf.TruConvertAc - InnovEnergy.Lib.Devices.Trumpf.TruConvertAc - latest - + diff --git a/csharp/lib/Devices/Trumpf/TruConvertAc/TruConvertAcControl.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcControl.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvertAc/TruConvertAcControl.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcControl.cs diff --git a/csharp/lib/Devices/Trumpf/TruConvertAc/TruConvertAcDevice.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcDevice.cs similarity index 99% rename from csharp/lib/Devices/Trumpf/TruConvertAc/TruConvertAcDevice.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcDevice.cs index 6308553aa..66cc153a7 100644 --- a/csharp/lib/Devices/Trumpf/TruConvertAc/TruConvertAcDevice.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcDevice.cs @@ -2,8 +2,7 @@ using System.Diagnostics.CodeAnalysis; using InnovEnergy.Lib.Devices.Trumpf.TruConvert; using InnovEnergy.Lib.Protocols.Modbus.Clients; using InnovEnergy.Lib.Protocols.Modbus.Connections; -using InnovEnergy.Lib.StatusApi.Connections; -using InnovEnergy.Lib.StatusApi.Phases; +using InnovEnergy.Lib.Units.Composite; using InnovEnergy.Lib.Utils; using static DecimalMath.DecimalEx; using static InnovEnergy.Lib.Devices.Trumpf.TruConvertAc.AcControlRegisters; @@ -216,7 +215,7 @@ public class TruConvertAcDevice return new TruConvertAcStatus ( - Ac: new ThreePhaseAcConnection + Ac: new Ac3Bus ( new AcPhase(gridVoltageL1,phaseCurrentL1, ACos(powerAcL1/apparentPowerAcL1)), new AcPhase(gridVoltageL2,phaseCurrentL2, ACos(powerAcL2/apparentPowerAcL2)), diff --git a/csharp/lib/Devices/Trumpf/TruConvertAc/TruConvertAcStatus.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcStatus.cs similarity index 94% rename from csharp/lib/Devices/Trumpf/TruConvertAc/TruConvertAcStatus.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcStatus.cs index 8f2047ebb..4747e0579 100644 --- a/csharp/lib/Devices/Trumpf/TruConvertAc/TruConvertAcStatus.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/TruConvertAcStatus.cs @@ -1,6 +1,6 @@ using InnovEnergy.Lib.Devices.Trumpf.TruConvert; using InnovEnergy.Lib.StatusApi.Connections; -using InnovEnergy.Lib.StatusApi.Devices; +using InnovEnergy.Lib.Units.Composite; namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; @@ -9,7 +9,7 @@ using WarningMessages = IReadOnlyList; public record TruConvertAcStatus ( - ThreePhaseAcConnection Ac, + Ac3Bus Ac, DcConnection Dc, String SerialNumber, MainState MainState, diff --git a/csharp/lib/Devices/Trumpf/TruConvertAc/WarningMessage.cs b/csharp/Lib/Devices/Trumpf/TruConvertAc/WarningMessage.cs similarity index 96% rename from csharp/lib/Devices/Trumpf/TruConvertAc/WarningMessage.cs rename to csharp/Lib/Devices/Trumpf/TruConvertAc/WarningMessage.cs index 45a5538de..2fb74b947 100644 --- a/csharp/lib/Devices/Trumpf/TruConvertAc/WarningMessage.cs +++ b/csharp/Lib/Devices/Trumpf/TruConvertAc/WarningMessage.cs @@ -1,6 +1,6 @@ using System.Diagnostics.CodeAnalysis; -namespace InnovEnergy.Lib.Devices.Trumpf.TruConvert; +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertAc; [SuppressMessage("ReSharper", "IdentifierTypo")] [SuppressMessage("ReSharper", "UnusedMember.Global")] diff --git a/csharp/lib/Devices/Trumpf/TruConvertDc/AlarmMessage.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/AlarmMessage.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvertDc/AlarmMessage.cs rename to csharp/Lib/Devices/Trumpf/TruConvertDc/AlarmMessage.cs diff --git a/csharp/lib/Devices/Trumpf/TruConvertDc/DcControlRegisters.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcControlRegisters.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvertDc/DcControlRegisters.cs rename to csharp/Lib/Devices/Trumpf/TruConvertDc/DcControlRegisters.cs diff --git a/csharp/lib/Devices/Trumpf/TruConvertDc/DcEnums.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/DcEnums.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvertDc/DcEnums.cs rename to csharp/Lib/Devices/Trumpf/TruConvertDc/DcEnums.cs diff --git a/csharp/lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj similarity index 50% rename from csharp/lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj rename to csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj index c2acc0062..1628a2b19 100644 --- a/csharp/lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj @@ -1,11 +1,5 @@ - - - - InnovEnergy.Lib.Devices.Trumpf.TruConvertDc - InnovEnergy.Lib.Devices.Trumpf.TruConvertDc - latest - + diff --git a/csharp/lib/Devices/Trumpf/TruConvertDc/TruConvertDcControl.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcControl.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvertDc/TruConvertDcControl.cs rename to csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcControl.cs diff --git a/csharp/lib/Devices/Trumpf/TruConvertDc/TruConvertDcDevice.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcDevice.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvertDc/TruConvertDcDevice.cs rename to csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcDevice.cs diff --git a/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcStatus.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcStatus.cs new file mode 100644 index 000000000..bf78b928b --- /dev/null +++ b/csharp/Lib/Devices/Trumpf/TruConvertDc/TruConvertDcStatus.cs @@ -0,0 +1,32 @@ +using InnovEnergy.Lib.StatusApi; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Devices.Trumpf.TruConvertDc; + +using AlarmMessages = IReadOnlyList; +using WarningMessages = IReadOnlyList; +using DcCurrentLimitStates = IReadOnlyList; + +public record TruConvertDcStatus +( + DcBus DcLeft, + DcBus DcRight, + State MainState, + Power TotalDcPower, // TODO: necessary? + State StatusOfCurrentLimiting, + Decimal OverloadCapacity, + Temperature DcDcInletTemperature, + State Alarms, + State Warnings, + State PowerOperation + + // UInt16 NumberOfConnectedSlaves, // TODO: necessary? + // UInt16 NumberOfConnectedSubSlaves, // TODO: necessary? +) : + DcDcConverterStatus(DcLeft, DcRight) +{ + public static TruConvertDcStatus operator |(TruConvertDcStatus left, TruConvertDcStatus right) => OpParallel(left, right); + private static readonly Func OpParallel = Operators.Op("|"); +} \ No newline at end of file diff --git a/csharp/lib/Devices/Trumpf/TruConvertDc/WarningMessage.cs b/csharp/Lib/Devices/Trumpf/TruConvertDc/WarningMessage.cs similarity index 100% rename from csharp/lib/Devices/Trumpf/TruConvertDc/WarningMessage.cs rename to csharp/Lib/Devices/Trumpf/TruConvertDc/WarningMessage.cs diff --git a/csharp/lib/InnovEnergy.lib.props b/csharp/Lib/InnovEnergy.Lib.props similarity index 75% rename from csharp/lib/InnovEnergy.lib.props rename to csharp/Lib/InnovEnergy.Lib.props index 0e1ca7736..de75048b8 100644 --- a/csharp/lib/InnovEnergy.lib.props +++ b/csharp/Lib/InnovEnergy.Lib.props @@ -4,6 +4,7 @@ Library + $(RootNamespace) diff --git a/csharp/lib/Protocols/DBus/Bus.cs b/csharp/Lib/Protocols/DBus/Bus.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Bus.cs rename to csharp/Lib/Protocols/DBus/Bus.cs diff --git a/csharp/lib/Protocols/DBus/DBus.csproj b/csharp/Lib/Protocols/DBus/DBus.csproj similarity index 70% rename from csharp/lib/Protocols/DBus/DBus.csproj rename to csharp/Lib/Protocols/DBus/DBus.csproj index 7f74a94ad..01a9e0400 100644 --- a/csharp/lib/Protocols/DBus/DBus.csproj +++ b/csharp/Lib/Protocols/DBus/DBus.csproj @@ -1,11 +1,9 @@ - + DBus Library true - InnovEnergy.Lib.Protocols.DBus - InnovEnergy.Lib.Protocols.DBus diff --git a/csharp/lib/Protocols/DBus/DBusConnection.cs b/csharp/Lib/Protocols/DBus/DBusConnection.cs similarity index 100% rename from csharp/lib/Protocols/DBus/DBusConnection.cs rename to csharp/Lib/Protocols/DBus/DBusConnection.cs diff --git a/csharp/lib/Protocols/DBus/DBusMessageStream.cs b/csharp/Lib/Protocols/DBus/DBusMessageStream.cs similarity index 100% rename from csharp/lib/Protocols/DBus/DBusMessageStream.cs rename to csharp/Lib/Protocols/DBus/DBusMessageStream.cs diff --git a/csharp/lib/Protocols/DBus/DBusService.cs b/csharp/Lib/Protocols/DBus/DBusService.cs similarity index 100% rename from csharp/lib/Protocols/DBus/DBusService.cs rename to csharp/Lib/Protocols/DBus/DBusService.cs diff --git a/csharp/lib/Protocols/DBus/Daemon/DBusDaemonApi.cs b/csharp/Lib/Protocols/DBus/Daemon/DBusDaemonApi.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Daemon/DBusDaemonApi.cs rename to csharp/Lib/Protocols/DBus/Daemon/DBusDaemonApi.cs diff --git a/csharp/lib/Protocols/DBus/Daemon/DBusDaemonConnection.Resolver.cs b/csharp/Lib/Protocols/DBus/Daemon/DBusDaemonConnection.Resolver.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Daemon/DBusDaemonConnection.Resolver.cs rename to csharp/Lib/Protocols/DBus/Daemon/DBusDaemonConnection.Resolver.cs diff --git a/csharp/lib/Protocols/DBus/Daemon/DBusDaemonConnection.cs b/csharp/Lib/Protocols/DBus/Daemon/DBusDaemonConnection.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Daemon/DBusDaemonConnection.cs rename to csharp/Lib/Protocols/DBus/Daemon/DBusDaemonConnection.cs diff --git a/csharp/lib/Protocols/DBus/Daemon/MatchRule.cs b/csharp/Lib/Protocols/DBus/Daemon/MatchRule.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Daemon/MatchRule.cs rename to csharp/Lib/Protocols/DBus/Daemon/MatchRule.cs diff --git a/csharp/lib/Protocols/DBus/Daemon/ObservableDictionary.cs b/csharp/Lib/Protocols/DBus/Daemon/ObservableDictionary.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Daemon/ObservableDictionary.cs rename to csharp/Lib/Protocols/DBus/Daemon/ObservableDictionary.cs diff --git a/csharp/lib/Protocols/DBus/Daemon/ReleaseNameReply.cs b/csharp/Lib/Protocols/DBus/Daemon/ReleaseNameReply.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Daemon/ReleaseNameReply.cs rename to csharp/Lib/Protocols/DBus/Daemon/ReleaseNameReply.cs diff --git a/csharp/lib/Protocols/DBus/Daemon/RequestNameOptions.cs b/csharp/Lib/Protocols/DBus/Daemon/RequestNameOptions.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Daemon/RequestNameOptions.cs rename to csharp/Lib/Protocols/DBus/Daemon/RequestNameOptions.cs diff --git a/csharp/lib/Protocols/DBus/Daemon/RequestNameReply.cs b/csharp/Lib/Protocols/DBus/Daemon/RequestNameReply.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Daemon/RequestNameReply.cs rename to csharp/Lib/Protocols/DBus/Daemon/RequestNameReply.cs diff --git a/csharp/lib/Protocols/DBus/Env.cs b/csharp/Lib/Protocols/DBus/Env.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Env.cs rename to csharp/Lib/Protocols/DBus/Env.cs diff --git a/csharp/lib/Protocols/DBus/Interop.cs b/csharp/Lib/Protocols/DBus/Interop.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Interop.cs rename to csharp/Lib/Protocols/DBus/Interop.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Convert/StringToSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Convert/StringToSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Convert/StringToSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Convert/StringToSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Convert/TypeToSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Convert/TypeToSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Convert/TypeToSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Convert/TypeToSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/ObjectPath.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/ObjectPath.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/ObjectPath.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/ObjectPath.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Signature.Equality.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Signature.Equality.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Signature.Equality.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Signature.Equality.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Signature.Terminals.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Signature.Terminals.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Signature.Terminals.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Signature.Terminals.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Signature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Signature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Signature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Signature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ArraySignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ArraySignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ArraySignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ArraySignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/BasicTypeSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/BasicTypeSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/BasicTypeSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/BasicTypeSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/BooleanSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/BooleanSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/BooleanSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/BooleanSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ByteSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ByteSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ByteSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ByteSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/CompositeSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/CompositeSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/CompositeSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/CompositeSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ContainerTypeSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ContainerTypeSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ContainerTypeSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ContainerTypeSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/DictionarySignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/DictionarySignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/DictionarySignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/DictionarySignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/DoubleSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/DoubleSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/DoubleSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/DoubleSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/EmptySignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/EmptySignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/EmptySignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/EmptySignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/FixedTypeSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/FixedTypeSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/FixedTypeSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/FixedTypeSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/Int16Signature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/Int16Signature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/Int16Signature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/Int16Signature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/Int32Signature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/Int32Signature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/Int32Signature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/Int32Signature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/Int64Signature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/Int64Signature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/Int64Signature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/Int64Signature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ObjectPathSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ObjectPathSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ObjectPathSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/ObjectPathSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/SignatureTypeSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/SignatureTypeSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/SignatureTypeSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/SignatureTypeSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/StringLikeTypeSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/StringLikeTypeSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/StringLikeTypeSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/StringLikeTypeSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/StringSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/StringSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/StringSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/StringSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/StructSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/StructSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/StructSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/StructSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/UInt16Signature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/UInt16Signature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/UInt16Signature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/UInt16Signature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/UInt32Signature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/UInt32Signature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/UInt32Signature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/UInt32Signature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/UInt64Signature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/UInt64Signature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/UInt64Signature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/UInt64Signature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/VariantSignature.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/VariantSignature.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/VariantSignature.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Signatures/Specialized/VariantSignature.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/DataTypes/Variant.cs b/csharp/Lib/Protocols/DBus/Protocol/DataTypes/Variant.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/DataTypes/Variant.cs rename to csharp/Lib/Protocols/DBus/Protocol/DataTypes/Variant.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/Header/Endian.cs b/csharp/Lib/Protocols/DBus/Protocol/Header/Endian.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/Header/Endian.cs rename to csharp/Lib/Protocols/DBus/Protocol/Header/Endian.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/Header/FieldCode.cs b/csharp/Lib/Protocols/DBus/Protocol/Header/FieldCode.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/Header/FieldCode.cs rename to csharp/Lib/Protocols/DBus/Protocol/Header/FieldCode.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/Header/HeaderExtensions.cs b/csharp/Lib/Protocols/DBus/Protocol/Header/HeaderExtensions.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/Header/HeaderExtensions.cs rename to csharp/Lib/Protocols/DBus/Protocol/Header/HeaderExtensions.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/Header/HeaderFlags.cs b/csharp/Lib/Protocols/DBus/Protocol/Header/HeaderFlags.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/Header/HeaderFlags.cs rename to csharp/Lib/Protocols/DBus/Protocol/Header/HeaderFlags.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/Header/MessageType.cs b/csharp/Lib/Protocols/DBus/Protocol/Header/MessageType.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/Header/MessageType.cs rename to csharp/Lib/Protocols/DBus/Protocol/Header/MessageType.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/Message.cs b/csharp/Lib/Protocols/DBus/Protocol/Message.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/Message.cs rename to csharp/Lib/Protocols/DBus/Protocol/Message.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/Replies.cs b/csharp/Lib/Protocols/DBus/Protocol/Replies.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/Replies.cs rename to csharp/Lib/Protocols/DBus/Protocol/Replies.cs diff --git a/csharp/lib/Protocols/DBus/Protocol/SerialSource.cs b/csharp/Lib/Protocols/DBus/Protocol/SerialSource.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Protocol/SerialSource.cs rename to csharp/Lib/Protocols/DBus/Protocol/SerialSource.cs diff --git a/csharp/lib/Protocols/DBus/Transport/AuthenticationMethod.cs b/csharp/Lib/Protocols/DBus/Transport/AuthenticationMethod.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Transport/AuthenticationMethod.cs rename to csharp/Lib/Protocols/DBus/Transport/AuthenticationMethod.cs diff --git a/csharp/lib/Protocols/DBus/Transport/BufferedSocketReader.cs b/csharp/Lib/Protocols/DBus/Transport/BufferedSocketReader.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Transport/BufferedSocketReader.cs rename to csharp/Lib/Protocols/DBus/Transport/BufferedSocketReader.cs diff --git a/csharp/lib/Protocols/DBus/Transport/DBusBufferReader.cs b/csharp/Lib/Protocols/DBus/Transport/DBusBufferReader.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Transport/DBusBufferReader.cs rename to csharp/Lib/Protocols/DBus/Transport/DBusBufferReader.cs diff --git a/csharp/lib/Protocols/DBus/Transport/DBusBufferWriter.cs b/csharp/Lib/Protocols/DBus/Transport/DBusBufferWriter.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Transport/DBusBufferWriter.cs rename to csharp/Lib/Protocols/DBus/Transport/DBusBufferWriter.cs diff --git a/csharp/lib/Protocols/DBus/Transport/DBusSocket.cs b/csharp/Lib/Protocols/DBus/Transport/DBusSocket.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Transport/DBusSocket.cs rename to csharp/Lib/Protocols/DBus/Transport/DBusSocket.cs diff --git a/csharp/lib/Protocols/DBus/Utils/DisposableStack.cs b/csharp/Lib/Protocols/DBus/Utils/DisposableStack.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Utils/DisposableStack.cs rename to csharp/Lib/Protocols/DBus/Utils/DisposableStack.cs diff --git a/csharp/lib/Protocols/DBus/Utils/Extensions.cs b/csharp/Lib/Protocols/DBus/Utils/Extensions.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Utils/Extensions.cs rename to csharp/Lib/Protocols/DBus/Utils/Extensions.cs diff --git a/csharp/lib/Protocols/DBus/Utils/HexView.cs b/csharp/Lib/Protocols/DBus/Utils/HexView.cs similarity index 100% rename from csharp/lib/Protocols/DBus/Utils/HexView.cs rename to csharp/Lib/Protocols/DBus/Utils/HexView.cs diff --git a/csharp/lib/Protocols/DBus/WireFormat/DBusMeasureWriter.cs b/csharp/Lib/Protocols/DBus/WireFormat/DBusMeasureWriter.cs similarity index 100% rename from csharp/lib/Protocols/DBus/WireFormat/DBusMeasureWriter.cs rename to csharp/Lib/Protocols/DBus/WireFormat/DBusMeasureWriter.cs diff --git a/csharp/lib/Protocols/DBus/WireFormat/DBusReader.cs b/csharp/Lib/Protocols/DBus/WireFormat/DBusReader.cs similarity index 100% rename from csharp/lib/Protocols/DBus/WireFormat/DBusReader.cs rename to csharp/Lib/Protocols/DBus/WireFormat/DBusReader.cs diff --git a/csharp/lib/Protocols/DBus/WireFormat/DBusSocketReader.cs b/csharp/Lib/Protocols/DBus/WireFormat/DBusSocketReader.cs similarity index 100% rename from csharp/lib/Protocols/DBus/WireFormat/DBusSocketReader.cs rename to csharp/Lib/Protocols/DBus/WireFormat/DBusSocketReader.cs diff --git a/csharp/lib/Protocols/DBus/WireFormat/DBusWriter.cs b/csharp/Lib/Protocols/DBus/WireFormat/DBusWriter.cs similarity index 100% rename from csharp/lib/Protocols/DBus/WireFormat/DBusWriter.cs rename to csharp/Lib/Protocols/DBus/WireFormat/DBusWriter.cs diff --git a/csharp/lib/Protocols/Modbus/Clients/ModbusClient.cs b/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs similarity index 61% rename from csharp/lib/Protocols/Modbus/Clients/ModbusClient.cs rename to csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs index 66576d9d1..c992d0874 100644 --- a/csharp/lib/Protocols/Modbus/Clients/ModbusClient.cs +++ b/csharp/Lib/Protocols/Modbus/Clients/ModbusClient.cs @@ -1,6 +1,8 @@ -using System.Threading.Channels; using InnovEnergy.Lib.Protocols.Modbus.Connections; using InnovEnergy.Lib.Protocols.Modbus.Conversions; +using InnovEnergy.Lib.Protocols.Modbus.Protocol; +using static InnovEnergy.Lib.Protocols.Modbus.Protocol.MultiRegisterEndianness; +using static InnovEnergy.Lib.Protocols.Modbus.Protocol.RegisterIndexing; namespace InnovEnergy.Lib.Protocols.Modbus.Clients; @@ -11,10 +13,12 @@ using Coils = IReadOnlyList; public abstract class ModbusClient { - protected ModbusConnection Connection { get; } - protected Byte SlaveId { get; } - - + protected ModbusConnection Connection { get; } + protected Byte SlaveId { get; } + protected RegisterIndexing RegisterIndexing { get; } + protected MultiRegisterEndianness Endianness { get; } + + // TODO: add additional functions: coils... public abstract Coils ReadDiscreteInputs (UInt16 readAddress, UInt16 nValues); @@ -40,12 +44,20 @@ public abstract class ModbusClient return WriteRegisters(writeAddress, (IReadOnlyList)values); } - - protected ModbusClient(ModbusConnection connection, Byte slaveId) + protected ModbusClient(ModbusConnection connection, + Byte slaveId, + RegisterIndexing registerIndexing = OneBased, + MultiRegisterEndianness endianness = LittleEndian) { - Connection = connection; - SlaveId = slaveId; + Connection = connection; + SlaveId = slaveId; + RegisterIndexing = registerIndexing; + Endianness = endianness; } public void CloseConnection() => Connection.Close(); -} \ No newline at end of file + + protected UInt16 LogicToWire(UInt16 address) => RegisterIndexing.LogicToSerialized(address); + protected UInt16 SerializedToLogic(UInt16 address) => RegisterIndexing.SerializedToLogic(address); +} + diff --git a/csharp/lib/Protocols/Modbus/Clients/ModbusRtuClient.cs b/csharp/Lib/Protocols/Modbus/Clients/ModbusRtuClient.cs similarity index 86% rename from csharp/lib/Protocols/Modbus/Clients/ModbusRtuClient.cs rename to csharp/Lib/Protocols/Modbus/Clients/ModbusRtuClient.cs index b901c3f76..c49456ab0 100644 --- a/csharp/lib/Protocols/Modbus/Clients/ModbusRtuClient.cs +++ b/csharp/Lib/Protocols/Modbus/Clients/ModbusRtuClient.cs @@ -30,7 +30,9 @@ public class ModbusRtuClient : ModbusClient public override ModbusRegisters ReadInputRegisters(UInt16 readAddress, UInt16 nValues) { - var cmd = new ReadInputRegistersCommandFrame(SlaveId, readAddress, nValues); + var wireReadAddress = LogicToWire(readAddress); + + var cmd = new ReadInputRegistersCommandFrame(SlaveId, wireReadAddress, nValues); var crc = CalcCrc(cmd); // TX @@ -54,7 +56,9 @@ public class ModbusRtuClient : ModbusClient public override ModbusRegisters ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues) { - var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues); + var wireReadAddress = LogicToWire(readAddress); + + var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, wireReadAddress, nValues); var crc = CalcCrc(cmd.Data); // TX @@ -82,7 +86,9 @@ public class ModbusRtuClient : ModbusClient public override UInt16 WriteRegisters(UInt16 writeAddress, UInt16s values) { - var cmd = new WriteRegistersCommandFrame(SlaveId, writeAddress, values); + var wireWriteAddress = LogicToWire(writeAddress); + + var cmd = new WriteRegistersCommandFrame(SlaveId, wireWriteAddress, values); var crc = CalcCrc(cmd); var nToRead = cmd.ExpectedResponseSize + CrcSize; @@ -105,10 +111,16 @@ public class ModbusRtuClient : ModbusClient public override ModbusRegisters ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite) { - var cmd = new ReadWriteRegistersCommandFrame(SlaveId, readAddress, nbToRead, writeAddress, registersToWrite); + var wireReadAddress = LogicToWire(readAddress); + var wireWriteAddress = LogicToWire(writeAddress); + + var cmd = new ReadWriteRegistersCommandFrame(SlaveId, + wireReadAddress, + nbToRead, + wireWriteAddress, + registersToWrite); var crc = CalcCrc(cmd); - // TX cmd.Data.Concat(crc).Apply(Connection.Transmit); diff --git a/csharp/lib/Protocols/Modbus/Clients/ModbusTcpClient.cs b/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs similarity index 74% rename from csharp/lib/Protocols/Modbus/Clients/ModbusTcpClient.cs rename to csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs index 89824106c..2952d4243 100644 --- a/csharp/lib/Protocols/Modbus/Clients/ModbusTcpClient.cs +++ b/csharp/Lib/Protocols/Modbus/Clients/ModbusTcpClient.cs @@ -1,16 +1,17 @@ using System.Diagnostics; using InnovEnergy.Lib.Protocols.Modbus.Connections; using InnovEnergy.Lib.Protocols.Modbus.Conversions; +using InnovEnergy.Lib.Protocols.Modbus.Protocol; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Commands; using InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Replies; using InnovEnergy.Lib.Protocols.Modbus.Tcp; +using static InnovEnergy.Lib.Protocols.Modbus.Protocol.MultiRegisterEndianness; namespace InnovEnergy.Lib.Protocols.Modbus.Clients; using UInt16s = IReadOnlyList; using Coils = IReadOnlyList; - public class ModbusTcpClient : ModbusClient { public const UInt16 DefaultPort = 502; @@ -20,16 +21,21 @@ public class ModbusTcpClient : ModbusClient private UInt16 NextId() => unchecked(++_Id); - public ModbusTcpClient(ModbusConnection connection, Byte slaveId) : base(connection, slaveId) + public ModbusTcpClient(ModbusConnection connection, + Byte slaveId, + RegisterIndexing registerIndexing = RegisterIndexing.OneBased, + MultiRegisterEndianness endianness = LittleEndian) : base(connection, slaveId, registerIndexing, endianness) { } public override IReadOnlyList ReadDiscreteInputs(UInt16 readAddress, UInt16 nValues) { + var wireReadAddress = LogicToWire(readAddress); + var id = NextId(); // TODO: check response id - var cmd = new ReadDiscreteInputsCommandFrame(SlaveId, readAddress, nValues); + var cmd = new ReadDiscreteInputsCommandFrame(SlaveId, wireReadAddress, nValues); var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); @@ -46,9 +52,10 @@ public class ModbusTcpClient : ModbusClient public override ModbusRegisters ReadInputRegisters(UInt16 readAddress, UInt16 nValues) { + var wireReadAddress = LogicToWire(readAddress); var id = NextId(); // TODO: check response id - - var cmd = new ReadInputRegistersCommandFrame(SlaveId, readAddress, nValues); + + var cmd = new ReadInputRegistersCommandFrame(SlaveId, wireReadAddress, nValues); var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); @@ -65,12 +72,15 @@ public class ModbusTcpClient : ModbusClient return new ModbusRegisters(readAddress, verified.RegistersRead.ToArray()); } + + public override ModbusRegisters ReadHoldingRegisters(UInt16 readAddress, UInt16 nValues) { + var wireReadAddress = LogicToWire(readAddress); + var id = NextId(); // TODO: check response id - - var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, readAddress, nValues); + var cmd = new ReadHoldingRegistersCommandFrame(SlaveId, wireReadAddress, nValues); var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); @@ -84,14 +94,15 @@ public class ModbusTcpClient : ModbusClient var verified = cmd.VerifyResponse(rxFrm); - return new ModbusRegisters(readAddress, verified.RegistersRead.ToArray()); + return new ModbusRegisters(readAddress, verified.RegistersRead.ToArray()); // TODO } public override UInt16 WriteMultipleCoils(UInt16 writeAddress, Coils coils) { + var wireWriteAddress = LogicToWire(writeAddress); + var id = NextId(); // TODO: check response id - - var cmd = new WriteCoilsCommandFrame(SlaveId, writeAddress, coils); + var cmd = new WriteCoilsCommandFrame(SlaveId, wireWriteAddress, coils); var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); @@ -109,9 +120,10 @@ public class ModbusTcpClient : ModbusClient public override UInt16 WriteRegisters(UInt16 writeAddress, UInt16s values) { + var wireWriteAddress = LogicToWire(writeAddress); + var id = NextId(); // TODO: check response id - - var cmd = new WriteRegistersCommandFrame(SlaveId, writeAddress, values); + var cmd = new WriteRegistersCommandFrame(SlaveId, wireWriteAddress, values); var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); @@ -130,9 +142,17 @@ public class ModbusTcpClient : ModbusClient public override ModbusRegisters ReadWriteRegisters(UInt16 readAddress, UInt16 nbToRead, UInt16 writeAddress, UInt16s registersToWrite) { + var wireReadAddress = LogicToWire(readAddress); + var wireWriteAddress = LogicToWire(writeAddress); + var id = NextId(); // TODO: check response id - var cmd = new ReadWriteRegistersCommandFrame(SlaveId, readAddress, nbToRead, writeAddress, registersToWrite); + var cmd = new ReadWriteRegistersCommandFrame(SlaveId, + wireReadAddress, + nbToRead, + wireWriteAddress, + registersToWrite); + var hdr = new MbapHeader(id, cmd.Data.Count); var frm = new ModbusTcpFrame(hdr, cmd); @@ -146,7 +166,7 @@ public class ModbusTcpClient : ModbusClient var verified = cmd.VerifyResponse(rxFrm); - return new ModbusRegisters(readAddress, verified.RegistersRead.ToArray()); + return new ModbusRegisters(readAddress, verified.RegistersRead.ToArray()); // TODO } } \ No newline at end of file diff --git a/csharp/lib/Protocols/Modbus/Connections/ModbusConnection.cs b/csharp/Lib/Protocols/Modbus/Connections/ModbusConnection.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Connections/ModbusConnection.cs rename to csharp/Lib/Protocols/Modbus/Connections/ModbusConnection.cs diff --git a/csharp/lib/Protocols/Modbus/Connections/ModbusSerialConnection.cs b/csharp/Lib/Protocols/Modbus/Connections/ModbusSerialConnection.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Connections/ModbusSerialConnection.cs rename to csharp/Lib/Protocols/Modbus/Connections/ModbusSerialConnection.cs diff --git a/csharp/lib/Protocols/Modbus/Connections/ModbusTcpConnection.cs b/csharp/Lib/Protocols/Modbus/Connections/ModbusTcpConnection.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Connections/ModbusTcpConnection.cs rename to csharp/Lib/Protocols/Modbus/Connections/ModbusTcpConnection.cs diff --git a/csharp/lib/Protocols/Modbus/Conversions/Endianness.cs b/csharp/Lib/Protocols/Modbus/Conversions/Endianness.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Conversions/Endianness.cs rename to csharp/Lib/Protocols/Modbus/Conversions/Endianness.cs diff --git a/csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Bit.cs b/csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Bit.cs similarity index 75% rename from csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Bit.cs rename to csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Bit.cs index e2e02ed7e..2ab1061dc 100644 --- a/csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Bit.cs +++ b/csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Bit.cs @@ -1,4 +1,5 @@ using System.Collections; +using InnovEnergy.Lib.Utils; namespace InnovEnergy.Lib.Protocols.Modbus.Conversions; @@ -8,7 +9,7 @@ public partial class ModbusRegisters { var offset = index - StartRegister; - var byteArray = BitConverter.GetBytes(Registers[offset]).Reverse().ToArray(); + var byteArray = Registers[offset].Apply(BitConverter.GetBytes).Reverse().ToArray(); var bitArray = new BitArray(byteArray); return bitArray.Get(bitIndex); @@ -18,7 +19,7 @@ public partial class ModbusRegisters { var offset = index - StartRegister; - var byteArray = BitConverter.GetBytes(Registers[offset]).Reverse().ToArray(); + var byteArray = Registers[offset].Apply(BitConverter.GetBytes).Reverse().ToArray(); var bitArray = new BitArray(byteArray); bitArray.Set(bitIndex, value); diff --git a/csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Boolean.cs b/csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Boolean.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Boolean.cs rename to csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Boolean.cs diff --git a/csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Bytes.cs b/csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Bytes.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Bytes.cs rename to csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Bytes.cs diff --git a/csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Int16.cs b/csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Int16.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Int16.cs rename to csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Int16.cs diff --git a/csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Int32.cs b/csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Int32.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Int32.cs rename to csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Int32.cs diff --git a/csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Single.cs b/csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Single.cs similarity index 89% rename from csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Single.cs rename to csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Single.cs index a6408185e..26f5776e6 100644 --- a/csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.Single.cs +++ b/csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.Single.cs @@ -9,7 +9,7 @@ public partial class ModbusRegisters var bytearray = BitConverter.GetBytes(value).Reverse().ToArray(); var value32 = BitConverter.ToUInt32(bytearray); - Registers[index - StartRegister] = (UInt16)(value32 >> 16); + Registers[index - StartRegister ] = (UInt16)(value32 >> 16); Registers[index - StartRegister + 1] = (UInt16)(value32 & 0xFFFF); } diff --git a/csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.String.cs b/csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.String.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.String.cs rename to csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.String.cs diff --git a/csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.UInt16.cs b/csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.UInt16.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.UInt16.cs rename to csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.UInt16.cs diff --git a/csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.UInt32.cs b/csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.UInt32.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.UInt32.cs rename to csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.UInt32.cs diff --git a/csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.cs b/csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Conversions/ModbusRegisters.cs rename to csharp/Lib/Protocols/Modbus/Conversions/ModbusRegisters.cs diff --git a/csharp/lib/Protocols/Modbus/Modbus.csproj b/csharp/Lib/Protocols/Modbus/Modbus.csproj similarity index 51% rename from csharp/lib/Protocols/Modbus/Modbus.csproj rename to csharp/Lib/Protocols/Modbus/Modbus.csproj index ec6eb7bf8..460195f09 100644 --- a/csharp/lib/Protocols/Modbus/Modbus.csproj +++ b/csharp/Lib/Protocols/Modbus/Modbus.csproj @@ -1,10 +1,5 @@ - - - - InnovEnergy.Lib.Protocols.Modbus - InnovEnergy.Lib.Protocols.Modbus - + diff --git a/csharp/lib/Protocols/Modbus/Protocol/ExceptionCode.cs b/csharp/Lib/Protocols/Modbus/Protocol/ExceptionCode.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/ExceptionCode.cs rename to csharp/Lib/Protocols/Modbus/Protocol/ExceptionCode.cs diff --git a/csharp/lib/Protocols/Modbus/Protocol/Exceptions.cs b/csharp/Lib/Protocols/Modbus/Protocol/Exceptions.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/Exceptions.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Exceptions.cs diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Accessors.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Accessors.cs new file mode 100644 index 000000000..2a2e9e2c5 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/Accessors.cs @@ -0,0 +1,10 @@ +namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; + +public static class Accessors +{ + public static MbByte ByteAt (this ArraySegment data, Byte i) => new MbByte(data, i); + public static MbWord WordAt (this ArraySegment data, Byte i) => new MbWord(data, i); + public static MbWords WordsAt (this ArraySegment data, Byte i) => new MbWords(data, i); + public static MbBits BitsAt (this ArraySegment data, Byte i) => new MbBits(data, i); + public static MbByte ByteAt(this ArraySegment data,Byte i) where T : struct, IConvertible => new MbByte(data, i); +} \ No newline at end of file diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Accessors/MbBits.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbBits.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Accessors/MbBits.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbBits.cs diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Accessors/MbByte.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbByte.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Accessors/MbByte.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbByte.cs diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Accessors/MbByte{T}.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbByte{T}.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Accessors/MbByte{T}.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbByte{T}.cs diff --git a/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbRegisters.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbRegisters.cs new file mode 100644 index 000000000..6db8492b9 --- /dev/null +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbRegisters.cs @@ -0,0 +1,105 @@ +using System.Collections; +using InnovEnergy.Lib.Utils; +using static InnovEnergy.Lib.Protocols.Modbus.Protocol.MultiRegisterEndianness; + +namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; + +using Float32 = Single; + +public struct MbRegisters : IReadOnlyList +{ + private MbWords Words { get; } + private UInt16 StartRegister { get; } + private MultiRegisterEndianness Endianness { get; } + + public MbRegisters(MbWords words, + UInt16 startRegister, + MultiRegisterEndianness endianness, + RegisterIndexing registerIndexing) + { + Words = words; + Endianness = endianness; + + var start = startRegister - (Int16) registerIndexing; // TODO: check + + if (start < 0) + throw new ArgumentOutOfRangeException(nameof(startRegister)); + + StartRegister = (UInt16)start; + } + + public IEnumerator GetEnumerator() => Words.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public Int32 Count => Words.Count; + + public UInt16 this[Int32 index] => Words[index]; + + private Byte MapIndex(UInt16 index) + { + var i = index - StartRegister; + if (i is < Byte.MinValue or > Byte.MaxValue) + throw new IndexOutOfRangeException(); + + return (Byte)i; + } + + public UInt16 GetUInt16(UInt16 index) + { + return index + .Apply(MapIndex) + .Apply(Words.GetUInt16); + } + + public void SetUInt16(UInt16 index, UInt16 value) + { + Words.SetUInt16(MapIndex(index), value); + } + + public Int16 GetInt16(UInt16 index) + { + return (Int16) GetUInt16(index); + } + + public void SetUInt16(UInt16 index, Int16 value) + { + SetUInt16(index, (UInt16)value); + } + + public UInt32 GetUInt32(UInt16 index) + { + var i = MapIndex(index); + + var hi = (UInt32) GetUInt16(i); + var lo = (UInt32) GetUInt16(++i); + + if (Endianness == LittleEndian) + (lo, hi) = (hi, lo); + + return hi << 16 | lo; + } + + // TODO + // public void SetUInt32(UInt32 index, UInt32 value) + + public Int32 GetInt32(UInt16 index) => (Int32)GetUInt32(index); + + // TODO + // public void SetInt32(Int32 index, Int32 value) + + + public Float32 GetFloat32(UInt16 index) + { + return index + .Apply(GetInt32) + .Apply(BitConverter.Int32BitsToSingle); + } + + // TODO + // public void SetFloat32(Float32 index, Float32 value) + + + + +} \ No newline at end of file diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWord.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWord.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWord.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWord.cs diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWords.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWords.cs similarity index 55% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWords.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWords.cs index 30876dceb..5511c5c63 100644 --- a/csharp/lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWords.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Accessors/MbWords.cs @@ -5,38 +5,33 @@ namespace InnovEnergy.Lib.Protocols.Modbus.Protocol.Frames.Accessors; public readonly struct MbWords : IReadOnlyList { - private readonly ArraySegment _Data; + internal readonly ArraySegment Data; - internal MbWords(ArraySegment data, Byte startIndex) : this(data, startIndex, CountWords(data, startIndex)) + internal MbWords(ArraySegment data, Byte startIndex = 0) : + this(data, startIndex, CountWords(data, startIndex)) { } + internal MbWords(ArraySegment data, Byte startIndex, UInt16 wordCount) + { + Data = new ArraySegment(data.Array!, startIndex + data.Offset, wordCount * 2); + } + private static UInt16 CountWords(ArraySegment data, Byte startIndex) { var wordCount = (data.Count - startIndex) / 2; return wordCount.ConvertTo(); } - internal MbWords(ArraySegment data, Byte startIndex, UInt16 wordCount) : this(data.Array!, startIndex, wordCount) - { - - } - - - internal MbWords(Byte[] data, Byte startIndex, UInt16 wordCount) - { - _Data = new ArraySegment(data, startIndex, wordCount * 2); - } - internal IReadOnlyCollection Set(IReadOnlyCollection values) { - if (values.Count != _Data.Count / 2) + if (values.Count != Data.Count / 2) throw new ArgumentException($"Expecting an list of size {values.Count}!", nameof(values)); var i = 0; foreach (var value in values) { - _Data[i++] = (Byte) (value >> 8); - _Data[i++] = (Byte) (value & 0xFF); + Data[i++] = (Byte) (value >> 8); + Data[i++] = (Byte) (value & 0xFF); } return values; @@ -45,12 +40,12 @@ public readonly struct MbWords : IReadOnlyList public IEnumerator GetEnumerator() { - var end = _Data.Count; + var end = Data.Count; for (var i = 0; i < end; ) { - var hi = _Data[i++] << 8; - var lo = _Data[i++]; + var hi = Data[i++] << 8; + var lo = Data[i++]; yield return (UInt16) (hi | lo); } @@ -58,18 +53,30 @@ public readonly struct MbWords : IReadOnlyList IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - public Int32 Count => _Data.Count / 2; + public Int32 Count => Data.Count / 2; public UInt16 this[Int32 index] { + // a single register is always big endian (according to standard) + get { var i = index * 2; - var hi = _Data[i] << 8; - var lo = _Data[i+1]; + var hi = Data[i] << 8; + var lo = Data[i+1]; return (UInt16) (hi | lo); } + + } + + public UInt16 GetUInt16(Byte index) => this[index]; + + public void SetUInt16(Byte index, UInt16 value) + { + var i = index * 2; + Data[i + 0] = (Byte)(value >> 8); + Data[i + 1] = (Byte)(value & 0xFF); } } \ No newline at end of file diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs similarity index 94% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs index 88461e76e..325d88e5c 100644 --- a/csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadDiscreteInputsCommandFrame.cs @@ -13,8 +13,8 @@ internal class ReadDiscreteInputsCommandFrame : ModbusFrame { private const Int32 Size = 6; - private MbAddress ReadAddress => Data.AddressAt(2); - private MbWord QuantityOfInputs => Data.WordAt(4); + private MbWord ReadAddress => Data.WordAt(2); + private MbWord QuantityOfInputs => Data.WordAt(4); public ReadDiscreteInputsCommandFrame(Byte slave, UInt16 readAddress, UInt16 nBits) : base(Size) diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/ReadHoldingRegistersCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadHoldingRegistersCommandFrame.cs similarity index 94% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/ReadHoldingRegistersCommandFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadHoldingRegistersCommandFrame.cs index b03914c0a..9134b0be9 100644 --- a/csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/ReadHoldingRegistersCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadHoldingRegistersCommandFrame.cs @@ -14,8 +14,8 @@ internal class ReadHoldingRegistersCommandFrame : ModbusFrame internal const Int32 Size = 6; - public MbAddress ReadAddress => Data.AddressAt(2); - public MbWord NbToRead => Data.WordAt(4); + public MbWord ReadAddress => Data.WordAt(2); + public MbWord NbToRead => Data.WordAt(4); public Int32 ExpectedResponseSize => ReadHoldingRegistersResponseFrame.ExpectedSize(NbToRead); diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/ReadInputRegistersCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadInputRegistersCommandFrame.cs similarity index 94% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/ReadInputRegistersCommandFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadInputRegistersCommandFrame.cs index f06845565..a17fe34f8 100644 --- a/csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/ReadInputRegistersCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadInputRegistersCommandFrame.cs @@ -11,8 +11,8 @@ internal class ReadInputRegistersCommandFrame : ModbusFrame { internal const Int32 Size = 6; - public MbAddress ReadAddress => Data.AddressAt(2); - public MbWord NbToRead => Data.WordAt(4); + public MbWord ReadAddress => Data.WordAt(2); + public MbWord NbToRead => Data.WordAt(4); public Int32 ExpectedResponseSize => ReadInputRegistersResponseFrame.ExpectedSize(NbToRead); diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/ReadWriteRegistersCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadWriteRegistersCommandFrame.cs similarity index 96% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/ReadWriteRegistersCommandFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadWriteRegistersCommandFrame.cs index 1e4c8366f..e280c071d 100644 --- a/csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/ReadWriteRegistersCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/ReadWriteRegistersCommandFrame.cs @@ -11,9 +11,9 @@ internal class ReadWriteRegistersCommandFrame : ModbusFrame { internal new const Int32 MinSize = 11; - public MbAddress ReadAddress => Data.AddressAt(2); + public MbWord ReadAddress => Data.WordAt(2); public MbWord NbToRead => Data.WordAt(4); - public MbAddress WriteAddress => Data.AddressAt(6); + public MbWord WriteAddress => Data.WordAt(6); public MbWord NbToWrite => Data.WordAt(8); public MbByte ByteCount => Data.ByteAt(10); public MbWords RegistersToWrite => Data.WordsAt(11); diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/WriteCoilsCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteCoilsCommandFrame.cs similarity index 91% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/WriteCoilsCommandFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteCoilsCommandFrame.cs index 5865ddd21..e366616cb 100644 --- a/csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/WriteCoilsCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteCoilsCommandFrame.cs @@ -14,10 +14,10 @@ public class WriteCoilsCommandFrame : ModbusFrame { private new const Int32 MinSize = 7; - private MbAddress WriteAddress => Data.AddressAt(2); - private MbWord NbOfCoils => Data.WordAt(4); - private MbByte ByteCount => Data.ByteAt(6); - public MbBits CoilsToWrite => Data.BitsAt(7); + private MbWord WriteAddress => Data.WordAt(2); + private MbWord NbOfCoils => Data.WordAt(4); + private MbByte ByteCount => Data.ByteAt(6); + public MbBits CoilsToWrite => Data.BitsAt(7); public WriteCoilsCommandFrame(Byte slave, UInt16 writeAddress, Booleans coils) : base(MinSize + NbBytes(coils)) { diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/WriteRegistersCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteRegistersCommandFrame.cs similarity index 91% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/WriteRegistersCommandFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteRegistersCommandFrame.cs index af925ff9e..b317767c1 100644 --- a/csharp/lib/Protocols/Modbus/Protocol/Frames/Commands/WriteRegistersCommandFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Commands/WriteRegistersCommandFrame.cs @@ -11,10 +11,10 @@ internal class WriteRegistersCommandFrame : ModbusFrame { internal new const Int32 MinSize = 7; - public MbAddress WriteAddress => Data.AddressAt(2); - public MbWord NbOfRegisters => Data.WordAt(4); - public MbByte ByteCount => Data.ByteAt(6); - public MbWords RegistersToWrite => Data.WordsAt(7); + public MbWord WriteAddress => Data.WordAt(2); + public MbWord NbOfRegisters => Data.WordAt(4); + public MbByte ByteCount => Data.ByteAt(6); + public MbWords RegistersToWrite => Data.WordsAt(7); public Int32 ExpectedResponseSize => WriteRegistersResponseFrame.ExpectedSize(); diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Constants.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Constants.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Constants.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Constants.cs diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/ModbusFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/ModbusFrame.cs similarity index 96% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/ModbusFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/ModbusFrame.cs index 539551b92..fa1c3b008 100644 --- a/csharp/lib/Protocols/Modbus/Protocol/Frames/ModbusFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/ModbusFrame.cs @@ -17,7 +17,7 @@ public class ModbusFrame internal ModbusFrame(Int32 size) { - Data = new ArraySegment(new Byte[size]); + Data = new Byte[size]; } diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/ErrorResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ErrorResponseFrame.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/ErrorResponseFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ErrorResponseFrame.cs diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputResponseFrame.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputResponseFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadDiscreteInputResponseFrame.cs diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/ReadHoldingRegistersResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadHoldingRegistersResponseFrame.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/ReadHoldingRegistersResponseFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadHoldingRegistersResponseFrame.cs diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/ReadInputRegistersResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadInputRegistersResponseFrame.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/ReadInputRegistersResponseFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadInputRegistersResponseFrame.cs diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/ReadWriteRegistersCommandFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadWriteRegistersCommandFrame.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/ReadWriteRegistersCommandFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/ReadWriteRegistersCommandFrame.cs diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs similarity index 92% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs index 6ec170980..76518a0e5 100644 --- a/csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteCoilsResponseFrame.cs @@ -7,8 +7,8 @@ public class WriteCoilsResponseFrame : ModbusFrame { private const Int32 Size = 6; - public MbAddress WriteAddress => Data.AddressAt(2); - public MbWord NbWritten => Data.WordAt(4); + public MbWord WriteAddress => Data.WordAt(2); + public MbWord NbWritten => Data.WordAt(4); internal static Int32 ExpectedSize() => Size; diff --git a/csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs similarity index 92% rename from csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs rename to csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs index 2817c3460..76a0a6b7d 100644 --- a/csharp/lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs +++ b/csharp/Lib/Protocols/Modbus/Protocol/Frames/Replies/WriteRegistersResponseFrame.cs @@ -7,8 +7,8 @@ internal class WriteRegistersResponseFrame : ModbusFrame { private const Int32 Size = 6; - public MbAddress WriteAddress => Data.AddressAt(2); - public MbWord NbWritten => Data.WordAt(4); + public MbWord WriteAddress => Data.WordAt(2); + public MbWord NbWritten => Data.WordAt(4); internal static Int32 ExpectedSize() => Size; diff --git a/csharp/lib/Protocols/Modbus/Protocol/FunctionCode.cs b/csharp/Lib/Protocols/Modbus/Protocol/FunctionCode.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Protocol/FunctionCode.cs rename to csharp/Lib/Protocols/Modbus/Protocol/FunctionCode.cs diff --git a/csharp/lib/Protocols/Modbus/Tcp/MbapHeader.cs b/csharp/Lib/Protocols/Modbus/Tcp/MbapHeader.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Tcp/MbapHeader.cs rename to csharp/Lib/Protocols/Modbus/Tcp/MbapHeader.cs diff --git a/csharp/lib/Protocols/Modbus/Tcp/ModbusTcpFrame.cs b/csharp/Lib/Protocols/Modbus/Tcp/ModbusTcpFrame.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Tcp/ModbusTcpFrame.cs rename to csharp/Lib/Protocols/Modbus/Tcp/ModbusTcpFrame.cs diff --git a/csharp/lib/Protocols/Modbus/Util/ArraySegmentExtensions.cs b/csharp/Lib/Protocols/Modbus/Util/ArraySegmentExtensions.cs similarity index 100% rename from csharp/lib/Protocols/Modbus/Util/ArraySegmentExtensions.cs rename to csharp/Lib/Protocols/Modbus/Util/ArraySegmentExtensions.cs diff --git a/csharp/Lib/StatusApi/BatteryStatus.cs b/csharp/Lib/StatusApi/BatteryStatus.cs new file mode 100644 index 000000000..f8a0124d5 --- /dev/null +++ b/csharp/Lib/StatusApi/BatteryStatus.cs @@ -0,0 +1,17 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.StatusApi.Generator; +using InnovEnergy.Lib.Units; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi; + +using T = BatteryStatus; + +[OpParallel] +public partial record BatteryStatus : DeviceStatus, IDcConnection +{ + public DcBus Dc { get; init; } + public Percent Soc { get; init; } + public Temperature Temperature { get; init; } +} + \ No newline at end of file diff --git a/csharp/Lib/StatusApi/BatteryStatus.generated.cs b/csharp/Lib/StatusApi/BatteryStatus.generated.cs new file mode 100644 index 000000000..d5f549056 --- /dev/null +++ b/csharp/Lib/StatusApi/BatteryStatus.generated.cs @@ -0,0 +1,16 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +using System.CodeDom.Compiler; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.StatusApi; + +using T = BatteryStatus; + +[GeneratedCode("generate.sh", "1")] +public partial record BatteryStatus +{ + private static readonly Func OpParallel = "|".CreateBinaryOpForProps(); + public static T operator |(T left, T right) => OpParallel(left, right); +} + \ No newline at end of file diff --git a/csharp/Lib/StatusApi/Connections/IAc1Connection.cs b/csharp/Lib/StatusApi/Connections/IAc1Connection.cs new file mode 100644 index 000000000..54060cb20 --- /dev/null +++ b/csharp/Lib/StatusApi/Connections/IAc1Connection.cs @@ -0,0 +1,8 @@ +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi.Connections; + +public interface IAc1Connection +{ + Ac1Bus Ac { get; } +} \ No newline at end of file diff --git a/csharp/Lib/StatusApi/Connections/IAc3Connection.cs b/csharp/Lib/StatusApi/Connections/IAc3Connection.cs new file mode 100644 index 000000000..a1c0ca1b5 --- /dev/null +++ b/csharp/Lib/StatusApi/Connections/IAc3Connection.cs @@ -0,0 +1,8 @@ +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi.Connections; + +public interface IAc3Connection +{ + Ac3Bus Ac { get; } +} \ No newline at end of file diff --git a/csharp/Lib/StatusApi/Connections/IDcConnection.cs b/csharp/Lib/StatusApi/Connections/IDcConnection.cs new file mode 100644 index 000000000..04d83289f --- /dev/null +++ b/csharp/Lib/StatusApi/Connections/IDcConnection.cs @@ -0,0 +1,9 @@ +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi.Connections; + + +public interface IDcConnection +{ + DcBus Dc { get; } +} \ No newline at end of file diff --git a/csharp/Lib/StatusApi/Connections/IPvConnection.cs b/csharp/Lib/StatusApi/Connections/IPvConnection.cs new file mode 100644 index 000000000..6dfddccea --- /dev/null +++ b/csharp/Lib/StatusApi/Connections/IPvConnection.cs @@ -0,0 +1,8 @@ +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi.Connections; + +public interface IPvConnection +{ + IReadOnlyList Strings { get; } +} \ No newline at end of file diff --git a/csharp/Lib/StatusApi/DcDcConverterStatus.cs b/csharp/Lib/StatusApi/DcDcConverterStatus.cs new file mode 100644 index 000000000..21941d5e6 --- /dev/null +++ b/csharp/Lib/StatusApi/DcDcConverterStatus.cs @@ -0,0 +1,14 @@ +using InnovEnergy.Lib.StatusApi.Generator; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi; + +[OpParallel] +public partial record DcDcConverterStatus : DeviceStatus +{ + public DcBus Left { get; init; } + public DcBus Right { get; init; } +} + + + \ No newline at end of file diff --git a/csharp/Lib/StatusApi/DcDcConverterStatus.generated.cs b/csharp/Lib/StatusApi/DcDcConverterStatus.generated.cs new file mode 100644 index 000000000..32a133a8a --- /dev/null +++ b/csharp/Lib/StatusApi/DcDcConverterStatus.generated.cs @@ -0,0 +1,16 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +using System.CodeDom.Compiler; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.StatusApi; + +using T = DcDcConverterStatus; + +[GeneratedCode("generate.sh", "1")] +public partial record DcDcConverterStatus +{ + private static readonly Func OpParallel = "|".CreateBinaryOpForProps(); + public static T operator |(T left, T right) => OpParallel(left, right); +} + \ No newline at end of file diff --git a/csharp/Lib/StatusApi/DeviceStatus.cs b/csharp/Lib/StatusApi/DeviceStatus.cs new file mode 100644 index 000000000..380007d85 --- /dev/null +++ b/csharp/Lib/StatusApi/DeviceStatus.cs @@ -0,0 +1,52 @@ +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.StatusApi; + +public abstract record DeviceStatus +{ + public String DeviceType => GetType() + .Unfold(t => t.BaseType) + .First(t => t.IsAbstract) + .Name + .Replace("Status", ""); +} + + +// public static class Program +// { +// public static void Main(string[] args) +// { +// var x = new ThreePhasePvInverterStatus +// { +// Ac = new() +// { +// Frequency = 50, +// L1 = new() +// { +// Current = 10, +// Voltage = 10, +// Phi = 0, +// }, +// L2 = new() +// { +// Current = 52, +// Voltage = 220, +// Phi = Angle.Pi / 2, +// }, +// L3 = new() +// { +// Current = 158, +// Voltage = 454, +// Phi = Angle.Pi / 3, +// }, +// }, +// Strings = new DcBus[] +// { +// new() { Current = 10, Voltage = 22 }, +// new() { Current = 12, Voltage = 33 }, +// } +// }; +// +// var s = x.ToJson(); +// } +// } \ No newline at end of file diff --git a/csharp/Lib/StatusApi/Generator/OpParallelAttribute.cs b/csharp/Lib/StatusApi/Generator/OpParallelAttribute.cs new file mode 100644 index 000000000..ec94f9d34 --- /dev/null +++ b/csharp/Lib/StatusApi/Generator/OpParallelAttribute.cs @@ -0,0 +1,5 @@ +namespace InnovEnergy.Lib.StatusApi.Generator; + +[AttributeUsage(AttributeTargets.Class)] +internal class OpParallelAttribute : Attribute +{} \ No newline at end of file diff --git a/csharp/Lib/StatusApi/Generator/Template.txt b/csharp/Lib/StatusApi/Generator/Template.txt new file mode 100644 index 000000000..43f22557f --- /dev/null +++ b/csharp/Lib/StatusApi/Generator/Template.txt @@ -0,0 +1,16 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +using System.CodeDom.Compiler; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.StatusApi; + +using T = Template; + +[GeneratedCode("generate.sh", "1")] +public partial record Template +{ + private static readonly Func OpParallel = "|".CreateBinaryOpForProps(); + public static T operator |(T left, T right) => OpParallel(left, right); +} + \ No newline at end of file diff --git a/csharp/Lib/StatusApi/Generator/generate.sh b/csharp/Lib/StatusApi/Generator/generate.sh new file mode 100755 index 000000000..1233bd1b7 --- /dev/null +++ b/csharp/Lib/StatusApi/Generator/generate.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + + +scriptDir=$( dirname -- "$0"; ) +cd "$scriptDir/.." || exit + +for path in $(grep -e '\[OpParallel\]' -l *.cs) +do + file=$(basename -- "$path") + class="${file%.*}" + echo "generating $file" + sed "s/Template/$class/g" "./Generator/Template.txt" > "./$class.generated.cs" +done \ No newline at end of file diff --git a/csharp/Lib/StatusApi/MpptStatus.cs b/csharp/Lib/StatusApi/MpptStatus.cs new file mode 100644 index 000000000..53acdf439 --- /dev/null +++ b/csharp/Lib/StatusApi/MpptStatus.cs @@ -0,0 +1,14 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.StatusApi.Generator; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi; + +[OpParallel] +public partial record MpptStatus : IDcConnection, IPvConnection +{ + public DcBus Dc { get; init; } + public IReadOnlyList Strings { get; init; } +} + + \ No newline at end of file diff --git a/csharp/Lib/StatusApi/MpptStatus.generated.cs b/csharp/Lib/StatusApi/MpptStatus.generated.cs new file mode 100644 index 000000000..5e2afe796 --- /dev/null +++ b/csharp/Lib/StatusApi/MpptStatus.generated.cs @@ -0,0 +1,16 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +using System.CodeDom.Compiler; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.StatusApi; + +using T = MpptStatus; + +[GeneratedCode("generate.sh", "1")] +public partial record MpptStatus +{ + private static readonly Func OpParallel = "|".CreateBinaryOpForProps(); + public static T operator |(T left, T right) => OpParallel(left, right); +} + \ No newline at end of file diff --git a/csharp/Lib/StatusApi/PowerMeterStatus.cs b/csharp/Lib/StatusApi/PowerMeterStatus.cs new file mode 100644 index 000000000..8140afef3 --- /dev/null +++ b/csharp/Lib/StatusApi/PowerMeterStatus.cs @@ -0,0 +1,11 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.StatusApi.Generator; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi; + +[OpParallel] +public partial record PowerMeterStatus : DeviceStatus, IAc3Connection +{ + public Ac3Bus Ac { get; init; } +} \ No newline at end of file diff --git a/csharp/Lib/StatusApi/PowerMeterStatus.generated.cs b/csharp/Lib/StatusApi/PowerMeterStatus.generated.cs new file mode 100644 index 000000000..9e6dcaf9c --- /dev/null +++ b/csharp/Lib/StatusApi/PowerMeterStatus.generated.cs @@ -0,0 +1,16 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +using System.CodeDom.Compiler; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.StatusApi; + +using T = PowerMeterStatus; + +[GeneratedCode("generate.sh", "1")] +public partial record PowerMeterStatus +{ + private static readonly Func OpParallel = "|".CreateBinaryOpForProps(); + public static T operator |(T left, T right) => OpParallel(left, right); +} + \ No newline at end of file diff --git a/csharp/Lib/StatusApi/SinglePhaseInverterStatus.cs b/csharp/Lib/StatusApi/SinglePhaseInverterStatus.cs new file mode 100644 index 000000000..6688d8886 --- /dev/null +++ b/csharp/Lib/StatusApi/SinglePhaseInverterStatus.cs @@ -0,0 +1,15 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.StatusApi.Generator; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi; + +[OpParallel] +public partial record SinglePhaseInverterStatus : + DeviceStatus, + IAc1Connection, + IDcConnection +{ + public Ac1Bus Ac { get; init; } + public DcBus Dc { get; init; } +} diff --git a/csharp/Lib/StatusApi/SinglePhaseInverterStatus.generated.cs b/csharp/Lib/StatusApi/SinglePhaseInverterStatus.generated.cs new file mode 100644 index 000000000..047f609fc --- /dev/null +++ b/csharp/Lib/StatusApi/SinglePhaseInverterStatus.generated.cs @@ -0,0 +1,16 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +using System.CodeDom.Compiler; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.StatusApi; + +using T = SinglePhaseInverterStatus; + +[GeneratedCode("generate.sh", "1")] +public partial record SinglePhaseInverterStatus +{ + private static readonly Func OpParallel = "|".CreateBinaryOpForProps(); + public static T operator |(T left, T right) => OpParallel(left, right); +} + \ No newline at end of file diff --git a/csharp/Lib/StatusApi/SinglePhasePvInverterStatus.cs b/csharp/Lib/StatusApi/SinglePhasePvInverterStatus.cs new file mode 100644 index 000000000..269254b6e --- /dev/null +++ b/csharp/Lib/StatusApi/SinglePhasePvInverterStatus.cs @@ -0,0 +1,15 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.StatusApi.Generator; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi; + +[OpParallel] +public partial record SinglePhasePvInverterStatus : + DeviceStatus, + IAc1Connection, + IPvConnection +{ + public Ac1Bus Ac { get; init; } + public IReadOnlyList Strings { get; init; } +} diff --git a/csharp/Lib/StatusApi/SinglePhasePvInverterStatus.generated.cs b/csharp/Lib/StatusApi/SinglePhasePvInverterStatus.generated.cs new file mode 100644 index 000000000..37340f111 --- /dev/null +++ b/csharp/Lib/StatusApi/SinglePhasePvInverterStatus.generated.cs @@ -0,0 +1,16 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +using System.CodeDom.Compiler; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.StatusApi; + +using T = SinglePhasePvInverterStatus; + +[GeneratedCode("generate.sh", "1")] +public partial record SinglePhasePvInverterStatus +{ + private static readonly Func OpParallel = "|".CreateBinaryOpForProps(); + public static T operator |(T left, T right) => OpParallel(left, right); +} + \ No newline at end of file diff --git a/csharp/Lib/StatusApi/StatusApi.csproj b/csharp/Lib/StatusApi/StatusApi.csproj new file mode 100644 index 000000000..b68a8b119 --- /dev/null +++ b/csharp/Lib/StatusApi/StatusApi.csproj @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/csharp/Lib/StatusApi/ThreePhaseInverterStatus.cs b/csharp/Lib/StatusApi/ThreePhaseInverterStatus.cs new file mode 100644 index 000000000..c525a6a67 --- /dev/null +++ b/csharp/Lib/StatusApi/ThreePhaseInverterStatus.cs @@ -0,0 +1,16 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.StatusApi.Generator; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi; + +[OpParallel] +public partial record ThreePhaseInverterStatus : + DeviceStatus, + IAc3Connection, + IDcConnection +{ + public Ac3Bus Ac { get; init; } + public DcBus Dc { get; init; } +} + \ No newline at end of file diff --git a/csharp/Lib/StatusApi/ThreePhaseInverterStatus.generated.cs b/csharp/Lib/StatusApi/ThreePhaseInverterStatus.generated.cs new file mode 100644 index 000000000..989941505 --- /dev/null +++ b/csharp/Lib/StatusApi/ThreePhaseInverterStatus.generated.cs @@ -0,0 +1,16 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +using System.CodeDom.Compiler; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.StatusApi; + +using T = ThreePhaseInverterStatus; + +[GeneratedCode("generate.sh", "1")] +public partial record ThreePhaseInverterStatus +{ + private static readonly Func OpParallel = "|".CreateBinaryOpForProps(); + public static T operator |(T left, T right) => OpParallel(left, right); +} + \ No newline at end of file diff --git a/csharp/Lib/StatusApi/ThreePhasePvInverterStatus.cs b/csharp/Lib/StatusApi/ThreePhasePvInverterStatus.cs new file mode 100644 index 000000000..8c122ffb3 --- /dev/null +++ b/csharp/Lib/StatusApi/ThreePhasePvInverterStatus.cs @@ -0,0 +1,15 @@ +using InnovEnergy.Lib.StatusApi.Connections; +using InnovEnergy.Lib.StatusApi.Generator; +using InnovEnergy.Lib.Units.Composite; + +namespace InnovEnergy.Lib.StatusApi; + +[OpParallel] +public partial record ThreePhasePvInverterStatus : + DeviceStatus, + IAc3Connection, + IPvConnection +{ + public Ac3Bus Ac { get; init; } + public IReadOnlyList Strings { get; init; } +} diff --git a/csharp/Lib/StatusApi/ThreePhasePvInverterStatus.generated.cs b/csharp/Lib/StatusApi/ThreePhasePvInverterStatus.generated.cs new file mode 100644 index 000000000..4e7170a21 --- /dev/null +++ b/csharp/Lib/StatusApi/ThreePhasePvInverterStatus.generated.cs @@ -0,0 +1,16 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +using System.CodeDom.Compiler; +using InnovEnergy.Lib.Units.Composite; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.StatusApi; + +using T = ThreePhasePvInverterStatus; + +[GeneratedCode("generate.sh", "1")] +public partial record ThreePhasePvInverterStatus +{ + private static readonly Func OpParallel = "|".CreateBinaryOpForProps(); + public static T operator |(T left, T right) => OpParallel(left, right); +} + \ No newline at end of file diff --git a/csharp/lib/SysTools/Edges/RemoteCommandToProcess.cs b/csharp/Lib/SysTools/Edges/RemoteCommandToProcess.cs similarity index 92% rename from csharp/lib/SysTools/Edges/RemoteCommandToProcess.cs rename to csharp/Lib/SysTools/Edges/RemoteCommandToProcess.cs index 71453ab3d..984594e11 100644 --- a/csharp/lib/SysTools/Edges/RemoteCommandToProcess.cs +++ b/csharp/Lib/SysTools/Edges/RemoteCommandToProcess.cs @@ -1,7 +1,7 @@ -using InnovEnergy.SysTools.Process; -using InnovEnergy.SysTools.Remote; +using InnovEnergy.Lib.SysTools.Process; +using InnovEnergy.Lib.SysTools.Remote; -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; public static class RemoteCommandToProcess { diff --git a/csharp/lib/SysTools/Edges/RemotePathToRemoteCommand.cs b/csharp/Lib/SysTools/Edges/RemotePathToRemoteCommand.cs similarity index 94% rename from csharp/lib/SysTools/Edges/RemotePathToRemoteCommand.cs rename to csharp/Lib/SysTools/Edges/RemotePathToRemoteCommand.cs index 112623032..5e882d473 100644 --- a/csharp/lib/SysTools/Edges/RemotePathToRemoteCommand.cs +++ b/csharp/Lib/SysTools/Edges/RemotePathToRemoteCommand.cs @@ -1,6 +1,6 @@ -using InnovEnergy.SysTools.Remote; +using InnovEnergy.Lib.SysTools.Remote; -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; public static class RemotePathToRemoteCommand { diff --git a/csharp/lib/SysTools/Edges/SshHostToRemoteCommand.cs b/csharp/Lib/SysTools/Edges/SshHostToRemoteCommand.cs similarity index 68% rename from csharp/lib/SysTools/Edges/SshHostToRemoteCommand.cs rename to csharp/Lib/SysTools/Edges/SshHostToRemoteCommand.cs index 9dd56a042..c9cfba220 100644 --- a/csharp/lib/SysTools/Edges/SshHostToRemoteCommand.cs +++ b/csharp/Lib/SysTools/Edges/SshHostToRemoteCommand.cs @@ -1,6 +1,6 @@ -using InnovEnergy.SysTools.Remote; +using InnovEnergy.Lib.SysTools.Remote; -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; public static class SshHostToRemoteCommand { diff --git a/csharp/lib/SysTools/Edges/SshHostToRemotePath.cs b/csharp/Lib/SysTools/Edges/SshHostToRemotePath.cs similarity index 67% rename from csharp/lib/SysTools/Edges/SshHostToRemotePath.cs rename to csharp/Lib/SysTools/Edges/SshHostToRemotePath.cs index 3d7c75156..4833861b6 100644 --- a/csharp/lib/SysTools/Edges/SshHostToRemotePath.cs +++ b/csharp/Lib/SysTools/Edges/SshHostToRemotePath.cs @@ -1,6 +1,6 @@ -using InnovEnergy.SysTools.Remote; +using InnovEnergy.Lib.SysTools.Remote; -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; public static class SshHostToRemotePath { diff --git a/csharp/lib/SysTools/Edges/StringToCommand.cs b/csharp/Lib/SysTools/Edges/StringToCommand.cs similarity index 65% rename from csharp/lib/SysTools/Edges/StringToCommand.cs rename to csharp/Lib/SysTools/Edges/StringToCommand.cs index f905db3d8..62c595f75 100644 --- a/csharp/lib/SysTools/Edges/StringToCommand.cs +++ b/csharp/Lib/SysTools/Edges/StringToCommand.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; public static class StringToCommand { @@ -7,50 +7,50 @@ public static class StringToCommand public static SysCommand Opt1(this String cmd, String option) { return cmd - .ToCommand() - .Opt1(option); + .ToCommand() + .Opt1(option); } public static SysCommand Opt1(this String cmd, String option, Object value, String separator = " ") { return cmd - .ToCommand() - .Opt1(option, value, separator); + .ToCommand() + .Opt1(option, value, separator); } public static SysCommand Opt2(this String cmd, String option) { return cmd - .ToCommand() - .Opt2(option); + .ToCommand() + .Opt2(option); } public static SysCommand Opt2(this String cmd, String option, Object value, String separator = "=") { return cmd - .ToCommand() - .Opt2(option, value, separator); + .ToCommand() + .Opt2(option, value, separator); } public static SysCommand Opt(this String cmd, String option) { return cmd - .ToCommand() - .Opt(option); + .ToCommand() + .Opt(option); } public static SysCommand Opt(this String cmd, String option, Object value) { return cmd - .ToCommand() - .Opt(option, value); + .ToCommand() + .Opt(option, value); } public static SysCommand Arg(this String cmd, Object argument) { return cmd - .ToCommand() - .Arg(argument); + .ToCommand() + .Arg(argument); } } \ No newline at end of file diff --git a/csharp/lib/SysTools/Edges/StringToProcess.cs b/csharp/Lib/SysTools/Edges/StringToProcess.cs similarity index 89% rename from csharp/lib/SysTools/Edges/StringToProcess.cs rename to csharp/Lib/SysTools/Edges/StringToProcess.cs index 6a4229ef6..e8d3c4d63 100644 --- a/csharp/lib/SysTools/Edges/StringToProcess.cs +++ b/csharp/Lib/SysTools/Edges/StringToProcess.cs @@ -1,6 +1,6 @@ -using InnovEnergy.SysTools.Process; +using InnovEnergy.Lib.SysTools.Process; -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; using Env = Dictionary; diff --git a/csharp/lib/SysTools/Edges/StringToRemotePath.cs b/csharp/Lib/SysTools/Edges/StringToRemotePath.cs similarity index 62% rename from csharp/lib/SysTools/Edges/StringToRemotePath.cs rename to csharp/Lib/SysTools/Edges/StringToRemotePath.cs index 5fd37b695..9a32e16b1 100644 --- a/csharp/lib/SysTools/Edges/StringToRemotePath.cs +++ b/csharp/Lib/SysTools/Edges/StringToRemotePath.cs @@ -1,6 +1,6 @@ -using InnovEnergy.SysTools.Remote; +using InnovEnergy.Lib.SysTools.Remote; -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; public static class StringToRemotePath { diff --git a/csharp/lib/SysTools/Edges/StringToSysPath.cs b/csharp/Lib/SysTools/Edges/StringToSysPath.cs similarity index 72% rename from csharp/lib/SysTools/Edges/StringToSysPath.cs rename to csharp/Lib/SysTools/Edges/StringToSysPath.cs index 4e31e2836..bf7a93615 100644 --- a/csharp/lib/SysTools/Edges/StringToSysPath.cs +++ b/csharp/Lib/SysTools/Edges/StringToSysPath.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; public static class StringToSysPath { diff --git a/csharp/lib/SysTools/Edges/SysCommandToProcess.cs b/csharp/Lib/SysTools/Edges/SysCommandToProcess.cs similarity index 96% rename from csharp/lib/SysTools/Edges/SysCommandToProcess.cs rename to csharp/Lib/SysTools/Edges/SysCommandToProcess.cs index ceee12ebe..b8b142b6f 100644 --- a/csharp/lib/SysTools/Edges/SysCommandToProcess.cs +++ b/csharp/Lib/SysTools/Edges/SysCommandToProcess.cs @@ -1,9 +1,9 @@ using System.Diagnostics; using System.Reactive.Linq; using System.Text; -using InnovEnergy.SysTools.Process; +using InnovEnergy.Lib.SysTools.Process; -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; public static class SysCommandToProcess { diff --git a/csharp/lib/SysTools/Edges/SysCommandToRemoteCommand.cs b/csharp/Lib/SysTools/Edges/SysCommandToRemoteCommand.cs similarity index 73% rename from csharp/lib/SysTools/Edges/SysCommandToRemoteCommand.cs rename to csharp/Lib/SysTools/Edges/SysCommandToRemoteCommand.cs index acb42b570..faa77d432 100644 --- a/csharp/lib/SysTools/Edges/SysCommandToRemoteCommand.cs +++ b/csharp/Lib/SysTools/Edges/SysCommandToRemoteCommand.cs @@ -1,6 +1,6 @@ -using InnovEnergy.SysTools.Remote; +using InnovEnergy.Lib.SysTools.Remote; -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; public static class SysCommandToRemoteCommand { diff --git a/csharp/lib/SysTools/Edges/SysPathToProcess.cs b/csharp/Lib/SysTools/Edges/SysPathToProcess.cs similarity index 89% rename from csharp/lib/SysTools/Edges/SysPathToProcess.cs rename to csharp/Lib/SysTools/Edges/SysPathToProcess.cs index be8fe9904..60a030fe8 100644 --- a/csharp/lib/SysTools/Edges/SysPathToProcess.cs +++ b/csharp/Lib/SysTools/Edges/SysPathToProcess.cs @@ -1,6 +1,6 @@ -using InnovEnergy.SysTools.Process; +using InnovEnergy.Lib.SysTools.Process; -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; public static class SysPathToProcess { diff --git a/csharp/lib/SysTools/Edges/SysPathToRemotePath.cs b/csharp/Lib/SysTools/Edges/SysPathToRemotePath.cs similarity index 66% rename from csharp/lib/SysTools/Edges/SysPathToRemotePath.cs rename to csharp/Lib/SysTools/Edges/SysPathToRemotePath.cs index aebf6eade..410f6bb7e 100644 --- a/csharp/lib/SysTools/Edges/SysPathToRemotePath.cs +++ b/csharp/Lib/SysTools/Edges/SysPathToRemotePath.cs @@ -1,6 +1,6 @@ -using InnovEnergy.SysTools.Remote; +using InnovEnergy.Lib.SysTools.Remote; -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; public static class SysPathToRemotePath { diff --git a/csharp/lib/SysTools/Edges/SysPathToSysCommand.cs b/csharp/Lib/SysTools/Edges/SysPathToSysCommand.cs similarity index 96% rename from csharp/lib/SysTools/Edges/SysPathToSysCommand.cs rename to csharp/Lib/SysTools/Edges/SysPathToSysCommand.cs index 647778e9d..9f27e68ff 100644 --- a/csharp/lib/SysTools/Edges/SysPathToSysCommand.cs +++ b/csharp/Lib/SysTools/Edges/SysPathToSysCommand.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.SysTools.Edges; +namespace InnovEnergy.Lib.SysTools.Edges; public static class SysPathToSysCommand { diff --git a/csharp/lib/SysTools/FileIo.cs b/csharp/Lib/SysTools/FileIo.cs similarity index 74% rename from csharp/lib/SysTools/FileIo.cs rename to csharp/Lib/SysTools/FileIo.cs index 1df5a0adf..bec888e56 100644 --- a/csharp/lib/SysTools/FileIo.cs +++ b/csharp/Lib/SysTools/FileIo.cs @@ -1,8 +1,9 @@ using System.Text; -using InnovEnergy.SysTools.Utils; +using InnovEnergy.Lib.Utils; -namespace InnovEnergy.SysTools; +namespace InnovEnergy.Lib.SysTools; +[Obsolete("Needs rework before use")] public static class FileIo { public static Boolean Exists (this SysPath path) => path.FileExists() || path.DirectoryExists(); @@ -14,12 +15,12 @@ public static class FileIo public static IEnumerable Directories(this SysPath sysPath) => Directory - .GetDirectories(sysPath) - .Select(SysPath.FromString); + .GetDirectories(sysPath) + .Select(SysPath.FromString); public static IEnumerable Files(this SysPath sysPath) => Directory - .GetFiles(sysPath) - .Select(SysPath.FromString); + .GetFiles(sysPath) + .Select(SysPath.FromString); public static SysPath CreateDirectory(this SysPath path) @@ -91,10 +92,10 @@ public static class FileIo SysPath Target(SysPath path) => targetDir.Append(path.RelativeTo(sourceDir)); - Utils.Utils.Traverse(sourceDir, Directories) - .Do(d => Target(d).CreateDirectory()) - .SelectMany(Files) - .ForEach(f => f.CopyFileTo(Target(f))); + sourceDir.Traverse(Directories) + .Do(d => Target(d).CreateDirectory()) + .SelectMany(Files) + .ForEach(f => f.CopyFileTo(Target(f))); return sourceDir; } @@ -115,14 +116,14 @@ public static class FileIo public static IEnumerable DescendantDirectories(this SysPath path) { - return Utils.Utils.Traverse(path, Directories); + return path.Traverse(Directories); } public static IEnumerable DescendantFiles(this SysPath path) { return path - .DescendantDirectories() - .SelectMany(Files); + .DescendantDirectories() + .SelectMany(Files); } public static IEnumerable Descendants(this SysPath path) @@ -140,13 +141,11 @@ public static class FileIo { var buf = new Byte[4096]; - using (var fs = File.OpenRead(path)) - { - var n = fs.Read(buf, 0, buf.Length); + using var fs = File.OpenRead(path); + var n = fs.Read(buf, 0, buf.Length); - foreach (var b in buf.Take(Math.Max(0, n))) - yield return b; - } + foreach (var b in buf.Take(Math.Max(0, n))) + yield return b; } @@ -154,29 +153,29 @@ public static class FileIo public static IEnumerable ReadLines(this SysPath path, Encoding encoding) { - using (var sr = new StreamReader(path, encoding)) - while (true) - { - var str = sr.ReadLine(); - if (str == null) - yield break; + using var sr = new StreamReader(path, encoding); + while (true) + { + var str = sr.ReadLine(); + if (str == null) + yield break; - yield return str; - } + yield return str; + } } public static String ReadText(this SysPath path) => path.ReadText(Encoding.UTF8); public static String ReadText(this SysPath path, Encoding encoding) { - using (var sr = new StreamReader(path, encoding)) - return sr.ReadToEnd(); + using var sr = new StreamReader(path, encoding); + return sr.ReadToEnd(); } public static SysPath WriteText(this SysPath filePath, String text) { - using (var sw = new StreamWriter(filePath, append: false)) - sw.Write(text); + using var sw = new StreamWriter(filePath, append: false); + sw.Write(text); return filePath; } @@ -189,17 +188,17 @@ public static class FileIo public static SysPath WriteLines(this SysPath filePath, IEnumerable lines) { - using (var sw = new StreamWriter(filePath, append: false)) - foreach (var line in lines) - sw.WriteLine(line); + using var sw = new StreamWriter(filePath, append: false); + foreach (var line in lines) + sw.WriteLine(line); return filePath; } public static SysPath AppendText(this SysPath filePath, String text) { - using (var sw = new StreamWriter(filePath, append: true)) - sw.Write(text); + using var sw = new StreamWriter(filePath, append: true); + sw.Write(text); return filePath; } @@ -212,9 +211,9 @@ public static class FileIo public static SysPath AppendLines(this SysPath filePath, IEnumerable lines) { - using (var sw = new StreamWriter(filePath, append: true)) - foreach (var line in lines) - sw.WriteLine(line); + using var sw = new StreamWriter(filePath, append: true); + foreach (var line in lines) + sw.WriteLine(line); return filePath; } diff --git a/csharp/lib/SysTools/Process/AsyncProcess.cs b/csharp/Lib/SysTools/Process/AsyncProcess.cs similarity index 97% rename from csharp/lib/SysTools/Process/AsyncProcess.cs rename to csharp/Lib/SysTools/Process/AsyncProcess.cs index c8c0f3262..ee0137482 100644 --- a/csharp/lib/SysTools/Process/AsyncProcess.cs +++ b/csharp/Lib/SysTools/Process/AsyncProcess.cs @@ -2,13 +2,14 @@ using System.Diagnostics; using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Reactive.Subjects; -using InnovEnergy.SysTools.Utils; +using InnovEnergy.Lib.SysTools.Utils; using static System.ConsoleColor; -namespace InnovEnergy.SysTools.Process; +namespace InnovEnergy.Lib.SysTools.Process; using Env = Dictionary; +[Obsolete("Use CliWrap instead")] public class AsyncProcess { private readonly Subject _StandardIn; diff --git a/csharp/lib/SysTools/Process/ProcessResult.cs b/csharp/Lib/SysTools/Process/ProcessResult.cs similarity index 92% rename from csharp/lib/SysTools/Process/ProcessResult.cs rename to csharp/Lib/SysTools/Process/ProcessResult.cs index bf9f3a52a..e8af589b3 100644 --- a/csharp/lib/SysTools/Process/ProcessResult.cs +++ b/csharp/Lib/SysTools/Process/ProcessResult.cs @@ -1,7 +1,8 @@ -using InnovEnergy.SysTools.Utils; +using InnovEnergy.Lib.SysTools.Utils; -namespace InnovEnergy.SysTools.Process; +namespace InnovEnergy.Lib.SysTools.Process; +[Obsolete("Use CliWrap instead")] public readonly struct ProcessResult { public ProcessResult(Int32 exitCode, diff --git a/csharp/lib/SysTools/Process/SyncProcess.cs b/csharp/Lib/SysTools/Process/SyncProcess.cs similarity index 98% rename from csharp/lib/SysTools/Process/SyncProcess.cs rename to csharp/Lib/SysTools/Process/SyncProcess.cs index 89b23073e..7d47f447f 100644 --- a/csharp/lib/SysTools/Process/SyncProcess.cs +++ b/csharp/Lib/SysTools/Process/SyncProcess.cs @@ -1,13 +1,13 @@ using System.Diagnostics; using System.Text.RegularExpressions; -using InnovEnergy.SysTools.Utils; +using InnovEnergy.Lib.SysTools.Utils; using static System.ConsoleColor; -namespace InnovEnergy.SysTools.Process; +namespace InnovEnergy.Lib.SysTools.Process; using Env = Dictionary; - +[Obsolete("Use CliWrap instead")] public class SyncProcess { public SysCommand Command { get; } diff --git a/csharp/lib/SysTools/Remote/RemoteCommand.cs b/csharp/Lib/SysTools/Remote/RemoteCommand.cs similarity index 96% rename from csharp/lib/SysTools/Remote/RemoteCommand.cs rename to csharp/Lib/SysTools/Remote/RemoteCommand.cs index 75d1febf5..fa80d2671 100644 --- a/csharp/lib/SysTools/Remote/RemoteCommand.cs +++ b/csharp/Lib/SysTools/Remote/RemoteCommand.cs @@ -1,7 +1,8 @@ -using InnovEnergy.SysTools.Utils; +using InnovEnergy.Lib.SysTools.Utils; -namespace InnovEnergy.SysTools.Remote; +namespace InnovEnergy.Lib.SysTools.Remote; +[Obsolete("Use CliWrap instead")] public readonly struct RemoteCommand { public SshHost Host { get; } diff --git a/csharp/lib/SysTools/Remote/RemoteFileIo.cs b/csharp/Lib/SysTools/Remote/RemoteFileIo.cs similarity index 97% rename from csharp/lib/SysTools/Remote/RemoteFileIo.cs rename to csharp/Lib/SysTools/Remote/RemoteFileIo.cs index 1ecdf6868..e317d7227 100644 --- a/csharp/lib/SysTools/Remote/RemoteFileIo.cs +++ b/csharp/Lib/SysTools/Remote/RemoteFileIo.cs @@ -1,8 +1,9 @@ -using InnovEnergy.SysTools.Edges; -using InnovEnergy.SysTools.Utils; +using InnovEnergy.Lib.SysTools.Edges; +using InnovEnergy.Lib.SysTools.Utils; -namespace InnovEnergy.SysTools.Remote; +namespace InnovEnergy.Lib.SysTools.Remote; +[Obsolete("Needs rework before use")] public static class RemoteFileIo { diff --git a/csharp/lib/SysTools/Remote/RemotePath.cs b/csharp/Lib/SysTools/Remote/RemotePath.cs similarity index 97% rename from csharp/lib/SysTools/Remote/RemotePath.cs rename to csharp/Lib/SysTools/Remote/RemotePath.cs index e8a048bdd..dcb75adb3 100644 --- a/csharp/lib/SysTools/Remote/RemotePath.cs +++ b/csharp/Lib/SysTools/Remote/RemotePath.cs @@ -1,5 +1,6 @@ -namespace InnovEnergy.SysTools.Remote; +namespace InnovEnergy.Lib.SysTools.Remote; +[Obsolete] public readonly struct RemotePath { public SysPath Path { get; } diff --git a/csharp/lib/SysTools/Remote/SshHost.cs b/csharp/Lib/SysTools/Remote/SshHost.cs similarity index 94% rename from csharp/lib/SysTools/Remote/SshHost.cs rename to csharp/Lib/SysTools/Remote/SshHost.cs index 7f7187302..a957f2887 100644 --- a/csharp/lib/SysTools/Remote/SshHost.cs +++ b/csharp/Lib/SysTools/Remote/SshHost.cs @@ -1,8 +1,9 @@ -using InnovEnergy.SysTools.Edges; -using InnovEnergy.SysTools.Utils; +using InnovEnergy.Lib.SysTools.Edges; +using InnovEnergy.Lib.SysTools.Utils; -namespace InnovEnergy.SysTools.Remote; +namespace InnovEnergy.Lib.SysTools.Remote; +[Obsolete("Needs rework before use")] public readonly struct SshHost { public const Int32 DefaultPort = 22; diff --git a/csharp/lib/SysTools/SysCommand.cs b/csharp/Lib/SysTools/SysCommand.cs similarity index 96% rename from csharp/lib/SysTools/SysCommand.cs rename to csharp/Lib/SysTools/SysCommand.cs index da3826e83..50237a5d8 100644 --- a/csharp/lib/SysTools/SysCommand.cs +++ b/csharp/Lib/SysTools/SysCommand.cs @@ -1,7 +1,8 @@ -using InnovEnergy.SysTools.Utils; +using InnovEnergy.Lib.SysTools.Utils; -namespace InnovEnergy.SysTools; +namespace InnovEnergy.Lib.SysTools; +[Obsolete("Use CliWrap instead")] public readonly struct SysCommand { public SysPath Path { get; } diff --git a/csharp/lib/SysTools/SysDirs.cs b/csharp/Lib/SysTools/SysDirs.cs similarity index 95% rename from csharp/lib/SysTools/SysDirs.cs rename to csharp/Lib/SysTools/SysDirs.cs index afec2e014..c65d4e7b7 100644 --- a/csharp/lib/SysTools/SysDirs.cs +++ b/csharp/Lib/SysTools/SysDirs.cs @@ -1,8 +1,9 @@ using static System.Environment; using static System.Environment.SpecialFolder; -namespace InnovEnergy.SysTools; +namespace InnovEnergy.Lib.SysTools; +[Obsolete] public static class SysDirs { diff --git a/csharp/lib/SysTools/SysPath.cs b/csharp/Lib/SysTools/SysPath.cs similarity index 97% rename from csharp/lib/SysTools/SysPath.cs rename to csharp/Lib/SysTools/SysPath.cs index 42bd090e0..b5ff40c36 100644 --- a/csharp/lib/SysTools/SysPath.cs +++ b/csharp/Lib/SysTools/SysPath.cs @@ -1,10 +1,11 @@ using System.Text; -using InnovEnergy.SysTools.Remote; -using InnovEnergy.SysTools.Utils; +using InnovEnergy.Lib.SysTools.Remote; +using InnovEnergy.Lib.SysTools.Utils; using static System.IO.Path; -namespace InnovEnergy.SysTools; +namespace InnovEnergy.Lib.SysTools; +[Obsolete("Needs rework before use")] public readonly struct SysPath { private readonly String _Path; diff --git a/csharp/Lib/SysTools/SysTools.csproj b/csharp/Lib/SysTools/SysTools.csproj new file mode 100644 index 000000000..72e17cc9b --- /dev/null +++ b/csharp/Lib/SysTools/SysTools.csproj @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/csharp/lib/SysTools/Utils/ConsoleUtils.cs b/csharp/Lib/SysTools/Utils/ConsoleUtils.cs similarity index 97% rename from csharp/lib/SysTools/Utils/ConsoleUtils.cs rename to csharp/Lib/SysTools/Utils/ConsoleUtils.cs index f1d418b3b..5c58ef3b4 100644 --- a/csharp/lib/SysTools/Utils/ConsoleUtils.cs +++ b/csharp/Lib/SysTools/Utils/ConsoleUtils.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.SysTools.Utils; +namespace InnovEnergy.Lib.SysTools.Utils; internal static class ConsoleUtils { diff --git a/csharp/lib/SysTools/Utils/EnumerableUtils.cs b/csharp/Lib/SysTools/Utils/EnumerableUtils.cs similarity index 68% rename from csharp/lib/SysTools/Utils/EnumerableUtils.cs rename to csharp/Lib/SysTools/Utils/EnumerableUtils.cs index e0e6338dc..9f02caacf 100644 --- a/csharp/lib/SysTools/Utils/EnumerableUtils.cs +++ b/csharp/Lib/SysTools/Utils/EnumerableUtils.cs @@ -1,18 +1,16 @@ -namespace InnovEnergy.SysTools.Utils; +namespace InnovEnergy.Lib.SysTools.Utils; internal static class EnumerableUtils { public static IEnumerable Pad(this IEnumerable src, Int32 length, T padding) { - using (var enumerator = src.GetEnumerator()) - { - while (enumerator.MoveNext() && length-- > 0) - yield return enumerator.Current; + using var enumerator = src.GetEnumerator(); + while (enumerator.MoveNext() && length-- > 0) + yield return enumerator.Current; - while (length-- > 0) - yield return padding; - } + while (length-- > 0) + yield return padding; } public static Dictionary> IndexColumn(this IEnumerable> src, UInt16 index) @@ -33,10 +31,10 @@ internal static class EnumerableUtils public static IEnumerable<(TLeft left, TRight right)> Zip(IEnumerable left, IEnumerable right) { - using (var l = left.GetEnumerator()) - using (var r = right.GetEnumerator()) - while (l.MoveNext() && r.MoveNext()) - yield return (l.Current, r.Current); + using var l = left.GetEnumerator(); + using var r = right.GetEnumerator(); + while (l.MoveNext() && r.MoveNext()) + yield return (l.Current, r.Current); } public static IEnumerator Enumerator(this T t) @@ -57,20 +55,6 @@ internal static class EnumerableUtils action(e); } - public static IEnumerable Do(this IEnumerable enumerable, Action action) - { - return enumerable.Select(e => - { - action(e); - return e; - }); - } - - public static void ForEach(this IEnumerable enumerable, Func func) - { - foreach (var e in enumerable) - func(e); - } public static IEnumerable WhereNot(this IEnumerable enumerable, Func predicate) diff --git a/csharp/lib/SysTools/Utils/StringUtils.cs b/csharp/Lib/SysTools/Utils/StringUtils.cs similarity index 99% rename from csharp/lib/SysTools/Utils/StringUtils.cs rename to csharp/Lib/SysTools/Utils/StringUtils.cs index c67f724c7..c6a6ef12c 100644 --- a/csharp/lib/SysTools/Utils/StringUtils.cs +++ b/csharp/Lib/SysTools/Utils/StringUtils.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.SysTools.Utils; +namespace InnovEnergy.Lib.SysTools.Utils; internal static class StringUtils { diff --git a/csharp/Lib/SysTools/Utils/Utils.cs b/csharp/Lib/SysTools/Utils/Utils.cs new file mode 100644 index 000000000..026c63e8c --- /dev/null +++ b/csharp/Lib/SysTools/Utils/Utils.cs @@ -0,0 +1,37 @@ +namespace InnovEnergy.Lib.SysTools.Utils; + +public static class Utils +{ + + private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public static DateTime FromUnixTime(UInt64 unixTime) + { + return Epoch.AddSeconds(unixTime); + } + + public static R ValueOrDefault(this Dictionary dict, T key) + { + return ValueOrDefault(dict, key, default); + } + + public static R ValueOrDefault(this Dictionary dict, T key, R defaultValue) + { + return dict.TryGetValue(key, out var value) ? value : defaultValue; + } + + public static void CopyFilesRecursively(String source, String target) + { + CopyFilesRecursively(new DirectoryInfo(source), new DirectoryInfo(target)); + } + + public static void CopyFilesRecursively(DirectoryInfo source, DirectoryInfo target) + { + foreach (var file in source.GetFiles()) + file.CopyTo(Path.Combine(target.FullName, file.Name)); + + foreach (var dir in source.GetDirectories()) + CopyFilesRecursively(dir, target.CreateSubdirectory(dir.Name)); + } + +} \ No newline at end of file diff --git a/csharp/Lib/Time/Time.csproj b/csharp/Lib/Time/Time.csproj new file mode 100644 index 000000000..e971a1f7d --- /dev/null +++ b/csharp/Lib/Time/Time.csproj @@ -0,0 +1,3 @@ + + + diff --git a/csharp/lib/Time/Unix/UnixTime.Compare.cs b/csharp/Lib/Time/Unix/UnixTime.Compare.cs similarity index 87% rename from csharp/lib/Time/Unix/UnixTime.Compare.cs rename to csharp/Lib/Time/Unix/UnixTime.Compare.cs index ca91d5ce2..044ef1a32 100644 --- a/csharp/lib/Time/Unix/UnixTime.Compare.cs +++ b/csharp/Lib/Time/Unix/UnixTime.Compare.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Time.Unix; +namespace InnovEnergy.Lib.Time.Unix; public readonly partial struct UnixTime : IComparable, IEquatable { diff --git a/csharp/lib/Time/Unix/UnixTime.Constructors.cs b/csharp/Lib/Time/Unix/UnixTime.Constructors.cs similarity index 94% rename from csharp/lib/Time/Unix/UnixTime.Constructors.cs rename to csharp/Lib/Time/Unix/UnixTime.Constructors.cs index ba26db8f3..5d963e488 100644 --- a/csharp/lib/Time/Unix/UnixTime.Constructors.cs +++ b/csharp/Lib/Time/Unix/UnixTime.Constructors.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Time.Unix; +namespace InnovEnergy.Lib.Time.Unix; public readonly partial struct UnixTime { diff --git a/csharp/lib/Time/Unix/UnixTime.Converters.cs b/csharp/Lib/Time/Unix/UnixTime.Converters.cs similarity index 77% rename from csharp/lib/Time/Unix/UnixTime.Converters.cs rename to csharp/Lib/Time/Unix/UnixTime.Converters.cs index d8495f092..0d5d17aeb 100644 --- a/csharp/lib/Time/Unix/UnixTime.Converters.cs +++ b/csharp/Lib/Time/Unix/UnixTime.Converters.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Time.Unix; +namespace InnovEnergy.Lib.Time.Unix; public readonly partial struct UnixTime { diff --git a/csharp/lib/Time/Unix/UnixTime.Operators.cs b/csharp/Lib/Time/Unix/UnixTime.Operators.cs similarity index 96% rename from csharp/lib/Time/Unix/UnixTime.Operators.cs rename to csharp/Lib/Time/Unix/UnixTime.Operators.cs index 850ccbfe3..7aeb71b69 100644 --- a/csharp/lib/Time/Unix/UnixTime.Operators.cs +++ b/csharp/Lib/Time/Unix/UnixTime.Operators.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Time.Unix; +namespace InnovEnergy.Lib.Time.Unix; public readonly partial struct UnixTime { diff --git a/csharp/lib/Time/Unix/UnixTime.Overrides.cs b/csharp/Lib/Time/Unix/UnixTime.Overrides.cs similarity index 88% rename from csharp/lib/Time/Unix/UnixTime.Overrides.cs rename to csharp/Lib/Time/Unix/UnixTime.Overrides.cs index 66621de8e..c2ba77a5e 100644 --- a/csharp/lib/Time/Unix/UnixTime.Overrides.cs +++ b/csharp/Lib/Time/Unix/UnixTime.Overrides.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Time.Unix; +namespace InnovEnergy.Lib.Time.Unix; public readonly partial struct UnixTime { diff --git a/csharp/lib/Time/Unix/UnixTime.cs b/csharp/Lib/Time/Unix/UnixTime.cs similarity index 87% rename from csharp/lib/Time/Unix/UnixTime.cs rename to csharp/Lib/Time/Unix/UnixTime.cs index b441c0c5f..f771fd01d 100644 --- a/csharp/lib/Time/Unix/UnixTime.cs +++ b/csharp/Lib/Time/Unix/UnixTime.cs @@ -2,7 +2,7 @@ // ReSharper disable ArrangeStaticMemberQualifier -namespace InnovEnergy.Time.Unix; +namespace InnovEnergy.Lib.Time.Unix; public readonly partial struct UnixTime { diff --git a/csharp/lib/Time/Unix/UnixTimeDelta.Compare.cs b/csharp/Lib/Time/Unix/UnixTimeSpan.Compare.cs similarity index 88% rename from csharp/lib/Time/Unix/UnixTimeDelta.Compare.cs rename to csharp/Lib/Time/Unix/UnixTimeSpan.Compare.cs index aac266c70..72f59f82e 100644 --- a/csharp/lib/Time/Unix/UnixTimeDelta.Compare.cs +++ b/csharp/Lib/Time/Unix/UnixTimeSpan.Compare.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Time.Unix; +namespace InnovEnergy.Lib.Time.Unix; public readonly partial struct UnixTimeSpan : IComparable, IEquatable { diff --git a/csharp/lib/Time/Unix/UnixTimeDelta.Constructors.cs b/csharp/Lib/Time/Unix/UnixTimeSpan.Constructors.cs similarity index 96% rename from csharp/lib/Time/Unix/UnixTimeDelta.Constructors.cs rename to csharp/Lib/Time/Unix/UnixTimeSpan.Constructors.cs index 7811fb4d6..c1fa4f877 100644 --- a/csharp/lib/Time/Unix/UnixTimeDelta.Constructors.cs +++ b/csharp/Lib/Time/Unix/UnixTimeSpan.Constructors.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Time.Unix; +namespace InnovEnergy.Lib.Time.Unix; public readonly partial struct UnixTimeSpan { diff --git a/csharp/lib/Time/Unix/UnixTimeDelta.Operators.cs b/csharp/Lib/Time/Unix/UnixTimeSpan.Operators.cs similarity index 93% rename from csharp/lib/Time/Unix/UnixTimeDelta.Operators.cs rename to csharp/Lib/Time/Unix/UnixTimeSpan.Operators.cs index d373ccde1..e59d3dfba 100644 --- a/csharp/lib/Time/Unix/UnixTimeDelta.Operators.cs +++ b/csharp/Lib/Time/Unix/UnixTimeSpan.Operators.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Time.Unix; +namespace InnovEnergy.Lib.Time.Unix; public readonly partial struct UnixTimeSpan { @@ -20,7 +20,7 @@ public readonly partial struct UnixTimeSpan public static UnixTimeSpan operator /(UnixTimeSpan a, UInt32 b) => new UnixTimeSpan(a.Ticks / b); public static UnixTimeSpan operator /(UnixTimeSpan a, Int32 b) => new UnixTimeSpan(a.Ticks / (UInt32)b); - public static UInt32 operator /(UnixTimeSpan a, UnixTimeSpan b) => a.Ticks / b.Ticks; + public static UInt32 operator /(UnixTimeSpan a, UnixTimeSpan b) => a.Ticks / b.Ticks; public static UnixTimeSpan operator %(UnixTimeSpan a, UInt32 b) => new UnixTimeSpan(a.Ticks % b); public static UnixTimeSpan operator %(UnixTimeSpan a, Int32 b) => new UnixTimeSpan(a.Ticks % (UInt32)b); diff --git a/csharp/lib/Time/Unix/UnixTimeDelta.Overrides.cs b/csharp/Lib/Time/Unix/UnixTimeSpan.Overrides.cs similarity index 96% rename from csharp/lib/Time/Unix/UnixTimeDelta.Overrides.cs rename to csharp/Lib/Time/Unix/UnixTimeSpan.Overrides.cs index 0c000fab9..9552fbbe3 100644 --- a/csharp/lib/Time/Unix/UnixTimeDelta.Overrides.cs +++ b/csharp/Lib/Time/Unix/UnixTimeSpan.Overrides.cs @@ -1,6 +1,6 @@ using System.Text; -namespace InnovEnergy.Time.Unix; +namespace InnovEnergy.Lib.Time.Unix; public readonly partial struct UnixTimeSpan { diff --git a/csharp/lib/Time/Unix/UnixTimeSpan.cs b/csharp/Lib/Time/Unix/UnixTimeSpan.cs similarity index 81% rename from csharp/lib/Time/Unix/UnixTimeSpan.cs rename to csharp/Lib/Time/Unix/UnixTimeSpan.cs index cb54ae279..d525deaba 100644 --- a/csharp/lib/Time/Unix/UnixTimeSpan.cs +++ b/csharp/Lib/Time/Unix/UnixTimeSpan.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Time.Unix; +namespace InnovEnergy.Lib.Time.Unix; public readonly partial struct UnixTimeSpan { diff --git a/csharp/lib/Time/Unix/UnixTimeDeltaExtensions.cs b/csharp/Lib/Time/Unix/UnixTimeSpanExtensions.cs similarity index 95% rename from csharp/lib/Time/Unix/UnixTimeDeltaExtensions.cs rename to csharp/Lib/Time/Unix/UnixTimeSpanExtensions.cs index c51928b98..fd98e3487 100644 --- a/csharp/lib/Time/Unix/UnixTimeDeltaExtensions.cs +++ b/csharp/Lib/Time/Unix/UnixTimeSpanExtensions.cs @@ -1,4 +1,4 @@ -namespace InnovEnergy.Time.Unix; +namespace InnovEnergy.Lib.Time.Unix; public static class UnixTimeDeltaExtensions { diff --git a/csharp/Lib/Units/Angle.cs b/csharp/Lib/Units/Angle.cs new file mode 100644 index 000000000..b8972c826 --- /dev/null +++ b/csharp/Lib/Units/Angle.cs @@ -0,0 +1,25 @@ +using DecimalMath; +using InnovEnergy.Lib.Units.Generator; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Units; + +[Sum] +public readonly partial struct Angle +{ + public static String Unit => "rad"; + public static String Symbol => "∠"; + + public static readonly Angle Pi = new Angle(DecimalEx.Pi); + + + public Angle(Decimal value) + { + var modulo = value.Modulo(DecimalEx.TwoPi); + + Value = modulo > DecimalEx.Pi + ? modulo - DecimalEx.TwoPi + : modulo; + } + +} \ No newline at end of file diff --git a/csharp/Lib/Units/Angle.generated.cs b/csharp/Lib/Units/Angle.generated.cs new file mode 100644 index 000000000..837f0b2a2 --- /dev/null +++ b/csharp/Lib/Units/Angle.generated.cs @@ -0,0 +1,97 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +#define Sum + +using static System.Math; +using System.Text.Json; +using System.Text.Json.Serialization; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Units; + +using T = Angle; + +[JsonConverter(typeof(AngleConverter))] +public readonly partial struct Angle +{ + public Decimal Value { get; } + public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; + + // scalar multiplication + + public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); + public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); + public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); + + // parallel + + #if Sum + + public static T operator |(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T t) => new T(-t.Value); + + #elif Mean + + public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); + + #elif Equal + + public static T operator |(T left, T right) + { + var d = Max(Abs(left.Value), Abs(right.Value)); + + if (d == 0m) + return new T(0m); + + var relativeError = Abs(left.Value - right.Value) / d; + + const Decimal maxRelativeError = 0.05m; + + if (relativeError > maxRelativeError) + throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + + $"Difference > {maxRelativeError * 100}% detected\n" + + $"{nameof(left)} : {left}\n" + + $"{nameof(right)}: {right}"); + + return new T((left.Value + right.Value) / 2m); + } + #endif + + // compare + + public static Boolean operator ==(T left, T right) => left.Value == right.Value; + public static Boolean operator !=(T left, T right) => left.Value != right.Value; + public static Boolean operator > (T left, T right) => left.Value > right.Value; + public static Boolean operator < (T left, T right) => left.Value < right.Value; + public static Boolean operator >=(T left, T right) => left.Value >= right.Value; + public static Boolean operator <=(T left, T right) => left.Value <= right.Value; + + // conversion + + public static implicit operator T(Decimal d) => new T(d); + public static implicit operator T(Double d) => new T((Decimal)d); + public static implicit operator T(Int32 i) => new T(i); + public static implicit operator Decimal(T t) => t.Value; + + // equality + + public Boolean Equals(T other) => Value == other.Value; + public override Boolean Equals(Object? obj) => obj is T other && Equals(other); + public override Int32 GetHashCode() => Value.GetHashCode(); + +} + + +internal class AngleConverter : JsonConverter +{ + public override Angle Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new Angle(reader.GetDecimal()); + } + + public override void Write(Utf8JsonWriter writer, Angle value, JsonSerializerOptions options) + { + var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits); + + writer.WriteNumberValue(rounded); + } +} \ No newline at end of file diff --git a/csharp/Lib/Units/ApparentPower.cs b/csharp/Lib/Units/ApparentPower.cs new file mode 100644 index 000000000..8c92385de --- /dev/null +++ b/csharp/Lib/Units/ApparentPower.cs @@ -0,0 +1,16 @@ +using InnovEnergy.Lib.Units.Generator; + +namespace InnovEnergy.Lib.Units; + +[Sum] +public readonly partial struct ApparentPower +{ + public static String Unit => "VA"; + public static String Symbol => "S"; + + public ApparentPower(Decimal value) + { + if (value < 0) throw new ArgumentException("Apparent power cannot be negative", nameof(value)); + Value = value; + } +} \ No newline at end of file diff --git a/csharp/Lib/Units/ApparentPower.generated.cs b/csharp/Lib/Units/ApparentPower.generated.cs new file mode 100644 index 000000000..a72053726 --- /dev/null +++ b/csharp/Lib/Units/ApparentPower.generated.cs @@ -0,0 +1,97 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +#define Sum + +using static System.Math; +using System.Text.Json; +using System.Text.Json.Serialization; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Units; + +using T = ApparentPower; + +[JsonConverter(typeof(ApparentPowerConverter))] +public readonly partial struct ApparentPower +{ + public Decimal Value { get; } + public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; + + // scalar multiplication + + public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); + public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); + public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); + + // parallel + + #if Sum + + public static T operator |(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T t) => new T(-t.Value); + + #elif Mean + + public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); + + #elif Equal + + public static T operator |(T left, T right) + { + var d = Max(Abs(left.Value), Abs(right.Value)); + + if (d == 0m) + return new T(0m); + + var relativeError = Abs(left.Value - right.Value) / d; + + const Decimal maxRelativeError = 0.05m; + + if (relativeError > maxRelativeError) + throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + + $"Difference > {maxRelativeError * 100}% detected\n" + + $"{nameof(left)} : {left}\n" + + $"{nameof(right)}: {right}"); + + return new T((left.Value + right.Value) / 2m); + } + #endif + + // compare + + public static Boolean operator ==(T left, T right) => left.Value == right.Value; + public static Boolean operator !=(T left, T right) => left.Value != right.Value; + public static Boolean operator > (T left, T right) => left.Value > right.Value; + public static Boolean operator < (T left, T right) => left.Value < right.Value; + public static Boolean operator >=(T left, T right) => left.Value >= right.Value; + public static Boolean operator <=(T left, T right) => left.Value <= right.Value; + + // conversion + + public static implicit operator T(Decimal d) => new T(d); + public static implicit operator T(Double d) => new T((Decimal)d); + public static implicit operator T(Int32 i) => new T(i); + public static implicit operator Decimal(T t) => t.Value; + + // equality + + public Boolean Equals(T other) => Value == other.Value; + public override Boolean Equals(Object? obj) => obj is T other && Equals(other); + public override Int32 GetHashCode() => Value.GetHashCode(); + +} + + +internal class ApparentPowerConverter : JsonConverter +{ + public override ApparentPower Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new ApparentPower(reader.GetDecimal()); + } + + public override void Write(Utf8JsonWriter writer, ApparentPower value, JsonSerializerOptions options) + { + var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits); + + writer.WriteNumberValue(rounded); + } +} \ No newline at end of file diff --git a/csharp/Lib/Units/Composite/Ac1Bus.cs b/csharp/Lib/Units/Composite/Ac1Bus.cs new file mode 100644 index 000000000..f2c769f24 --- /dev/null +++ b/csharp/Lib/Units/Composite/Ac1Bus.cs @@ -0,0 +1,27 @@ +using System.Diagnostics.CodeAnalysis; + +namespace InnovEnergy.Lib.Units.Composite; + +public record Ac1Bus : AcPhase +{ + public Frequency Frequency { get; init; } + + [SuppressMessage("ReSharper", "RedundantCast")] + public static Ac1Bus operator |(Ac1Bus left, Ac1Bus right) + { + var f = left.Frequency | right.Frequency; + var p = (AcPhase)left | (AcPhase)right; + + return new Ac1Bus + { + Frequency = f, + Current = p.Current, + Voltage = p.Voltage, + Phi = p.Phi + }; + } + +} + + + diff --git a/csharp/Lib/Units/Composite/Ac3Bus.cs b/csharp/Lib/Units/Composite/Ac3Bus.cs new file mode 100644 index 000000000..e35c73cfc --- /dev/null +++ b/csharp/Lib/Units/Composite/Ac3Bus.cs @@ -0,0 +1,20 @@ +using InnovEnergy.Lib.Utils; +using static DecimalMath.DecimalEx; + +namespace InnovEnergy.Lib.Units.Composite; + +public record Ac3Bus +{ + public AcPhase L1 { get; init; } + public AcPhase L2 { get; init; } + public AcPhase L3 { get; init; } + public Frequency Frequency { get; init; } + + public ApparentPower ApparentPower => L1.ApparentPower + L2.ApparentPower + L3.ApparentPower; + public ReactivePower ReactivePower => L1.ReactivePower + L2.ReactivePower + L3.ReactivePower; + public Power ActivePower => L1.ActivePower + L2.ActivePower + L3.ActivePower; + public Angle Phi => ATan2(ReactivePower, ActivePower); + + public static Ac3Bus operator |(Ac3Bus left, Ac3Bus right) => OpParallel(left, right); + private static readonly Func OpParallel = "|".CreateBinaryOpForProps(); +} \ No newline at end of file diff --git a/csharp/Lib/Units/Composite/AcPhase.cs b/csharp/Lib/Units/Composite/AcPhase.cs new file mode 100644 index 000000000..d590fbdee --- /dev/null +++ b/csharp/Lib/Units/Composite/AcPhase.cs @@ -0,0 +1,66 @@ +using static DecimalMath.DecimalEx; + +namespace InnovEnergy.Lib.Units.Composite; + + +public record AcPhase : IBus +{ + private readonly Voltage _Voltage; + public Voltage Voltage + { + get => _Voltage; + init => _Voltage = value >= 0m ? value : throw new ArgumentException("RMS value cannot be negative"); + } + + private readonly Current _Current; + public Current Current + { + get => _Current; + init => _Current = value >= 0m ? value : throw new ArgumentException("RMS value cannot be negative"); + } + + public Angle Phi { get; init; } + + public ApparentPower ApparentPower => Voltage.Value * Current.Value ; + public Power ActivePower => ApparentPower.Value * PowerFactor.Value; + public ReactivePower ReactivePower => ApparentPower.Value * Sin(Phi); + public Number PowerFactor => Cos(Phi); + + + public static AcPhase operator |(AcPhase left, AcPhase right) + { + // the Voltages of two phases are expected to be in phase and equal + + var v = left.Voltage | right.Voltage; + + // currents (RMS) can be different and out of phase + // https://www.johndcook.com/blog/2020/08/17/adding-phase-shifted-sine-waves/ + + // IF + // left(t) = ILeft sin(ωt) + // right(t) = IRight sin(ωt + φ). + // sum(t) = left(t) + right(t) = ISum sin(ωt + ψ). + + // THEN + // ψ = arctan( IRight * sin(φ) / (ILeft + IRight cos(φ)) ). + // C = IRight * sin(φ) / sin(ψ). + + // in this calculation left(t) has zero phase shift. + // we can shift both waves by -left.Phi, so + // φ := right.phi - left.phi + + + var phi = right.Phi - left.Phi; + var phiSum = ATan2(right.Current * Sin(phi), left.Current + right.Current * Cos(phi)); + var iSum = right.Current * Sin(phi) / Sin(phiSum); + + return new AcPhase + { + Voltage = v, + Current = iSum, + Phi = phiSum + }; + } + + +} \ No newline at end of file diff --git a/csharp/Lib/Units/Composite/DcBus.cs b/csharp/Lib/Units/Composite/DcBus.cs new file mode 100644 index 000000000..9bab0143a --- /dev/null +++ b/csharp/Lib/Units/Composite/DcBus.cs @@ -0,0 +1,14 @@ +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Units.Composite; + +public record DcBus : IBus +{ + public Voltage Voltage { get; init; } + public Current Current { get; init; } + + public Power Power => Current * Voltage; + + public static DcBus operator |(DcBus left, DcBus right) => OpParallel(left, right); + private static readonly Func OpParallel = "|".CreateBinaryOpForProps(); +} \ No newline at end of file diff --git a/csharp/Lib/Units/Composite/IBus.cs b/csharp/Lib/Units/Composite/IBus.cs new file mode 100644 index 000000000..8b9694972 --- /dev/null +++ b/csharp/Lib/Units/Composite/IBus.cs @@ -0,0 +1,11 @@ +using System.Diagnostics.CodeAnalysis; + +namespace InnovEnergy.Lib.Units.Composite; + +[SuppressMessage("ReSharper", "MemberCanBeProtected.Global")] + +public interface IBus +{ + public Voltage Voltage { get; } + public Current Current { get; } +} \ No newline at end of file diff --git a/csharp/Lib/Units/Current.cs b/csharp/Lib/Units/Current.cs new file mode 100644 index 000000000..b3c36ee34 --- /dev/null +++ b/csharp/Lib/Units/Current.cs @@ -0,0 +1,20 @@ +using InnovEnergy.Lib.Units.Generator; + +namespace InnovEnergy.Lib.Units; + + +[Sum] +public readonly partial struct Current +{ + public static String Unit => "A"; + public static String Symbol => "I"; + + public Current(Decimal value) => Value = value; + + // P=UI + public static Power operator *(Current current, Voltage voltage) => new Power(current.Value * voltage.Value); + + // U=RI + public static Voltage operator *(Current current, Resistance resistance) => new Voltage(resistance.Value* current.Value); + +} \ No newline at end of file diff --git a/csharp/Lib/Units/Current.generated.cs b/csharp/Lib/Units/Current.generated.cs new file mode 100644 index 000000000..c06cb3ff0 --- /dev/null +++ b/csharp/Lib/Units/Current.generated.cs @@ -0,0 +1,97 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +#define Sum + +using static System.Math; +using System.Text.Json; +using System.Text.Json.Serialization; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Units; + +using T = Current; + +[JsonConverter(typeof(CurrentConverter))] +public readonly partial struct Current +{ + public Decimal Value { get; } + public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; + + // scalar multiplication + + public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); + public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); + public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); + + // parallel + + #if Sum + + public static T operator |(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T t) => new T(-t.Value); + + #elif Mean + + public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); + + #elif Equal + + public static T operator |(T left, T right) + { + var d = Max(Abs(left.Value), Abs(right.Value)); + + if (d == 0m) + return new T(0m); + + var relativeError = Abs(left.Value - right.Value) / d; + + const Decimal maxRelativeError = 0.05m; + + if (relativeError > maxRelativeError) + throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + + $"Difference > {maxRelativeError * 100}% detected\n" + + $"{nameof(left)} : {left}\n" + + $"{nameof(right)}: {right}"); + + return new T((left.Value + right.Value) / 2m); + } + #endif + + // compare + + public static Boolean operator ==(T left, T right) => left.Value == right.Value; + public static Boolean operator !=(T left, T right) => left.Value != right.Value; + public static Boolean operator > (T left, T right) => left.Value > right.Value; + public static Boolean operator < (T left, T right) => left.Value < right.Value; + public static Boolean operator >=(T left, T right) => left.Value >= right.Value; + public static Boolean operator <=(T left, T right) => left.Value <= right.Value; + + // conversion + + public static implicit operator T(Decimal d) => new T(d); + public static implicit operator T(Double d) => new T((Decimal)d); + public static implicit operator T(Int32 i) => new T(i); + public static implicit operator Decimal(T t) => t.Value; + + // equality + + public Boolean Equals(T other) => Value == other.Value; + public override Boolean Equals(Object? obj) => obj is T other && Equals(other); + public override Int32 GetHashCode() => Value.GetHashCode(); + +} + + +internal class CurrentConverter : JsonConverter +{ + public override Current Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new Current(reader.GetDecimal()); + } + + public override void Write(Utf8JsonWriter writer, Current value, JsonSerializerOptions options) + { + var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits); + + writer.WriteNumberValue(rounded); + } +} \ No newline at end of file diff --git a/csharp/Lib/Units/Energy.cs b/csharp/Lib/Units/Energy.cs new file mode 100644 index 000000000..006568b06 --- /dev/null +++ b/csharp/Lib/Units/Energy.cs @@ -0,0 +1,17 @@ +using InnovEnergy.Lib.Time.Unix; +using InnovEnergy.Lib.Units.Generator; + + +namespace InnovEnergy.Lib.Units; + +[Sum] +public readonly partial struct Energy +{ + public static String Unit => "kWh"; + public static String Symbol => "E"; + + public Energy(Decimal value) => Value = value; + + public static Power operator /(Energy energy, TimeSpan timeSpan) => energy.Value * 1000m / (Decimal) timeSpan.TotalHours ; + public static Power operator /(Energy energy, UnixTimeSpan timeSpan) => energy.Value * 3_600_000m / timeSpan.Ticks; +} \ No newline at end of file diff --git a/csharp/Lib/Units/Energy.generated.cs b/csharp/Lib/Units/Energy.generated.cs new file mode 100644 index 000000000..90496238b --- /dev/null +++ b/csharp/Lib/Units/Energy.generated.cs @@ -0,0 +1,97 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +#define Sum + +using static System.Math; +using System.Text.Json; +using System.Text.Json.Serialization; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Units; + +using T = Energy; + +[JsonConverter(typeof(EnergyConverter))] +public readonly partial struct Energy +{ + public Decimal Value { get; } + public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; + + // scalar multiplication + + public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); + public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); + public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); + + // parallel + + #if Sum + + public static T operator |(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T t) => new T(-t.Value); + + #elif Mean + + public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); + + #elif Equal + + public static T operator |(T left, T right) + { + var d = Max(Abs(left.Value), Abs(right.Value)); + + if (d == 0m) + return new T(0m); + + var relativeError = Abs(left.Value - right.Value) / d; + + const Decimal maxRelativeError = 0.05m; + + if (relativeError > maxRelativeError) + throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + + $"Difference > {maxRelativeError * 100}% detected\n" + + $"{nameof(left)} : {left}\n" + + $"{nameof(right)}: {right}"); + + return new T((left.Value + right.Value) / 2m); + } + #endif + + // compare + + public static Boolean operator ==(T left, T right) => left.Value == right.Value; + public static Boolean operator !=(T left, T right) => left.Value != right.Value; + public static Boolean operator > (T left, T right) => left.Value > right.Value; + public static Boolean operator < (T left, T right) => left.Value < right.Value; + public static Boolean operator >=(T left, T right) => left.Value >= right.Value; + public static Boolean operator <=(T left, T right) => left.Value <= right.Value; + + // conversion + + public static implicit operator T(Decimal d) => new T(d); + public static implicit operator T(Double d) => new T((Decimal)d); + public static implicit operator T(Int32 i) => new T(i); + public static implicit operator Decimal(T t) => t.Value; + + // equality + + public Boolean Equals(T other) => Value == other.Value; + public override Boolean Equals(Object? obj) => obj is T other && Equals(other); + public override Int32 GetHashCode() => Value.GetHashCode(); + +} + + +internal class EnergyConverter : JsonConverter +{ + public override Energy Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new Energy(reader.GetDecimal()); + } + + public override void Write(Utf8JsonWriter writer, Energy value, JsonSerializerOptions options) + { + var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits); + + writer.WriteNumberValue(rounded); + } +} \ No newline at end of file diff --git a/csharp/Lib/Units/Frequency.cs b/csharp/Lib/Units/Frequency.cs new file mode 100644 index 000000000..3041325c2 --- /dev/null +++ b/csharp/Lib/Units/Frequency.cs @@ -0,0 +1,18 @@ +using InnovEnergy.Lib.Units.Generator; + +namespace InnovEnergy.Lib.Units; + +[Equal] +public readonly partial struct Frequency +{ + public static String Unit => "Hz"; + public static String Symbol => "f"; + + public Frequency(Decimal value) + { + if (value < 0) + throw new ArgumentException(nameof(Frequency) + " cannot be negative", nameof(value)); + + Value = value; + } +} \ No newline at end of file diff --git a/csharp/Lib/Units/Frequency.generated.cs b/csharp/Lib/Units/Frequency.generated.cs new file mode 100644 index 000000000..576e7b1a4 --- /dev/null +++ b/csharp/Lib/Units/Frequency.generated.cs @@ -0,0 +1,97 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +#define Equal + +using static System.Math; +using System.Text.Json; +using System.Text.Json.Serialization; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Units; + +using T = Frequency; + +[JsonConverter(typeof(FrequencyConverter))] +public readonly partial struct Frequency +{ + public Decimal Value { get; } + public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; + + // scalar multiplication + + public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); + public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); + public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); + + // parallel + + #if Sum + + public static T operator |(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T t) => new T(-t.Value); + + #elif Mean + + public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); + + #elif Equal + + public static T operator |(T left, T right) + { + var d = Max(Abs(left.Value), Abs(right.Value)); + + if (d == 0m) + return new T(0m); + + var relativeError = Abs(left.Value - right.Value) / d; + + const Decimal maxRelativeError = 0.05m; + + if (relativeError > maxRelativeError) + throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + + $"Difference > {maxRelativeError * 100}% detected\n" + + $"{nameof(left)} : {left}\n" + + $"{nameof(right)}: {right}"); + + return new T((left.Value + right.Value) / 2m); + } + #endif + + // compare + + public static Boolean operator ==(T left, T right) => left.Value == right.Value; + public static Boolean operator !=(T left, T right) => left.Value != right.Value; + public static Boolean operator > (T left, T right) => left.Value > right.Value; + public static Boolean operator < (T left, T right) => left.Value < right.Value; + public static Boolean operator >=(T left, T right) => left.Value >= right.Value; + public static Boolean operator <=(T left, T right) => left.Value <= right.Value; + + // conversion + + public static implicit operator T(Decimal d) => new T(d); + public static implicit operator T(Double d) => new T((Decimal)d); + public static implicit operator T(Int32 i) => new T(i); + public static implicit operator Decimal(T t) => t.Value; + + // equality + + public Boolean Equals(T other) => Value == other.Value; + public override Boolean Equals(Object? obj) => obj is T other && Equals(other); + public override Int32 GetHashCode() => Value.GetHashCode(); + +} + + +internal class FrequencyConverter : JsonConverter +{ + public override Frequency Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return new Frequency(reader.GetDecimal()); + } + + public override void Write(Utf8JsonWriter writer, Frequency value, JsonSerializerOptions options) + { + var rounded = value.Value.RoundToSignificantDigits(Units.JsonSignificantDigits); + + writer.WriteNumberValue(rounded); + } +} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/EqualAttribute.cs b/csharp/Lib/Units/Generator/EqualAttribute.cs new file mode 100644 index 000000000..c0131044a --- /dev/null +++ b/csharp/Lib/Units/Generator/EqualAttribute.cs @@ -0,0 +1,5 @@ +namespace InnovEnergy.Lib.Units.Generator; + +[AttributeUsage(AttributeTargets.Struct)] +internal class EqualAttribute: Attribute +{} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/MeanAttribute.cs b/csharp/Lib/Units/Generator/MeanAttribute.cs new file mode 100644 index 000000000..161da8b0e --- /dev/null +++ b/csharp/Lib/Units/Generator/MeanAttribute.cs @@ -0,0 +1,5 @@ +namespace InnovEnergy.Lib.Units.Generator; + +[AttributeUsage(AttributeTargets.Struct)] +internal class MeanAttribute: Attribute +{} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/SumAttribute.cs b/csharp/Lib/Units/Generator/SumAttribute.cs new file mode 100644 index 000000000..c4af9a156 --- /dev/null +++ b/csharp/Lib/Units/Generator/SumAttribute.cs @@ -0,0 +1,5 @@ +namespace InnovEnergy.Lib.Units.Generator; + +[AttributeUsage(AttributeTargets.Struct)] +internal class SumAttribute: Attribute +{} \ No newline at end of file diff --git a/csharp/Lib/Units/Generator/Template.txt b/csharp/Lib/Units/Generator/Template.txt new file mode 100644 index 000000000..38e30aa04 --- /dev/null +++ b/csharp/Lib/Units/Generator/Template.txt @@ -0,0 +1,97 @@ +#nullable enable // Auto-generated code requires an explicit '#nullable' directive in source. +#define AggregationType + +using static System.Math; +using System.Text.Json; +using System.Text.Json.Serialization; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.Lib.Units; + +using T = Template; + +[JsonConverter(typeof(TemplateConverter))] +public readonly partial struct Template +{ + public Decimal Value { get; } + public override String ToString() => Value.RoundToSignificantDigits(Units.DisplaySignificantDigits) + Unit; + + // scalar multiplication + + public static T operator *(Decimal scalar, T t) => new T(scalar * t.Value); + public static T operator *(T t, Decimal scalar) => new T(scalar * t.Value); + public static T operator /(T t, Decimal scalar) => new T(t.Value / scalar); + + // parallel + + #if Sum + + public static T operator |(T left, T right) => new T(left.Value + right.Value); + public static T operator -(T t) => new T(-t.Value); + + #elif Mean + + public static T operator |(T left, T right) => new T((left.Value + right.Value)/2m); + + #elif Equal + + public static T operator |(T left, T right) + { + var d = Max(Abs(left.Value), Abs(right.Value)); + + if (d == 0m) + return new T(0m); + + var relativeError = Abs(left.Value - right.Value) / d; + + const Decimal maxRelativeError = 0.05m; + + if (relativeError > maxRelativeError) + throw new Exception($"{nameof(left)} and {nameof(right)} must be approximately equal.\n" + + $"Difference > {maxRelativeError * 100}% detected\n" + + $"{nameof(left)} : {left}\n" + + $"{nameof(right)}: {right}"); + + return new T((left.Value + right.Value) / 2m); + } + #endif + + // compare + + public static Boolean operator ==(T left, T right) => left.Value == right.Value; + public static Boolean operator !=(T left, T right) => left.Value != right.Value; + public static Boolean operator > (T left, T right) => left.Value > right.Value; + public static Boolean operator < (T left, T right) => left.Value < right.Value; + public static Boolean operator >=(T left, T right) => left.Value >= right.Value; + public static Boolean operator <=(T left, T right) => left.Value <= right.Value; + + // conversion + + public static implicit operator T(Decimal d) => new T(d); + public static implicit operator T(Double d) => new T((Decimal)d); + public static implicit operator T(Int32 i) => new T(i); + public static implicit operator Decimal(T t) => t.Value; + + // equality + + public Boolean Equals(T other) => Value == other.Value; + public override Boolean Equals(Object? obj) => obj is T other && Equals(other); + public override Int32 GetHashCode() => Value.GetHashCode(); + +} + + +internal class TemplateConverter : JsonConverter