From 977eb3758b279719373ef53e40db42bd191654d9 Mon Sep 17 00:00:00 2001 From: Mike Carr Date: Thu, 26 Dec 2024 08:23:40 -0800 Subject: [PATCH 1/4] add fixes for android --- .../Helpers/AndroidFileHelper.cs | 77 ++++++++++++++++++ OpenIPC_Config.Android/MainActivity.cs | 7 +- .../OpenIPC_Config.Android.csproj | 11 +++ OpenIPC_Config/App.axaml.cs | 21 +++++ OpenIPC_Config/Assets/Resources.Designer.cs | 5 ++ OpenIPC_Config/Assets/Resources.resx | 3 + OpenIPC_Config/Models/OpenIPC.cs | 36 +++++++- OpenIPC_Config/Services/NetworkHelper.cs | 42 ++++++++++ .../ViewModels/SetupTabViewModel.cs | 19 +++-- .../ViewModels/TelemetryTabViewModel.cs | 4 + .../Views/ConnectControlsView.axaml | 3 + OpenIPC_Config/Views/MainView.axaml | 2 + OpenIPC_Config/Views/MainWindow.axaml | 3 +- .../Views/NetworkIPScannerView.axaml | 11 +++ .../Views/SetupCameraButtonsView.axaml | 1 + OpenIPC_Config/binaries/msposd | Bin 126544 -> 126544 bytes README-Android.md | 31 +++++++ 17 files changed, 265 insertions(+), 11 deletions(-) create mode 100644 OpenIPC_Config.Android/Helpers/AndroidFileHelper.cs create mode 100644 OpenIPC_Config/Services/NetworkHelper.cs create mode 100644 README-Android.md diff --git a/OpenIPC_Config.Android/Helpers/AndroidFileHelper.cs b/OpenIPC_Config.Android/Helpers/AndroidFileHelper.cs new file mode 100644 index 0000000..2c86620 --- /dev/null +++ b/OpenIPC_Config.Android/Helpers/AndroidFileHelper.cs @@ -0,0 +1,77 @@ +using System; +using System.IO; +using Android.Content; +using Android.Content.Res; +using Android.Util; + +namespace OpenIPC_Config.Android.Helpers; + +public class AndroidFileHelper +{ + public static string ReadAssetFile(string relativePath) + { + // var assets = Android.App.Application.Context.Assets; + var assets = global::Android.App.Application.Context.Assets; + + using (var stream = assets.Open(relativePath)) + using (var reader = new StreamReader(stream)) + { + return reader.ReadToEnd(); + } + } + + public static string[] ListAssetFiles(string folderPath) + { + var assets = global::Android.App.Application.Context.Assets; + return assets.List(folderPath); // Lists all files in the folder + } + + public static void CopyAssetsToInternalStorage(Context context) + { + AssetManager assets = context.Assets; + string internalStoragePath = context.FilesDir.AbsolutePath; // Internal storage path + + // Start recursive copy from the root "binaries" folder + CopyFolder(assets, "binaries", internalStoragePath); + } + + private static void CopyFolder(AssetManager assets, string sourceFolder, string destinationFolder) + { + try + { + // Ensure the destination folder exists + Directory.CreateDirectory(destinationFolder); + + // List all items (files and folders) in the source folder + string[] items = assets.List(sourceFolder); + + foreach (string item in items) + { + string sourcePath = string.IsNullOrEmpty(sourceFolder) ? item : $"{sourceFolder}/{item}"; + string destinationPath = Path.Combine(destinationFolder, item); + + // Check if the item is a directory or file + if (assets.List(sourcePath).Length > 0) + { + // If it's a folder, recursively copy its contents + CopyFolder(assets, sourcePath, destinationPath); + } + else + { + // If it's a file, copy it + using (Stream input = assets.Open(sourcePath)) + using (FileStream output = new FileStream(destinationPath, FileMode.Create)) + { + input.CopyTo(output); + } + + Log.Debug("FileHelper", $"Copied file: {sourcePath} to {destinationPath}"); + } + } + } + catch (Exception ex) + { + Log.Error("FileHelper", $"Error copying assets: {ex.Message}"); + } + } +} \ No newline at end of file diff --git a/OpenIPC_Config.Android/MainActivity.cs b/OpenIPC_Config.Android/MainActivity.cs index 00ceef9..45a70b4 100644 --- a/OpenIPC_Config.Android/MainActivity.cs +++ b/OpenIPC_Config.Android/MainActivity.cs @@ -1,7 +1,9 @@ -using Android.App; +using System; +using Android.App; using Android.Content.PM; using Avalonia; using Avalonia.Android; +using OpenIPC_Config.Android.Helpers; namespace OpenIPC_Config.Android; @@ -15,6 +17,9 @@ public class MainActivity : AvaloniaMainActivity { protected override AppBuilder CustomizeAppBuilder(AppBuilder builder) { + + AndroidFileHelper.CopyAssetsToInternalStorage(global::Android.App.Application.Context); + return base.CustomizeAppBuilder(builder) .WithInterFont(); } diff --git a/OpenIPC_Config.Android/OpenIPC_Config.Android.csproj b/OpenIPC_Config.Android/OpenIPC_Config.Android.csproj index 16be333..8aadbd5 100644 --- a/OpenIPC_Config.Android/OpenIPC_Config.Android.csproj +++ b/OpenIPC_Config.Android/OpenIPC_Config.Android.csproj @@ -13,12 +13,23 @@ OpenIPC_Config.Android + + $(DefineConstants);ANDROID + + Resources\drawable\Icon.png + + + assets\binaries\%(RecursiveDir)%(Filename)%(Extension) + + + + diff --git a/OpenIPC_Config/App.axaml.cs b/OpenIPC_Config/App.axaml.cs index 8dd90eb..46e70b4 100644 --- a/OpenIPC_Config/App.axaml.cs +++ b/OpenIPC_Config/App.axaml.cs @@ -24,10 +24,31 @@ namespace OpenIPC_Config; public class App : Application { public static IServiceProvider ServiceProvider { get; private set; } + + public static string OSType { get; private set; } + private void DetectOsType() + { + // Detect OS Type + if (OperatingSystem.IsAndroid() || OperatingSystem.IsIOS()) + { + OSType = "Mobile"; + } + else if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + OSType = "Desktop"; + } + else + { + OSType = "Unknown"; + } + } public override void Initialize() { AvaloniaXamlLoader.Load(this); + + DetectOsType(); + } public override void OnFrameworkInitializationCompleted() diff --git a/OpenIPC_Config/Assets/Resources.Designer.cs b/OpenIPC_Config/Assets/Resources.Designer.cs index 46695c4..1ab8d1c 100644 --- a/OpenIPC_Config/Assets/Resources.Designer.cs +++ b/OpenIPC_Config/Assets/Resources.Designer.cs @@ -227,6 +227,11 @@ public static string UpdateCameraToolTip get { return ResourceManager.GetString("UpdateCameraToolTip", resourceCulture); } } + public static string LocalIpToolTip + { + get { return ResourceManager.GetString("LocalIpToolTip", resourceCulture); } + } + } } diff --git a/OpenIPC_Config/Assets/Resources.resx b/OpenIPC_Config/Assets/Resources.resx index 1838f45..d1e32bd 100644 --- a/OpenIPC_Config/Assets/Resources.resx +++ b/OpenIPC_Config/Assets/Resources.resx @@ -181,4 +181,7 @@ If you set 90fps GOP: 1 , real gop is 90 and if you set value to 9 the correctio Updates Camera's firmware to selected firmware + + IP of the device running the App + \ No newline at end of file diff --git a/OpenIPC_Config/Models/OpenIPC.cs b/OpenIPC_Config/Models/OpenIPC.cs index b634bcf..1f91c57 100644 --- a/OpenIPC_Config/Models/OpenIPC.cs +++ b/OpenIPC_Config/Models/OpenIPC.cs @@ -1,6 +1,7 @@ using System; using System.IO; using System.Reflection; +using System.Runtime.InteropServices; namespace OpenIPC_Config.Models; @@ -38,9 +39,8 @@ public enum FileType public const string RemoteGsKeyPath = "/etc/gs.key"; public const string RemoteTempFolder = "/tmp"; - public const string DroneKeyPath = "binaries/drone.key"; - public const string GsKeyPath = "binaries/gs.key"; - + public static string DroneKeyPath; + public static string GsKeyPath; public static string LocalFirmwareFolder; public static string LocalBackUpFolder; @@ -77,6 +77,17 @@ public static string GetBinariesPath() basePath = AppContext.BaseDirectory; return Path.Combine(basePath, "binaries"); } + + else if (OperatingSystem.IsAndroid() ) + { + basePath = AppContext.BaseDirectory; + return basePath; + } + else if (OperatingSystem.IsIOS()) + { + basePath = AppContext.BaseDirectory; + return Path.Combine(basePath, "binaries"); + } throw new PlatformNotSupportedException("Unsupported platform"); } @@ -85,6 +96,8 @@ private static void InitializePaths() { var appName = Assembly.GetExecutingAssembly().GetName().Name; + + if (OperatingSystem.IsAndroid()) { // Android-specific path @@ -94,6 +107,9 @@ private static void InitializePaths() DeviceSettingsConfigPath = Path.Combine(AppDataConfigDirectory, "openipc_config.json"); LocalTempFolder = Path.Combine(AppDataConfigDirectory, "Temp"); LocalFirmwareFolder = Path.Combine(AppDataConfigDirectory, "firmware"); + + DroneKeyPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "drone.key"); + GsKeyPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "gs.key"); } else if (OperatingSystem.IsIOS()) { @@ -105,17 +121,24 @@ private static void InitializePaths() LocalTempFolder = Path.Combine(AppDataConfigDirectory, "Temp"); LocalFirmwareFolder = Path.Combine(AppDataConfigDirectory, "firmware"); LocalBackUpFolder = Path.Combine(AppDataConfigDirectory, "backup"); + + DroneKeyPath = Path.Combine(AppDataConfigDirectory, "binaries/drone.key"); + GsKeyPath = Path.Combine(AppDataConfigDirectory, "binaries/gs.key"); } else if (OperatingSystem.IsWindows()) { AppDataConfigDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), appName); + AppDataConfigPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), appName, "appsettings.json"); DeviceSettingsConfigPath = Path.Combine(AppDataConfigDirectory, "openipc_config.json"); LocalTempFolder = Path.Combine(AppDataConfigDirectory, "Temp"); LocalFirmwareFolder = Path.Combine(AppDataConfigDirectory, "firmware"); LocalBackUpFolder = Path.Combine(AppDataConfigDirectory, "backup"); + + DroneKeyPath = Path.Combine(AppDataConfigDirectory, "binaries/drone.key"); + GsKeyPath = Path.Combine(AppDataConfigDirectory, "binaries/gs.key"); } else if (OperatingSystem.IsMacOS()) { @@ -127,6 +150,9 @@ private static void InitializePaths() LocalTempFolder = Path.Combine(AppDataConfigDirectory, "Temp"); LocalFirmwareFolder = Path.Combine(AppDataConfigDirectory, "firmware"); LocalBackUpFolder = Path.Combine(AppDataConfigDirectory, "backup"); + + DroneKeyPath = Path.Combine(AppDataConfigDirectory, "binaries/drone.key"); + GsKeyPath = Path.Combine(AppDataConfigDirectory, "binaries/gs.key"); } else // Assume Linux { @@ -136,8 +162,12 @@ private static void InitializePaths() LocalTempFolder = Path.Combine(AppDataConfigDirectory, "Temp"); LocalFirmwareFolder = Path.Combine(AppDataConfigDirectory, "firmware"); LocalBackUpFolder = Path.Combine(AppDataConfigDirectory, "backup"); + + DroneKeyPath = Path.Combine(AppDataConfigDirectory, "binaries/drone.key"); + GsKeyPath = Path.Combine(AppDataConfigDirectory, "binaries/gs.key"); } + // Ensure the config directory exists Directory.CreateDirectory(AppDataConfigDirectory); diff --git a/OpenIPC_Config/Services/NetworkHelper.cs b/OpenIPC_Config/Services/NetworkHelper.cs new file mode 100644 index 0000000..d5160bc --- /dev/null +++ b/OpenIPC_Config/Services/NetworkHelper.cs @@ -0,0 +1,42 @@ +using System; +using System.Net; +using System.Net.NetworkInformation; +using System.Net.Sockets; + +namespace OpenIPC_Config.Services; + +public class NetworkHelper +{ + public static string GetLocalIPAddress() + { + try + { + var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces(); + foreach (var netInterface in networkInterfaces) + { + // Ignore loopback and inactive interfaces + if (netInterface.OperationalStatus != OperationalStatus.Up || + netInterface.NetworkInterfaceType == NetworkInterfaceType.Loopback) + { + continue; + } + + var properties = netInterface.GetIPProperties(); + foreach (var address in properties.UnicastAddresses) + { + if (address.Address.AddressFamily == AddressFamily.InterNetwork) // IPv4 + { + return address.Address.ToString(); + } + } + } + + throw new Exception("No valid network interfaces found."); + } + catch (Exception ex) + { + Console.WriteLine($"Error: {ex.Message}"); + return null; + } + } +} \ No newline at end of file diff --git a/OpenIPC_Config/ViewModels/SetupTabViewModel.cs b/OpenIPC_Config/ViewModels/SetupTabViewModel.cs index 297aa02..685e902 100644 --- a/OpenIPC_Config/ViewModels/SetupTabViewModel.cs +++ b/OpenIPC_Config/ViewModels/SetupTabViewModel.cs @@ -25,13 +25,14 @@ namespace OpenIPC_Config.ViewModels; public partial class SetupTabViewModel : ViewModelBase { - + public bool IsMobile => App.OSType == "Mobile"; + public bool IsEnabledForView => CanConnect && !IsMobile; + [ObservableProperty] private bool _canConnect; [ObservableProperty] private int _downloadProgress; [ObservableProperty] private string _chkSumStatusColor; - [ObservableProperty] public ObservableCollection _firmwareVersions; [ObservableProperty] private bool _isCamera; @@ -46,6 +47,7 @@ public partial class SetupTabViewModel : ViewModelBase [ObservableProperty] private string _scanMessages; [ObservableProperty] private string _selectedFwVersion; [ObservableProperty] private string _selectedSensor; + [ObservableProperty] private string _localIp; private ICommand _firmwareUpdateCommand; private ICommand _generateKeysCommand; @@ -76,6 +78,8 @@ public SetupTabViewModel(ILogger logger, ChkSumStatusColor = "Green"; + LocalIp = "Device IP: " + NetworkHelper.GetLocalIPAddress(); + ScanIpLabel = "192.168.1."; ShowProgressBarCommand = new RelayCommand(() => IsProgressBarVisible = true); @@ -166,7 +170,9 @@ private void OnAppMessage(AppMessage appMessage) private void InitializeCollections() { // load sensor files from local folder - var directoryPath = Path.Combine(OpenIPC.GetBinariesPath(), "sensors"); + + var binariesPath = OpenIPC.GetBinariesPath(); + var directoryPath = Path.Combine(binariesPath, "sensors"); //var directoryPath = OpenIPC.LocalSensorsFolder; PopulateSensorFileNames(directoryPath); @@ -605,7 +611,8 @@ private async void RecvDroneKey() { Log.Debug("RecvDroneKeyCommand executed"); - if (File.Exists("drone.key")) + var droneKeyPath = Path.Combine(OpenIPC.AppDataConfigDirectory, "drone.key"); + if (File.Exists(droneKeyPath)) { Log.Debug("drone.key already exists locally, do you want to overwrite it?"); var msBox = MessageBoxManager.GetMessageBoxStandard("File exists!", @@ -621,8 +628,8 @@ private async void RecvDroneKey() await SshClientService.DownloadFileLocalAsync(DeviceConfig.Instance, Models.OpenIPC.RemoteEtcFolder + "/drone.key", - "drone.key"); - if (!File.Exists("drone.key")) Log.Debug("RecvDroneKeyCommand failed"); + droneKeyPath); + if (!File.Exists(droneKeyPath)) Log.Debug("RecvDroneKeyCommand failed"); Log.Debug("RecvDroneKeyCommand executed...done"); } diff --git a/OpenIPC_Config/ViewModels/TelemetryTabViewModel.cs b/OpenIPC_Config/ViewModels/TelemetryTabViewModel.cs index c9f402f..71a23ea 100644 --- a/OpenIPC_Config/ViewModels/TelemetryTabViewModel.cs +++ b/OpenIPC_Config/ViewModels/TelemetryTabViewModel.cs @@ -34,6 +34,10 @@ public partial class TelemetryTabViewModel : ViewModelBase [ObservableProperty] private string _telemetryContent; #endregion + public bool IsMobile => App.OSType == "Mobile"; + public bool IsEnabledForView => CanConnect && !IsMobile; + + #region Collections public ObservableCollection SerialPorts { get; private set; } public ObservableCollection BaudRates { get; private set; } diff --git a/OpenIPC_Config/Views/ConnectControlsView.axaml b/OpenIPC_Config/Views/ConnectControlsView.axaml index 78a3a63..9d7519e 100644 --- a/OpenIPC_Config/Views/ConnectControlsView.axaml +++ b/OpenIPC_Config/Views/ConnectControlsView.axaml @@ -11,6 +11,8 @@ + + @@ -77,4 +79,5 @@ Command="{Binding ConnectCommand}" /> + diff --git a/OpenIPC_Config/Views/MainView.axaml b/OpenIPC_Config/Views/MainView.axaml index 854237c..78a8742 100644 --- a/OpenIPC_Config/Views/MainView.axaml +++ b/OpenIPC_Config/Views/MainView.axaml @@ -13,6 +13,7 @@ + @@ -85,4 +86,5 @@ + diff --git a/OpenIPC_Config/Views/MainWindow.axaml b/OpenIPC_Config/Views/MainWindow.axaml index b6b29af..956a60b 100644 --- a/OpenIPC_Config/Views/MainWindow.axaml +++ b/OpenIPC_Config/Views/MainWindow.axaml @@ -3,7 +3,8 @@ xmlns:views="clr-namespace:OpenIPC_Config.Views" x:Class="OpenIPC_Config.Views.MainWindow" Title="OpenIPC FPV Config Tool" - Width="900" Height="850" MinWidth="600" MinHeight="800"> + > + diff --git a/OpenIPC_Config/Views/NetworkIPScannerView.axaml b/OpenIPC_Config/Views/NetworkIPScannerView.axaml index 87bd579..9493fe6 100644 --- a/OpenIPC_Config/Views/NetworkIPScannerView.axaml +++ b/OpenIPC_Config/Views/NetworkIPScannerView.axaml @@ -15,6 +15,7 @@ + @@ -44,6 +45,8 @@ Do not touch anything until finished