using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Web; using CliWrap; using CliWrap.Buffered; using InnovEnergy.App.RemoteSupportConsole; using InnovEnergy.Lib.Utils; using InnovEnergy.Lib.Victron.VictronVRM; using SQLite; using static System.Text.Json.JsonSerializer; using Installation = InnovEnergy.App.VrmGrabber.DataTypes.Installation; using VrmInstallation = InnovEnergy.Lib.Victron.VictronVRM.Installation; using FILE=System.IO.File; namespace InnovEnergy.App.VrmGrabber.Database; public class InstallationDetails { public InstallationDetails(String? ip, IReadOnlyList details) { Details = details; Ip = ip; } public IReadOnlyList? Details { get; set; } public String? Ip { get; set; } } public static partial class Db { public static Dictionary InstallationsAndDetails; internal const String DbPath = "./db.sqlite"; private static SQLiteConnection Connection { get; } = new SQLiteConnection(DbPath); public static TableQuery Installations => Connection.Table(); public static void Init() { // used to force static constructor } static Db() { // on startup create/migrate tables Connection.RunInTransaction(() => { Connection.CreateTable(); }); } public static async Task UpdateDetailsAndInstallations() { while (true) { await UpdateInstallationsAndDetailsFromVrm(0); } } [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "")] private static async Task UpdateInstallationsAndDetailsFromVrm(Int32 _) { var fileContent = await File.ReadAllTextAsync("./token.json"); var acc = Deserialize(fileContent); var user = VrmAccount.Token(acc!.idUser, acc.token); var installations = await user.GetInstallations(); // var returnDictionary = new Dictionary(); foreach (var installation in installations.Take(70)) //TODO REMOVE TAKE { Console.WriteLine(installation.Name); var details = await GetInstallationDetails(installation); var ip = Ip(details); var updatedInstallation = new Installation( installation.Name, ip[0], (Int64)installation.IdSite, installation.Identifier, details.Details.MachineSerial() ?? "Unknown", HttpUtility.UrlEncode(installation.Name), ip[1], details.Details.Last().Json["timestamp"].ToString(), Serialize(details.Details), await NumberOfBatteries(ip[0]), await BatteryFirmwareVersion(ip[0])); if (GetInstallationByIdentifier(installation.Identifier) == null) { Create(updatedInstallation); } else { Update(updatedInstallation); } } } private static async Task BatteryFirmwareVersion(String? ip) { if (ip is null or "Unknown") return "Unknown"; var pathToBattery = await ExecuteBufferedAsyncCommandOnIp(ip, "dbus-send --system --dest=com.victronenergy.system --type=method_call --print-reply /ServiceMapping/com_victronenergy_battery_1 com.victronenergy.BusItem.GetText"); var command = $"dbus-send --system --dest={pathToBattery} --type=method_call --print-reply /FirmwareVersion com.victronenergy.BusItem.GetText"; return await ExecuteBufferedAsyncCommandOnIp(ip, command); //todo fill me } private static async Task NumberOfBatteries(String? ip) { if (ip is null or "Unknown") return 0; var pathToBattery = await ExecuteBufferedAsyncCommandOnIp(ip, "dbus-send --system --dest=com.victronenergy.system --type=method_call --print-reply /ServiceMapping/com_victronenergy_battery_1 com.victronenergy.BusItem.GetText"); var cmd = await ExecuteBufferedAsyncCommandOnIp(ip,$"dbus-send --system --dest={pathToBattery} --type=method_call --print-reply /NbOfBatteries com.victronenergy.BusItem.GetText" ); return cmd == "Unknown" ? 0 : Int64.Parse(cmd); //No Batteries can be found } private static async Task ExecuteBufferedAsyncCommandOnIp(String? ip, String command) { if (ip is null or "Unknown") return "Unknown"; var cmd = await Cli.Wrap("ssh") .WithArguments($@"root@{ip}") .AppendArgument("-o StrictHostKeyChecking=accept-new") .AppendArgument(command) .WithValidation(CommandResultValidation.None).ExecuteBufferedAsync(); return cmd.StandardOutput.Split('"')[1]; } private static String?[] Ip(InstallationDetails details) { var online = "❌"; var lookup = details.Ip; if (lookup == "Unknown") { var serial = details.Details.MachineSerial() ?? "Unknown"; if (serial != "Unknown" && FILE.Exists($@"/etc/openvpn/server/Salino/ccd/{serial}")) lookup = FILE.ReadAllText($@"/etc/openvpn/server/Salino/ccd/{serial}").Split(' ')[1]; } else { online = "✅"; } return new[] { lookup, online }; } private static async Task GetInstallationDetails(VrmInstallation i) { await Task.Delay(1000); try { var details = await i.GetDetails(); var ip = await VpnInfo.LookUpIp(i.Identifier, details.MachineSerial()) ?? "Unknown"; if (ip != "Unknown") await UpdateInstallationName(i, ip); return new InstallationDetails(ip, details); } catch (Exception e) { Console.WriteLine(e); } return new InstallationDetails("Unknown", Array.Empty()); } private static async Task UpdateInstallationName(VrmInstallation installation, String? ip) { var oldNameInFileRequest = await Cli.Wrap("ssh") .WithArguments($@"root@{ip}") .AppendArgument("-o StrictHostKeyChecking=accept-new") .AppendArgument("cat /data/innovenergy/openvpn/installation-name") .WithValidation(CommandResultValidation.None).ExecuteBufferedAsync(); var oldNameInFileWithoutNewLine = oldNameInFileRequest.StandardOutput.TrimEnd(); if (oldNameInFileRequest.ExitCode == 0 && oldNameInFileWithoutNewLine != installation.Name) { var overwriteNameCommand = Cli.Wrap("ssh") .WithArguments($@"root@{ip}") .AppendArgument($"echo '{installation.Name}' > /data/innovenergy/openvpn/installation-name"); var overwriteNameResponse = await overwriteNameCommand .WithValidation(CommandResultValidation.None).ExecuteBufferedAsync(); if (overwriteNameResponse.ExitCode != 0) { Console.WriteLine(overwriteNameResponse.StandardError); Console.WriteLine("Renaming did not work"); } else { var rebootAfterRename = await Cli.Wrap("ssh") .WithArguments($@"root@{ip}") .AppendArgument("reboot") .WithValidation(CommandResultValidation.None).ExecuteBufferedAsync(); if (rebootAfterRename.ExitCode != 0) { Console.WriteLine(overwriteNameResponse.StandardError); Console.WriteLine("Rebooting did not work"); } else { Console.WriteLine($"Renamed and rebooted {installation.Name}"); } } } } private static Boolean RunTransaction(Func func) { var savepoint = Connection.SaveTransactionPoint(); var success = false; try { success = func(); } finally { if (success) Connection.Release(savepoint); else Connection.RollbackTo(savepoint); } return success; } } public class AccToken { public UInt64 idUser { get; init;} public String token { get; init;} } /* dbus-send --system --dest=com.victronenergy.battery.ttyUSB1 --print-reply /FirmwareVersion \ org.freedesktop.DBus.Properties.Get string:com.victronenergy.battery.ttyUSB1 * * */