diff --git a/src/Moryx.AbstractionLayer/Drivers/Camera/ICameraDriver.cs b/src/Moryx.AbstractionLayer/Drivers/Camera/ICameraDriver.cs
new file mode 100644
index 000000000..3c4dd5653
--- /dev/null
+++ b/src/Moryx.AbstractionLayer/Drivers/Camera/ICameraDriver.cs
@@ -0,0 +1,31 @@
+using Moryx.AbstractionLayer.Drivers;
+using System.Threading.Tasks;
+
+namespace Moryx.Drivers.Camera.Interfaces
+{
+ ///
+ /// Interface for camera devices, that provide image data
+ ///
+ public interface ICameraDriver : IDriver where TImage : class
+ {
+ ///
+ /// Registers an ICameraDriverListener that should be provided
+ /// with images.
+ ///
+ void Register(ICameraDriverListener listener);
+
+ ///
+ /// Unregisters an ICameraDriverListener
+ ///
+ void Unregister(ICameraDriverListener listener);
+
+ ///
+ /// Capture a single image from the camera
+ ///
+ ///
+ /// The image that was captured or null in case no image
+ /// could be retrieved
+ ///
+ Task CaptureImage();
+ }
+}
diff --git a/src/Moryx.AbstractionLayer/Drivers/Camera/ICameraDriverListener.cs b/src/Moryx.AbstractionLayer/Drivers/Camera/ICameraDriverListener.cs
new file mode 100644
index 000000000..da7a7fe34
--- /dev/null
+++ b/src/Moryx.AbstractionLayer/Drivers/Camera/ICameraDriverListener.cs
@@ -0,0 +1,18 @@
+using Moryx.AbstractionLayer.Drivers;
+using System.Threading.Tasks;
+
+namespace Moryx.Drivers.Camera.Interfaces
+{
+ ///
+ /// Interface for objects that register as listeners to camera drivers
+ ///
+ public interface ICameraDriverListener where T : class
+ {
+ ///
+ /// Invoked, when a new image is received by the camera
+ ///
+ ///
+ ///
+ Task OnImage(T image);
+ }
+}
diff --git a/src/Moryx.CommandCenter.Web/src/common/container/App.tsx b/src/Moryx.CommandCenter.Web/src/common/container/App.tsx
index 1fe1e8c8a..dd81dd397 100644
--- a/src/Moryx.CommandCenter.Web/src/common/container/App.tsx
+++ b/src/Moryx.CommandCenter.Web/src/common/container/App.tsx
@@ -104,7 +104,7 @@ function App(props: AppPropModel & AppDispatchPropModel) {
} />
} />
- } />
+ } />
diff --git a/src/Moryx.Resources.Management/Facades/ResourceManagementFacade.cs b/src/Moryx.Resources.Management/Facades/ResourceManagementFacade.cs
index baa5f7a23..95cc8075d 100644
--- a/src/Moryx.Resources.Management/Facades/ResourceManagementFacade.cs
+++ b/src/Moryx.Resources.Management/Facades/ResourceManagementFacade.cs
@@ -9,7 +9,7 @@
namespace Moryx.Resources.Management
{
- internal class ResourceManagementFacade : IResourceManagement, IFacadeControl
+ internal class ResourceManagementFacade : FacadeBase, IResourceManagement
{
#region Dependency Injection
@@ -22,11 +22,9 @@ internal class ResourceManagementFacade : IResourceManagement, IFacadeControl
#endregion
#region IFacadeControl
- ///
- public Action ValidateHealthState { get; set; }
///
- public void Activate()
+ public override void Activate()
{
Manager.ResourceAdded += OnResourceAdded;
Manager.CapabilitiesChanged += OnCapabilitiesChanged;
@@ -34,7 +32,7 @@ public void Activate()
}
///
- public void Deactivate()
+ public override void Deactivate()
{
Manager.ResourceAdded -= OnResourceAdded;
Manager.CapabilitiesChanged -= OnCapabilitiesChanged;
diff --git a/src/Moryx/Tools/FunctionResult.cs b/src/Moryx/Tools/FunctionResult.cs
new file mode 100644
index 000000000..527691f29
--- /dev/null
+++ b/src/Moryx/Tools/FunctionResult.cs
@@ -0,0 +1,246 @@
+// Copyright (c) 2023, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using System;
+
+namespace Moryx.Tools.FunctionResult
+{
+ ///
+ /// Generic type that allows functions to always return a proper result,
+ /// that either contains a valid value or an error and helps exercising
+ /// error handling.
+ ///
+ ///
+ public class FunctionResult
+ {
+ ///
+ /// Result value in case of success
+ ///
+ public TResult? Result { get; } = default;
+
+ ///
+ /// Error in case of failure
+ ///
+ public FunctionResultError? Error { get; } = null;
+
+ ///
+ /// Indicates if the result contains a valid value
+ /// or not
+ ///
+ public bool Success => Error == null;
+
+ ///
+ /// Creates a result with a value
+ ///
+ ///
+ public FunctionResult(TResult result)
+ {
+ Result = result;
+ }
+
+ ///
+ /// Creates an error result with
+ ///
+ ///
+ public FunctionResult(FunctionResultError error)
+ {
+ Error = error;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return Success
+ ? Result?.ToString() ?? "null"
+ : Error!.ToString();
+ }
+
+ ///
+ /// Process result value and errors in a 'pattern matching '-like way
+ ///
+ /// Function to be excecuted in case of success
+ /// Function to be excecuted in case of an error
+ /// of the executed function
+ public FunctionResult Match(Func> success, Func> error)
+ => Success ? success(Result!) : error(Error!);
+
+ ///
+ /// Process result value and errors in a 'pattern matching '-like way
+ ///
+ /// Action to be excecuted in case of success
+ /// Action to be excecuted in case of an error
+ /// The current
+ public FunctionResult Match(Action success, Action error)
+ => Match(
+ s =>
+ {
+ success(s);
+ return this;
+ },
+ e =>
+ {
+ error(e);
+ return this;
+ });
+ }
+
+ ///
+ /// of type to be used
+ /// for functions that would return
+ ///
+ public class FunctionResult : FunctionResult
+ {
+ ///
+ /// Creates a `successful` with 'no' value
+ ///
+ public FunctionResult() : base(new Nothing())
+ {
+ }
+
+
+ ///
+ /// Creates an error result with
+ ///
+ ///
+ public FunctionResult(FunctionResultError error) : base(error)
+ {
+ }
+
+ ///
+ /// Helper to create an Ok in a descriptive way.
+ ///
+ ///
+ public static FunctionResult Ok()
+ => new FunctionResult();
+
+ ///
+ /// Helper to create a with an error message in a descriptive way.
+ ///
+ ///
+ public static FunctionResult WithError(string message)
+ => new(new FunctionResultError(message));
+
+ ///
+ /// Helper to create a with an in a descriptive way.
+ ///
+ ///
+ public static FunctionResult WithError(Exception exception)
+ => new(new FunctionResultError(exception));
+
+ ///
+ /// Helper to create an Ok in a descriptive way.
+ ///
+ /// of
+ public static FunctionResult Ok(TResult result)
+ => new(result);
+
+ ///
+ /// Helper to create a with an error message in a descriptive way.
+ ///
+ ///
+ public static FunctionResult WithError(string message)
+ => new(new FunctionResultError(message));
+
+ ///
+ /// Helper to create an Ok in a descriptive way.
+ ///
+ ///
+ public static FunctionResult WithError(Exception exception)
+ => new(new FunctionResultError(exception));
+
+ }
+
+ ///
+ /// Holds a description of the error and optionally an
+ ///
+ public class FunctionResultError
+ {
+ ///
+ /// Error message
+ ///
+ public string Message { get; }
+
+ ///
+ /// Exception that might be the reason for the error
+ ///
+ public Exception? Exception { get; } = null;
+
+
+ ///
+ /// Creates an error with error message
+ ///
+ /// In case of is null
+ public FunctionResultError(string message)
+ {
+ if (message is null)
+ throw new ArgumentNullException(nameof(message));
+
+ Message = message;
+ }
+
+ ///
+ /// Creates an error with error message
+ ///
+ /// In case of is null
+ public FunctionResultError(Exception exception)
+ {
+ if (exception is null)
+ throw new ArgumentNullException(nameof(exception));
+
+ Message = exception.Message;
+ Exception = exception;
+ }
+
+ ///
+ public override string ToString()
+ {
+ return Exception != null
+ ? Exception.Message
+ : Message;
+ }
+
+ }
+
+
+ ///
+ /// Placeholder type to return nothing when for example
+ /// would be returned
+ ///
+ public class Nothing
+ {
+ }
+
+ ///
+ /// Extensions for
+ ///
+ public static class FunctionResultExtensions
+ {
+ ///
+ /// Executes the provided function in case of a successful result
+ ///
+ /// returned by
+ public static FunctionResult Then(this FunctionResult result, Func> func)
+ => result.Match(func, _ => result);
+
+ ///
+ /// Executes the provided function in case of an error result
+ ///
+ /// returned by
+ public static FunctionResult Catch(this FunctionResult result, Func> func)
+ => result.Match(_ => result, func);
+
+ ///
+ /// Executes the provided action in case of a successful result
+ ///
+ /// The underlying
+ public static FunctionResult Then(this FunctionResult result, Action action)
+ => result.Match(action, _ => { });
+
+ ///
+ /// Executes the provided action in case of a error result
+ ///
+ /// The underlying
+ public static FunctionResult Catch(this FunctionResult result, Action action)
+ => result.Match(_ => { }, action);
+ }
+}
diff --git a/src/Tests/Moryx.Tests/Moryx.Tests.csproj b/src/Tests/Moryx.Tests/Moryx.Tests.csproj
index 39faf789d..75f356f3b 100644
--- a/src/Tests/Moryx.Tests/Moryx.Tests.csproj
+++ b/src/Tests/Moryx.Tests/Moryx.Tests.csproj
@@ -9,6 +9,7 @@
+
diff --git a/src/Tests/Moryx.Tests/Tools/FunctionResultTestsBase.cs b/src/Tests/Moryx.Tests/Tools/FunctionResultTestsBase.cs
new file mode 100644
index 000000000..f79eec0f9
--- /dev/null
+++ b/src/Tests/Moryx.Tests/Tools/FunctionResultTestsBase.cs
@@ -0,0 +1,14 @@
+// Copyright (c) 2023, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using NUnit.Framework;
+
+namespace Moryx.Tests.Tools
+{
+ [TestFixture]
+ public class FunctionResultTestsBase
+ {
+ protected const string Message = "Error occured!";
+ protected const string ExceptionMessage = "Exception Message";
+ }
+}
diff --git a/src/Tests/Moryx.Tests/Tools/FunctionResultWithNothingTests.cs b/src/Tests/Moryx.Tests/Tools/FunctionResultWithNothingTests.cs
new file mode 100644
index 000000000..7057077cc
--- /dev/null
+++ b/src/Tests/Moryx.Tests/Tools/FunctionResultWithNothingTests.cs
@@ -0,0 +1,171 @@
+// Copyright (c) 2023, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using NUnit.Framework;
+using Moryx.Tools.FunctionResult;
+using System;
+using Moq;
+
+namespace Moryx.Tests.Tools;
+
+[TestFixture]
+public class FunctionResultWithNothingTests : FunctionResultTestsBase
+{
+ protected Mock>> _funcMockSuccess;
+ protected Mock>> _funcMockError;
+
+ protected Mock> _actionMockSuccess;
+ protected Mock> _actionMockError;
+
+ [SetUp]
+ public void Setup()
+ {
+ _funcMockSuccess = new Mock>>();
+ _funcMockSuccess
+ .Setup(f => f(It.IsAny()))
+ .Returns((Nothing arg) => new FunctionResult(arg));
+ _funcMockError = new Mock>>();
+ _funcMockError
+ .Setup(f => f(It.IsAny()))
+ .Returns((FunctionResultError arg) => new FunctionResult(arg));
+
+ _actionMockSuccess = new Mock>();
+ _actionMockError = new Mock>();
+ }
+
+
+ [Test]
+ public void ResultWithValueGetCreated()
+ {
+ var result = new FunctionResult();
+
+ Assert.That(result.Success, Is.True);
+ Assert.That(result.Error, Is.Null);
+ Assert.That(result.Result, Is.TypeOf());
+ Assert.That(result.ToString(), Contains.Substring("Nothing"));
+ }
+
+ [Test]
+ public void ErrorResultWithMessageGetsCreated()
+ {
+ var result = new FunctionResult(new FunctionResultError(Message));
+
+ Assert.That(result.Success, Is.False);
+ Assert.That(result.Result, Is.Null);
+
+ Assert.That(result.Error.Message, Is.EqualTo(Message));
+ Assert.That(result.Error.Exception, Is.Null);
+ Assert.That(result.ToString(), Is.EqualTo(Message));
+ }
+
+ [Test]
+ public void ErrorResultWithExceptionGetsCreated()
+ {
+ var result = new FunctionResult(new FunctionResultError(new Exception(ExceptionMessage)));
+
+ Assert.That(result.Success, Is.False);
+ Assert.That(result.Result, Is.Null);
+
+ Assert.That(result.Error.Message, Is.EqualTo(ExceptionMessage));
+ Assert.That(result.Error.Exception, Is.TypeOf());
+ Assert.That(result.ToString(), Is.EqualTo(ExceptionMessage));
+ }
+
+ [Test]
+ public void ResultWithNothingGetsCreatedByUsingExtension()
+ {
+ var result = FunctionResult.Ok();
+
+ Assert.That(result.Success, Is.True);
+ Assert.That(result.Result, Is.TypeOf());
+ Assert.That(result.Error, Is.Null);
+ Assert.That(result.ToString(), Contains.Substring("Nothing"));
+ }
+
+ [Test]
+ public void ErrorResultWithMessageGetsCreatedByUsingExtension()
+ {
+ var result = FunctionResult.WithError(Message);
+
+ Assert.That(result.Success, Is.False);
+ Assert.That(result.Result, Is.Null);
+
+
+ Assert.That(result.Error.Message, Is.EqualTo(Message));
+ Assert.That(result.Error.Exception, Is.Null);
+ Assert.That(result.ToString(), Is.EqualTo(Message));
+ }
+
+ [Test]
+ public void ErrorResultWithExceptionGetsCreatedByUsingExtension()
+ {
+ FunctionResult result = FunctionResult.WithError(new Exception(ExceptionMessage));
+
+ Assert.That(result.Success, Is.False);
+ Assert.That(result.Result, Is.Null);
+
+ Assert.That(result.Error.Message, Is.EqualTo(ExceptionMessage));
+ Assert.That(result.Error.Exception, Is.TypeOf());
+ Assert.That(result.ToString(), Is.EqualTo(ExceptionMessage));
+ }
+
+ [Test]
+ public void CannotCreateErrorResultWithNullExeption()
+ {
+ Assert.Throws(() => { FunctionResult.WithError((Exception)null); });
+ }
+
+ [Test]
+ public void CannotCreateErrorResultWithNullMessage()
+ {
+ Assert.Throws(() => { FunctionResult.WithError((string)null); });
+ }
+
+ [Test]
+ public void ExecutesFuncOnError()
+ {
+ FunctionResult.WithError("500")
+ .Then(_funcMockSuccess.Object)
+ .Catch(_funcMockError.Object)
+ ;
+
+ _funcMockSuccess.Verify(f => f(It.IsAny()), Times.Never);
+ _funcMockError.Verify(f => f(It.IsAny()), Times.Once);
+ }
+
+ [Test]
+ public void ExecutesFuncOnSuccess()
+ {
+ FunctionResult.Ok()
+ .Catch(_funcMockError.Object)
+ .Then(_funcMockSuccess.Object)
+ ;
+
+ _funcMockSuccess.Verify(f => f(It.IsAny()), Times.Once);
+ _funcMockError.Verify(f => f(It.IsAny()), Times.Never);
+ }
+
+ [Test]
+ public void ExecutesActionOnError()
+ {
+ FunctionResult.WithError("500")
+ .Then(_actionMockSuccess.Object)
+ .Catch(_actionMockError.Object)
+ ;
+
+ _actionMockSuccess.Verify(a => a(It.IsAny()), Times.Never);
+ _actionMockError.Verify(a => a(It.IsAny()), Times.Once);
+ }
+
+ [Test]
+ public void ExecutesActionOnSuccess()
+ {
+ FunctionResult.Ok()
+ .Catch(_actionMockError.Object)
+ .Then(_actionMockSuccess.Object)
+ ;
+
+ _actionMockSuccess.Verify(a => a(It.IsAny()), Times.Once);
+ _actionMockError.Verify(a => a(It.IsAny()), Times.Never);
+ }
+}
diff --git a/src/Tests/Moryx.Tests/Tools/FunctionResultWithTypeTests.cs b/src/Tests/Moryx.Tests/Tools/FunctionResultWithTypeTests.cs
new file mode 100644
index 000000000..c84b6b755
--- /dev/null
+++ b/src/Tests/Moryx.Tests/Tools/FunctionResultWithTypeTests.cs
@@ -0,0 +1,293 @@
+// Copyright (c) 2023, Phoenix Contact GmbH & Co. KG
+// Licensed under the Apache License, Version 2.0
+
+using NUnit.Framework;
+using System;
+using Moq;
+using Moryx.Tools.FunctionResult;
+
+namespace Moryx.Tests.Tools;
+
+[TestFixture]
+public class FunctionResultWithTypeTests : FunctionResultTestsBase
+{
+ protected Mock>> _funcMockSuccess;
+ protected Mock>> _funcMockError;
+
+ protected Mock> _actionMockSuccess;
+ protected Mock> _actionMockError;
+
+ [SetUp]
+ public void Setup()
+ {
+ _funcMockSuccess = new Mock>>();
+ _funcMockSuccess
+ .Setup(f => f(It.IsAny()))
+ .Returns((int arg) => new FunctionResult(arg));
+ _funcMockError = new Mock>>();
+ _funcMockError
+ .Setup(f => f(It.IsAny()))
+ .Returns((FunctionResultError arg) => new FunctionResult(arg));
+
+ _actionMockSuccess = new Mock>();
+ _actionMockError = new Mock>();
+ }
+
+ [Test]
+ public void ResultWithValueGetsCreated()
+ {
+ var result = new FunctionResult(1);
+
+ Assert.That(result.Success, Is.True);
+ Assert.That(result.Error, Is.Null);
+ Assert.That(result.Result, Is.EqualTo(1));
+ Assert.That(result.ToString(), Is.EqualTo("1"));
+ }
+
+ [Test]
+ public void ErrorResultWithMessageGetsCreated()
+ {
+ var result = new FunctionResult(new FunctionResultError(Message));
+
+ Assert.That(result.Success, Is.False);
+ Assert.That(result.Result, Is.EqualTo(0));
+
+ Assert.That(result.Error.Message, Is.EqualTo(Message));
+ Assert.That(result.Error.Exception, Is.Null);
+ Assert.That(result.ToString(), Is.EqualTo(Message));
+ }
+
+ [Test]
+ public void ErrorResultWithExceptionGetsCreated()
+ {
+ var result = new FunctionResult(new FunctionResultError(new Exception(ExceptionMessage)));
+
+ Assert.That(result.Success, Is.False);
+ Assert.That(result.Result, Is.EqualTo(0));
+
+ Assert.That(result.Error.Message, Is.EqualTo(ExceptionMessage));
+ Assert.That(result.Error.Exception, Is.TypeOf());
+ Assert.That(result.ToString(), Is.EqualTo(ExceptionMessage));
+ }
+
+ [Test]
+ public void ResultWithValueGetsCreatedByUsingExtension()
+ {
+ var result = FunctionResult.Ok(10);
+
+ Assert.That(result.Success, Is.True);
+ Assert.That(result.Result, Is.EqualTo(10));
+
+ Assert.That(result.Error, Is.Null);
+ Assert.That(result.ToString(), Is.EqualTo("10"));
+ }
+
+ [Test]
+ public void ErrorResultWithMessageGetsCreatedByUsingExtension()
+ {
+ var result = FunctionResult.WithError(Message);
+
+ Assert.That(result.Success, Is.False);
+ Assert.That(result.Result, Is.EqualTo(0));
+
+
+ Assert.That(result.Error.Message, Is.EqualTo(Message));
+ Assert.That(result.Error.Exception, Is.Null);
+ Assert.That(result.ToString(), Is.EqualTo(Message));
+ }
+
+ [Test]
+ public void ErrorResultWithExceptionGetsCreatedByUsingExtension()
+ {
+ var result = FunctionResult.WithError(new Exception(ExceptionMessage));
+
+ Assert.That(result.Success, Is.False);
+ Assert.That(result.Result, Is.EqualTo(0));
+
+ Assert.That(result.Error.Message, Is.EqualTo(ExceptionMessage));
+ Assert.That(result.Error.Exception, Is.TypeOf());
+ Assert.That(result.ToString(), Is.EqualTo(ExceptionMessage));
+ }
+
+ [Test]
+ public void ResultToStringEqualsTheResultsToStringReturnValue()
+ {
+ var floatResult = FunctionResult.Ok(3.14f);
+ string floatAsString = Convert.ToString(3.14f); // avoid localization issues
+ var noResult = FunctionResult.Ok(new Nothing());
+ var nullResult = FunctionResult.Ok