diff --git a/source/Meadow.Core/Bases/App.cs b/source/Meadow.Core/Bases/AppBase.cs similarity index 71% rename from source/Meadow.Core/Bases/App.cs rename to source/Meadow.Core/Bases/AppBase.cs index 208220a3..47bc2e0b 100644 --- a/source/Meadow.Core/Bases/App.cs +++ b/source/Meadow.Core/Bases/AppBase.cs @@ -10,8 +10,7 @@ /// class for Meadow applications to get strongly-typed access to the current /// device information. /// -public abstract class App : IApp, IAsyncDisposable - where D : class, IMeadowDevice +public abstract class AppBase : IApp { private ExecutionContext executionContext; @@ -24,11 +23,10 @@ public abstract class App : IApp, IAsyncDisposable /// /// Base constructor for the App class /// - protected App() + protected AppBase() { executionContext = Thread.CurrentThread.ExecutionContext; - Device = MeadowOS.CurrentDevice as D ?? throw new ArgumentException($"Current device is not {typeof(D).Name}"); // 'D' is guaranteed to be initialized and the same type Abort = MeadowOS.AppAbort.Token; Resolver.Services.Add(this); @@ -39,20 +37,9 @@ protected App() /// /// The action to call /// An optional state object to pass to the Action - public void InvokeOnMainThread(Action action, object? state = null) + public virtual void InvokeOnMainThread(Action action, object? state = null) { - switch (Device.Information.Platform) - { - // ExecutionContext in Mono on the F7 isn't fully working - but we also don't worry about a MainThread there either - case Hardware.MeadowPlatform.F7FeatherV1: - case Hardware.MeadowPlatform.F7FeatherV2: - case Hardware.MeadowPlatform.F7CoreComputeV2: - action.Invoke(state); - break; - default: - ExecutionContext.Run(executionContext, new ContextCallback(action), state); - break; - } + ExecutionContext.Run(executionContext, new ContextCallback(action), state); } /// @@ -96,11 +83,6 @@ public void OnUpdateComplete(Version oldVersion, out bool rollbackUpdate) rollbackUpdate = false; } - /// - /// The root Device interface - /// - public static D Device { get; protected set; } = default!; - /// /// The app cancellation token /// @@ -110,4 +92,4 @@ public void OnUpdateComplete(Version oldVersion, out bool rollbackUpdate) /// Virtual method provided for App implementations to clean up resources on Disposal /// public virtual ValueTask DisposeAsync() { return new ValueTask(Task.CompletedTask); } -} \ No newline at end of file +} diff --git a/source/Meadow.Core/Bases/App_D.cs b/source/Meadow.Core/Bases/App_D.cs new file mode 100644 index 00000000..c2442a19 --- /dev/null +++ b/source/Meadow.Core/Bases/App_D.cs @@ -0,0 +1,43 @@ +namespace Meadow; + +using System; + + +/// +/// Provides a base implementation for the Meadow App. Use this +/// class for Meadow applications to get strongly-typed access to the current +/// device information. +/// +public abstract class App : AppBase + where D : class, IMeadowDevice +{ + /// + /// The root Device interface + /// + public static D Device { get; protected set; } = default!; + + /// + /// Base constructor for the App class + /// + public App() + { + Device = MeadowOS.CurrentDevice as D ?? throw new ArgumentException($"Current device is not {typeof(D).Name}"); // 'D' is guaranteed to be initialized and the same type + } + + /// + public override void InvokeOnMainThread(Action action, object? state = null) + { + switch (Device.Information.Platform) + { + // ExecutionContext in Mono on the F7 isn't fully working - but we also don't worry about a MainThread there either + case Hardware.MeadowPlatform.F7FeatherV1: + case Hardware.MeadowPlatform.F7FeatherV2: + case Hardware.MeadowPlatform.F7CoreComputeV2: + action.Invoke(state); + break; + default: + base.InvokeOnMainThread(action, state); + break; + } + } +} \ No newline at end of file diff --git a/source/Meadow.Core/Bases/App_D_P_H.cs b/source/Meadow.Core/Bases/App_D_P_H.cs new file mode 100644 index 00000000..dcff3cee --- /dev/null +++ b/source/Meadow.Core/Bases/App_D_P_H.cs @@ -0,0 +1,20 @@ +namespace Meadow; + +/// +/// Provides a base implementation for the Meadow App. Use this +/// class for Meadow applications to get strongly-typed access to the current +/// device information. +/// +/// The type of the IMeadowDevice this app targets +/// The type of the IMeadowAppEmbeddedHardwareProvider to create +/// The type of the IMeadowAppEmbeddedHardware the Provider will return +public abstract class App : AppBase + where D : class, IMeadowDevice + where P : IMeadowAppEmbeddedHardwareProvider + where H : IMeadowAppEmbeddedHardware +{ + /// + /// The instance if the IMeadowAppEmbeddedHardware on which the stack is running + /// + public static H Hardware { get; internal set; } = default!; +} diff --git a/source/Meadow.Core/MeadowOS.cs b/source/Meadow.Core/MeadowOS.cs index 9e887180..1e16b537 100644 --- a/source/Meadow.Core/MeadowOS.cs +++ b/source/Meadow.Core/MeadowOS.cs @@ -355,7 +355,7 @@ private static MeadowPlatform DetectPlatform() return MeadowPlatform.Unknown; } - private static Type FindDeviceTypeParameter(Type type) + private static Type FindDeviceTypeParameter_old(Type type) { if (type.IsGenericType) { @@ -363,10 +363,43 @@ private static Type FindDeviceTypeParameter(Type type) if (dt != null) return dt; } + return FindDeviceTypeParameter_old(type.BaseType); + } + + private static (Type DeviceType, Type? HardwareProviderType) FindDeviceTypeParameter(Type type) + { + Type? deviceType = null; + Type? hardwareProviderType = null; + + if (type.IsGenericType) + { + var genericArgs = type.GetGenericArguments(); + + Resolver.Log.Info($"{type.Name} generic args:"); + foreach (var arg in genericArgs) + { + Resolver.Log.Info(arg.Name); + if (typeof(IMeadowDevice).IsAssignableFrom(arg)) + { + deviceType = arg; + } + //else if (typeof(IMeadowAppEmbeddedHardwareProvider).IsAssignableFrom(arg)) + else if (arg.GetInterfaces().Any(i => i.Name.StartsWith("IMeadowAppEmbeddedHardwareProvider"))) + { + hardwareProviderType = arg; + } + } + } + + if (deviceType != null) + { + return (deviceType, hardwareProviderType); + } + return FindDeviceTypeParameter(type.BaseType); } - private static (Type appType, Type deviceType)? FindAppForPlatform(MeadowPlatform platform) + private static (Type appType, Type deviceType, Type? hardwareProviderType)? FindAppForPlatform(MeadowPlatform platform) { var allApps = FindAppType(null); @@ -375,26 +408,32 @@ private static (Type appType, Type deviceType)? FindAppForPlatform(MeadowPlatfor throw new Exception("Cannot find an IApp implementation"); } + Resolver.Log.Info("IApps found:"); + foreach (var app in allApps) + { + Resolver.Log.Info(app.Name); + } + // find an IApp that matches our target platform switch (platform) { case MeadowPlatform.Windows: // look for Desktop or Windows // (wish C# supported static properties on an interface, they type could then tell what platforms it supports) - (Type, Type)? windowsTypeTuple = null; + (Type, Type, Type?)? windowsTypeTuple = null; foreach (var app in allApps) { var devicetype = FindDeviceTypeParameter(app); - if (devicetype.FullName == "Meadow.Desktop") + if (devicetype.DeviceType.FullName == "Meadow.Desktop") { - return (app, devicetype); + return (app, devicetype.DeviceType, devicetype.HardwareProviderType); } - else if (devicetype.FullName == "Meadow.Windows") + else if (devicetype.DeviceType.FullName == "Meadow.Windows") { // keep a ref in case Desktop isn't found - windowsTypeTuple = (app, devicetype); + windowsTypeTuple = (app, devicetype.DeviceType, devicetype.HardwareProviderType); } } @@ -405,20 +444,20 @@ private static (Type appType, Type deviceType)? FindAppForPlatform(MeadowPlatfor throw new Exception("Cannot find an IApp that targets Desktop or Windows"); case MeadowPlatform.OSX: - (Type, Type)? macTypeTuple = null; + (Type, Type, Type?)? macTypeTuple = null; foreach (var app in allApps) { var devicetype = FindDeviceTypeParameter(app); - if (devicetype.FullName == "Meadow.Desktop") + if (devicetype.DeviceType.FullName == "Meadow.Desktop") { - return (app, devicetype); + return (app, devicetype.DeviceType, devicetype.HardwareProviderType); } - else if (devicetype.FullName == "Meadow.Mac") + else if (devicetype.DeviceType.FullName == "Meadow.Mac") { // keep a ref in case Desktop isn't found - macTypeTuple = (app, devicetype); + macTypeTuple = (app, devicetype.DeviceType, devicetype.HardwareProviderType); } } @@ -429,20 +468,20 @@ private static (Type appType, Type deviceType)? FindAppForPlatform(MeadowPlatfor throw new Exception("Cannot find an IApp that targets Desktop or Mac"); case MeadowPlatform.DesktopLinux: - (Type, Type)? linuxTypeTuple = null; + (Type, Type, Type?)? linuxTypeTuple = null; foreach (var app in allApps) { var devicetype = FindDeviceTypeParameter(app); - if (devicetype.FullName == "Meadow.Desktop") + if (devicetype.DeviceType.FullName == "Meadow.Desktop") { - return (app, devicetype); + return (app, devicetype.DeviceType, devicetype.HardwareProviderType); } - else if (devicetype.FullName == "Meadow.Linux") + else if (devicetype.DeviceType.FullName == "Meadow.Linux") { // keep a ref in case Desktop isn't found - linuxTypeTuple = (app, devicetype); + linuxTypeTuple = (app, devicetype.DeviceType, devicetype.HardwareProviderType); } } @@ -458,13 +497,13 @@ private static (Type appType, Type deviceType)? FindAppForPlatform(MeadowPlatfor { var devicetype = FindDeviceTypeParameter(app); - switch (devicetype.FullName) + switch (devicetype.DeviceType.FullName) { case "Meadow.RaspberryPi": case "Meadow.JetsonNano": case "Meadow.JetsonXavierAgx": case "Meadow.SnickerdoodleBlack": - return (app, devicetype); + return (app, devicetype.DeviceType, devicetype.HardwareProviderType); } } @@ -492,23 +531,23 @@ private static (Type appType, Type deviceType)? FindAppForPlatform(MeadowPlatfor { Resolver.Log.Warn("Multi-targeting of F7 devices is only supported on OS 1.9 and later"); } - return (app, devicetype); + return (app, devicetype.DeviceType, devicetype.HardwareProviderType); case Interop.HardwareVersion.F7FeatherV1: - if (devicetype.FullName == "Meadow.Devices.F7FeatherV1") + if (devicetype.DeviceType.FullName == "Meadow.Devices.F7FeatherV1") { - return (app, devicetype); + return (app, devicetype.DeviceType, devicetype.HardwareProviderType); } break; case Interop.HardwareVersion.F7FeatherV2: - if (devicetype.FullName == "Meadow.Devices.F7FeatherV2") + if (devicetype.DeviceType.FullName == "Meadow.Devices.F7FeatherV2") { - return (app, devicetype); + return (app, devicetype.DeviceType, devicetype.HardwareProviderType); } break; case Interop.HardwareVersion.F7CoreComputeV2: - if (devicetype.FullName == "Meadow.Devices.F7CoreComputeV2") + if (devicetype.DeviceType.FullName == "Meadow.Devices.F7CoreComputeV2") { - return (app, devicetype); + return (app, devicetype.DeviceType, devicetype.HardwareProviderType); } break; } @@ -551,6 +590,7 @@ private static bool Initialize(string[]? args, IApp? app) Type appType = appTypes!.Value.appType; var deviceType = appTypes!.Value.deviceType; + var hardwareProviderType = appTypes!.Value.hardwareProviderType; try { @@ -589,11 +629,11 @@ private static bool Initialize(string[]? args, IApp? app) { if (ex.InnerException != null) { - Resolver.Log.Error($"Creating App instance failure : {ex.Message}{Environment.NewLine}{ex.InnerException}"); + Resolver.Log.Error($"Creating IMeadowDevice instance failure : {ex.Message}{Environment.NewLine}{ex.InnerException}"); } else { - Resolver.Log.Error($"Creating App instance failure : {ex.Message}"); + Resolver.Log.Error($"Creating IMeadowDevice instance failure : {ex.Message}"); } return false; } @@ -621,6 +661,61 @@ private static bool Initialize(string[]? args, IApp? app) Resolver.Log.Trace($"File system Initialize starting..."); InitializeFileSystem(); + IMeadowAppEmbeddedHardware? appHardwareInstance = null; + + try + { + if (hardwareProviderType != null) + { + // because reflection doesn't seem to traverse the type constraints, manually do this trash + var createMethod = hardwareProviderType + .GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy) + .Where(m => m.Name == nameof(IMeadowAppEmbeddedHardwareProvider.Create) + && m.GetParameters().Length == 1 + && m.GetParameters().FirstOrDefault( + p => typeof(IMeadowDevice).IsAssignableFrom(p.ParameterType)) + != null) + .FirstOrDefault(); + + if (createMethod != null) + { + Resolver.Log.Trace($"Creating provider '{hardwareProviderType.Name}'"); + var provider = Activator.CreateInstance(hardwareProviderType); + + Resolver.Log.Trace($"Using provider to create hardware..."); + var hardware = createMethod.Invoke(provider, new object[] { CurrentDevice }); + + Resolver.Log.Trace($"Hardware is a {hardware.GetType().Name}"); + appHardwareInstance = hardware as IMeadowAppEmbeddedHardware; + if (appHardwareInstance != null) + { + Resolver.Log.Trace($"Hardware is an IMeadowAppEmbeddedHardware"); + } + } + else + { + Resolver.Log.Trace($"No appropriavte Create method found"); + } + + } + else + { + Resolver.Log.Trace($"No hardware provider type"); + } + } + catch (Exception ex) + { + if (ex.InnerException != null) + { + Resolver.Log.Error($"Creating hardware provider instance failure : {ex.Message}{Environment.NewLine}{ex.InnerException}"); + } + else + { + Resolver.Log.Error($"Creating hardware provider instance failure : {ex.Message}"); + } + return false; + } + if (app == null) { // Create the app object, bound immediately to the @@ -652,6 +747,37 @@ private static bool Initialize(string[]? args, IApp? app) } } + Resolver.Log.Trace($"Checking for Hardware property"); + if (appType.GetProperty("Hardware", + BindingFlags.Static + | BindingFlags.Public + | BindingFlags.NonPublic + | BindingFlags.FlattenHierarchy) is PropertyInfo hpi) + { + if (hpi.CanWrite) + { + Resolver.Log.Trace($"Setting Hardware property"); + hpi.SetValue(app, appHardwareInstance); + } + else + { + Resolver.Log.Trace($"!! Hardware property not writable"); + } + } + else + { + Resolver.Log.Trace($"!! NO Hardware property. Found these:"); + + foreach (var prop in appType.GetProperties( + BindingFlags.Static + | BindingFlags.Instance + | BindingFlags.Public + | BindingFlags.NonPublic + | BindingFlags.FlattenHierarchy)) + { + Resolver.Log.Trace($" - {prop.Name}"); + } + } App = app; var cloudConnectionService = new MeadowCloudConnectionService(MeadowCloudSettings);