Skip to content

Commit fed3ae8

Browse files
committed
Merge remote-tracking branch 'origin/dev' into future
2 parents ae071ed + 6ec81cd commit fed3ae8

26 files changed

+768
-8
lines changed

.github/dependabot.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ version: 2
77
updates:
88
- package-ecosystem: "nuget" # See documentation for possible values
99
directory: "/" # Location of package manifests
10+
target-branch: "future"
1011
open-pull-requests-limit: 10
1112
schedule:
1213
interval: "weekly"
@@ -19,4 +20,4 @@ updates:
1920
- "nuget"
2021
- "dependencies"
2122
ignore:
22-
- dependency-name: "Moryx.AbstractionLayer"
23+
- dependency-name: "Moryx.AbstractionLayer"

Directory.Build.targets

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@
1616
<!-- Package versions for package references across all projects -->
1717
<ItemGroup>
1818
<!--3rd party dependencies-->
19-
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="17.7.1" />
20-
<PackageReference Update="Moq" Version="4.17.2" />
19+
<PackageReference Update="Microsoft.NET.Test.Sdk" Version="17.6.3" />
20+
<PackageReference Update="Moq" Version="4.20.69" />
2121
<PackageReference Update="NUnit" Version="3.13.3" />
2222
<PackageReference Update="NUnit3TestAdapter" Version="4.5.0" />
23-
<PackageReference Update="coverlet.collector" Version="6.0.0" >
23+
<PackageReference Update="coverlet.collector" Version="3.2.0" >
2424
<PrivateAssets>all</PrivateAssets>
2525
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2626
</PackageReference>

MoryxFactory.sln

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moryx.ProcessData", "src\Mo
2929
EndProject
3030
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moryx.ProcessData.Tests", "src\Tests\Moryx.ProcessData.Tests\Moryx.ProcessData.Tests.csproj", "{8E9609B6-9AF6-4F8E-A8DD-0B98E8B3B587}"
3131
EndProject
32+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Moryx.Factory", "src\Moryx.Factory\Moryx.Factory.csproj", "{4B1C8062-EF37-4880-AFDF-2C3CCEE3A848}"
33+
EndProject
34+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Moryx.Simulation", "src\Moryx.Simulation\Moryx.Simulation.csproj", "{23B4A0C7-6F13-4FBB-998C-CD56DCF6D775}"
35+
EndProject
3236
Global
3337
GlobalSection(SolutionConfigurationPlatforms) = preSolution
3438
Debug|Any CPU = Debug|Any CPU
@@ -59,6 +63,14 @@ Global
5963
{8E9609B6-9AF6-4F8E-A8DD-0B98E8B3B587}.Debug|Any CPU.Build.0 = Debug|Any CPU
6064
{8E9609B6-9AF6-4F8E-A8DD-0B98E8B3B587}.Release|Any CPU.ActiveCfg = Release|Any CPU
6165
{8E9609B6-9AF6-4F8E-A8DD-0B98E8B3B587}.Release|Any CPU.Build.0 = Release|Any CPU
66+
{4B1C8062-EF37-4880-AFDF-2C3CCEE3A848}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
67+
{4B1C8062-EF37-4880-AFDF-2C3CCEE3A848}.Debug|Any CPU.Build.0 = Debug|Any CPU
68+
{4B1C8062-EF37-4880-AFDF-2C3CCEE3A848}.Release|Any CPU.ActiveCfg = Release|Any CPU
69+
{4B1C8062-EF37-4880-AFDF-2C3CCEE3A848}.Release|Any CPU.Build.0 = Release|Any CPU
70+
{23B4A0C7-6F13-4FBB-998C-CD56DCF6D775}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
71+
{23B4A0C7-6F13-4FBB-998C-CD56DCF6D775}.Debug|Any CPU.Build.0 = Debug|Any CPU
72+
{23B4A0C7-6F13-4FBB-998C-CD56DCF6D775}.Release|Any CPU.ActiveCfg = Release|Any CPU
73+
{23B4A0C7-6F13-4FBB-998C-CD56DCF6D775}.Release|Any CPU.Build.0 = Release|Any CPU
6274
EndGlobalSection
6375
GlobalSection(SolutionProperties) = preSolution
6476
HideSolutionNode = FALSE

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ This repository contains the APIs, domain objects and developer documentation fo
3131
| `Moryx.Orders` | [![NuGet](https://img.shields.io/nuget/v/Moryx.Orders.svg)](https://www.nuget.org/packages/Moryx.Orders/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.Orders)](https://www.myget.org/feed/moryx/package/nuget/Moryx.Orders) | [![MyGet-Release](https://img.shields.io/myget/moryx-future/vpre/Moryx.Orders)](https://www.myget.org/feed/moryx-future/package/nuget/Moryx.Orders) |
3232
| `Moryx.Users` | [![NuGet](https://img.shields.io/nuget/v/Moryx.Users.svg)](https://www.nuget.org/packages/Moryx.Users/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.Users)](https://www.myget.org/feed/moryx/package/nuget/Moryx.Users) | [![MyGet-Release](https://img.shields.io/myget/moryx-future/vpre/Moryx.Users)](https://www.myget.org/feed/moryx-future/package/nuget/Moryx.USers) |
3333
| `Moryx.ProcessData` | [![NuGet](https://img.shields.io/nuget/v/Moryx.ProcessData.svg)](https://www.nuget.org/packages/Moryx.ProcessData/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.ProcessData)](https://www.myget.org/feed/moryx/package/nuget/Moryx.ProcessData) | [![MyGet-Release](https://img.shields.io/myget/moryx-future/vpre/Moryx.ProcessData)](https://www.myget.org/feed/moryx-future/package/nuget/Moryx.ProcessData) |
34+
| `Moryx.Simulation` | [![NuGet](https://img.shields.io/nuget/v/Moryx.Simulation.svg)](https://www.nuget.org/packages/Moryx.Simulation/) | [![MyGet](https://img.shields.io/myget/moryx/vpre/Moryx.Simulation)](https://www.myget.org/feed/moryx/package/nuget/Moryx.Simulation) | [![MyGet-Release](https://img.shields.io/myget/moryx-future/vpre/Moryx.Simulation)](https://www.myget.org/feed/moryx-future/package/nuget/Moryx.Simulation) |
3435
## Contribute
3536

3637
You can contribute to the MORYX Factory Domain by reporting bugs and suggesting extensions to APIs.
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
# Simulation Driver
2+
3+
If none of the existing, commercial drivers from `Moryx.Drivers.Simulation` fits your requirements, you can easily implement your own driver. All you need to do is implement the interface `Moryx.Simulation.ISimulationDriver` as well as the interface of the driver you want to replace like `IMessageDriver<object>` or `IInOutDriver`.
4+
5+
A module like the commercial simulator will call `Ready` and `Result` on your instance as a replacement for the events usually received from real drivers and sensors. You need to translate them into values or events of you driver interface like `IMessageDriver.Received`. The methods references the activity which you can use to extract the relevant attributes like process id, carrier or product identifiers as needed by your cell. Process execution initiated by the drivers API like parameter values or `Send` methods need to be represented by the `SimulatedState`.
6+
7+
## Example of simulated driver
8+
9+
So here is an example of how your mock driver can implement `Moryx.Simulation.ISimulationDriver`:
10+
``` csharp
11+
[ResourceRegistration]
12+
public class TestMockDriver : Driver, IMessageDriver<object>,ISimulationDriver {
13+
14+
public bool HasChannels => false;
15+
16+
public IDriver Driver => this;
17+
18+
public string Identifier => Name;
19+
20+
private SimulationState _simulatedState;
21+
// simulated state of the driver
22+
public SimulationState SimulatedState
23+
{
24+
get => _simulatedState;
25+
private set
26+
{
27+
_simulatedState = value;
28+
SimulatedStateChanged?.Invoke(this, value);
29+
}
30+
}
31+
32+
// cell referenced by the driver
33+
[ResourceReference(ResourceRelationType.Driver, ResourceReferenceRole.Source)]
34+
public AssemblyCell Cell { get; set; }
35+
36+
public IEnumerable<ICell> Usages => new[] { Cell };
37+
38+
protected override void OnStart()
39+
{
40+
base.OnStart();
41+
// initial simulated state of the driver
42+
SimulatedState = SimulationState.Idle;
43+
}
44+
45+
public IMessageChannel<TChannel> Channel<TChannel>(string identifier)
46+
{
47+
throw new NotImplementedException();
48+
}
49+
50+
public IMessageChannel<TSend, TReceive> Channel<TSend, TReceive>(string identifier)
51+
{
52+
throw new NotImplementedException();
53+
}
54+
55+
// Message received from the cell
56+
public void Send(object payload)
57+
{
58+
switch (payload)
59+
{
60+
case AssembleProductMessage assemble:
61+
SimulatedState = SimulationState.Executing;
62+
break;
63+
case ReleaseWorkpieceMessage release:
64+
SimulatedState = SimulationState.Idle;
65+
break;
66+
}
67+
}
68+
69+
public Task SendAsync(object payload)
70+
{
71+
Send(payload);
72+
return Task.CompletedTask;
73+
}
74+
75+
public void Ready(IActivity activity)
76+
{
77+
SimulatedState = SimulationState.Requested;
78+
79+
Received?.Invoke(this, new WorkpieceArrivedMessage { ProcessId = activity.Process.Id });
80+
}
81+
82+
public void Result(SimulationResult result)
83+
{
84+
Received?.Invoke(this, new AssemblyCompletedMessage { Result = result.Result });
85+
}
86+
87+
public event EventHandler<object> Received;
88+
89+
public event EventHandler<SimulationState> SimulatedStateChanged;
90+
91+
//... rest of your codes//
92+
}
93+
```
94+
95+
Note: The `Send()` method might differ from cell to cell.You can override the `Send()` and do your implementation of what needs to happend when your "mock" driver receives something from the cell. In case your driver doesn't have a `send()` method, you can use your own method and react upon the type of message you received just like described inside the `TestMockDriver.Send()` method above.
96+
97+
# How does my Cell use my `MockDriver` ?
98+
99+
In your cell definition/Class you can use the following example based on the type of driver you're trying to simulate. Let's say we are trying to "mock" an `MQTT driver` since Moryx MQTT driver implements from `IMessageDriver<object>` in your cell you can have the following case:
100+
```csharp
101+
// SolderingCell.cs
102+
103+
[ResourceRegistration]
104+
public class SolderingCell : Cell
105+
{
106+
//... rest of your codes//
107+
108+
[ResourceReference(ResourceRelationType.Driver, IsRequired = true)]
109+
public IMessageDriver<object> Driver { get; set; }
110+
111+
//... rest of your codes//
112+
}
113+
```
114+
115+
Since the our "mock" driver has the following property defined:
116+
```csharp
117+
//... rest of the codes//
118+
119+
/// <summary>
120+
/// Cell linked to the driver
121+
/// </summary>
122+
[ResourceReference(ResourceRelationType.Driver, ResourceReferenceRole.Source)]
123+
public AssemblyCell Cell { get; set; }
124+
125+
//... rest of the codes//
126+
```
127+
Moryx will take care of the relationship once you assign the driver to the cell.
128+
To start the communication you can define message types in your resources folder. For starter we can define some message types. You can also use already existing messages
129+
```csharp
130+
//AssembleProductMessage.cs
131+
132+
/// <summary>
133+
/// Message specificto start an activity
134+
/// </summary>
135+
public class AssembleProductMessage
136+
{
137+
public long ActivityId { get; set; }
138+
}
139+
140+
//AssemblyCompletedMessage.cs
141+
public class AssemblyCompletedMessage
142+
{
143+
public int Result { get; set; }
144+
}
145+
146+
//ReleaseWorkpieceMessage.cs
147+
public class ReleaseWorkpieceMessage
148+
{
149+
//rest of the code...
150+
}
151+
152+
//WorkpieceArrivedMessage.cs
153+
public class WorkpieceArrivedMessage
154+
{
155+
public long ProcessId { get; set; }
156+
}
157+
```
158+
In the cell when you need to send a data to the driver you can do as described bellow:
159+
```csharp
160+
//AssemblyCell.cs
161+
//... rest of the codes//
162+
163+
public override void StartActivity(ActivityStart activityStart)
164+
{
165+
Driver.Send(new AssembleProductMessage { ActivityId = activityStart.Activity.Id });
166+
}
167+
168+
//... rest of the codes//
169+
```
170+
As you can see inside the `StartActivity` method we want the cell to send data to the driver.
171+
172+
Now in our driver `TestMockDriver` class above. Inside the `public void Send(object payload)` method we are filtering the payload based on the message type that we just define. We can act upon the type of message received. For instance:
173+
```csharp
174+
//... rest of the codes//
175+
176+
public void Send(object payload)
177+
{
178+
switch (payload)
179+
{
180+
case AssembleProductMessage assemble:
181+
SimulatedState = SimulationState.Executing;
182+
break;
183+
case ReleaseWorkpieceMessage release:
184+
SimulatedState = SimulationState.Idle;
185+
break;
186+
}
187+
}
188+
189+
//... rest of the codes//
190+
```
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Simulation parameters
2+
3+
If you need to influence the simulation behavior of activites you can add `ISimulationParameters` to your parameters and return different execution times either by configuration or through binding on digital twins.
4+
5+
## Configurable execution time
6+
7+
The example below shows how to implement configurable execution time parameters
8+
9+
````cs
10+
internal class ConfiguredSimulationParameter : Parameters, ISimulationParameters
11+
{
12+
[EntrySerialize(EntrySerializeMode.Always)]
13+
[Description("Execution time in seconds")]
14+
public int ExecutionTimeSec { get; set; }
15+
16+
TimeSpan ISimulationParameters.ExecutionTime => new TimeSpan(0, 0, ExecutionTimeSec);
17+
18+
/// <inheritdoc />
19+
protected override void Populate(IProcess process, Parameters instance)
20+
{
21+
var parameters = (ConfiguredSimulationParameter)instance;
22+
23+
parameters.ExecutionTimeSec = ExecutionTimeSec;
24+
}
25+
}
26+
````
27+
28+
## Binding execution times
29+
30+
You can also determine execution time by binding like any other parameters. You could bind to static values or calculate execution time dynamically.
31+
32+
````cs
33+
internal class BindingSimulationParameters : Parameters, ISimulationParameters
34+
{
35+
[EntrySerialize(EntrySerializeMode.Never)]
36+
public TimeSpan ExecutionTime { get; set; }
37+
38+
/// <inheritdoc />
39+
protected override void Populate(IProcess process, Parameters instance)
40+
{
41+
var parameters = (BindingSimulationParameters)instance;
42+
43+
var recipe = (MyRecipe)process.Recipe;
44+
parameters.ExecutionTime = new TimeSpan(recipe.TestTimeSec);
45+
}
46+
}
47+
````

src/Moryx.ControlSystem/VisualInstructions/EnumInstructionAttribute.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,21 @@ public class EnumInstructionAttribute : Attribute
1919
/// <summary>
2020
/// The title of the button generated for the enum value marked with this attribute.
2121
/// </summary>
22+
[Obsolete("Use Display Attribute instead")]
2223
public string Title { get; }
2324

2425
/// <summary>
25-
/// Empty constructor to use <see cref="Hide"/> without declaring a title
26+
/// Create instruction attribute without title
2627
/// </summary>
2728
public EnumInstructionAttribute()
28-
{
29+
{
30+
2931
}
3032

3133
/// <summary>
3234
/// Constructor with title
3335
/// </summary>
34-
/// <param name="title">The title</param>
36+
[Obsolete("Use empty constructor and Display Attribute instead")]
3537
public EnumInstructionAttribute(string title)
3638
{
3739
Title = title;

src/Moryx.ControlSystem/VisualInstructions/EnumInstructionResult.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2021, Phoenix Contact GmbH & Co. KG
22
// Licensed under the Apache License, Version 2.0
33

4+
using Moryx.Tools;
45
using System;
56
using System.Collections.Generic;
67
using System.Linq;
@@ -39,9 +40,11 @@ private static IDictionary<string, int> ParseEnum(Type resultEnum, params string
3940
foreach (var name in Enum.GetNames(resultEnum).Except(exceptions))
4041
{
4142
var member = resultEnum.GetMember(name)[0];
43+
// Try to fetch display name or title from attribute
44+
var displayName = member.GetDisplayName();
4245
var attribute = (EnumInstructionAttribute)member.GetCustomAttributes(typeof(EnumInstructionAttribute), false).FirstOrDefault();
4346

44-
var text = attribute?.Title ?? name;
47+
var text = displayName ?? attribute?.Title ?? name;
4548
var numericValue = (int)Enum.Parse(resultEnum, name);
4649
allValues[text] = numericValue;
4750

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2023, Phoenix Contact GmbH & Co. KG
2+
// Licensed under the Apache License, Version 2.0
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Text;
7+
8+
namespace Moryx.Factory
9+
{
10+
/// <summary>
11+
/// Attribute for a visual representation of the current property inside the Factory monitor UI
12+
/// </summary>
13+
public class EntryVisualizationAttribute : Attribute
14+
{
15+
public EntryVisualizationAttribute(string unit, string icon)
16+
{
17+
Unit = unit;
18+
Icon = icon;
19+
}
20+
21+
/// <summary>
22+
/// Unit of the value for the current property (Ex. Kw/h)
23+
/// </summary>
24+
public string Unit { get; }
25+
26+
/// <summary>
27+
/// Icon to display for this property inside the Factory Monitor UI
28+
/// </summary>
29+
public string Icon { get; }
30+
}
31+
}

0 commit comments

Comments
 (0)