diff --git a/README.md b/README.md index 4310267..1ab2dbe 100644 --- a/README.md +++ b/README.md @@ -4,23 +4,21 @@ In this program, you will build a MORYX application from scratch. You will go through the typical process of an application developer and learn about MORYX concepts and terminology on the way. -You will accompany a pencil manufacturer on its road to the digital factory. -Despite using some specialized machines, they don't have any automated +You will accompany a pencil manufacturer on its road to the digital factory. +Despite using some specialized machines, they don't have any automated processes right now. - ## Who this tour is for This tour addresses application developers, that have a basic understanding of software development/programming. While *ideally* you are a **.NET/C#** developer, -used to work with **VisualStudio** and experienced in **Object Orientated +used to work with **VisualStudio** and experienced in **Object Orientated Programming**, all of that is **not mandatory** to master this. - ## Prerequisite Below is a list of patterns and basic concepts that MORYX is built upon, but you -don't need to know right upfront. +don't need to know right upfront. ### General/OOP @@ -33,7 +31,6 @@ This is a list of more or less 'advanced' topics * [C# Reflection](https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/reflection-and-attributes/) - ### Design Patterns * [Facade](https://en.wikipedia.org/wiki/Facade_pattern#:~:text=The%20facade%20pattern%20(also%20spelled,complex%20underlying%20or%20structural%20code.)) @@ -43,19 +40,19 @@ This is a list of more or less 'advanced' topics * [Repository](https://de.wikipedia.org/wiki/Repository_(Entwurfsmuster)) * [State](https://en.wikipedia.org/wiki/State_pattern) - ## Requirements Before you start, you need the following tools installed on your machine: * [ ] [Git](https://git-scm.com/) -* [ ] [Visual Studio 2022](https://visualstudio.microsoft.com/downloads/) (Any version below wouldn't work, but you can install them in parallel) +* [ ] [Visual Studio 2022 or newer](https://visualstudio.microsoft.com/downloads/) +* [ ] [.NET 10](https://dotnet.microsoft.com/en-us/download/dotnet/10.0) ## [Chapter 1 - Basics](chapter-1-basics.md) In this chapter you will build an application to digitalize a manual pencil production line. -While it introduces the basic terminologies and shows how to create a MORYX +While it introduces the basic terminologies and shows how to create a MORYX application from scratch it enables you to * Model digital twins from existing 'things' within a factory and products @@ -63,7 +60,6 @@ application from scratch it enables you to * Lay out a production workflow * Run your first production process - MORYX Terminology and Concepts you will learn * Resources, Cells @@ -76,29 +72,33 @@ MORYX Terminology and Concepts you will learn ## [Chapter 2 - Drivers](chapter-2-drivers.md) -Since so many pencils were sold, the manufacturer decided that only manual cells aren't feasible anymore. So some manual cells are replaced by fully automated ones. +Since so many pencils were sold, the manufacturer decided that only manual cells aren't feasible anymore. So some manual cells are replaced by fully automated ones. In this chapter you will learn how to -* Communicate with hardware + +* Communicate with hardware * Use different hardware, without having to adjust the source code * Set up a simulated production Terminology and Concepts you will learn - * Drivers - * Protocols - * Simulation +* Drivers +* Protocols +* Simulation ## [Chapter 3 - Basics II](chapter-3-basics-II.md) + The manufacturer decided to have a separate Colorizing Cell for each color in order not to have to change the paint anymore. In this chapter you will learn how to * Find the right Cell depending on a product property -MORYX Terminology and Concepts you will learn - * Capabilities - * ParameterBinding +MORYX Terminology and Concepts you will learn + +* Capabilities +* ParameterBinding ## Help + If you need help, you can ask and find MORYX related questions on Stack Overflow using the tag [moryx](https://stackoverflow.com/questions/tagged/moryx). There is also a Gitter channel or you can open issues on GitHub. diff --git a/chapter-1-basics.md b/chapter-1-basics.md index 98406b6..e2ec20d 100644 --- a/chapter-1-basics.md +++ b/chapter-1-basics.md @@ -1,9 +1,10 @@ # Basics + In this chapter you will learn the basics of MORYX and how it helps you to set up a production. ## Use Case -Your customer *Pencilla Inc.* produces pencils. The production needs prepared +Your customer *Pencilla Inc.* produces pencils. The production needs prepared material - wooden slats and graphite - and consists of the following four steps:​ * Assembling: Glue slats and graphite together​ and shape it. @@ -21,30 +22,30 @@ For more information about pencil production, look [here](https://musgravepencil To setup a new project, you need the *MORYX CLI* installed. If you have installed VisualStudio already, you would use `dotnet` tools: -``` -$ dotnet tool install -g moryx.cli +```bash +dotnet tool install -g moryx.cli ``` Use `moryx new` to create a new MORYX application. It bootstraps a new C# solution -using the provided name and optionally takes additional parameters like `--steps`, +using the provided name and optionally takes additional parameters like `--steps`, `--products`. Thus, it enables you to initialize everything you need in a single command: -``` -$ moryx new --steps --products +```bash +moryx new --steps --products ``` Considering *Pencilla Inc.* to apply MORYX to the whole factory, you decide to use `PencilFactory` as the project name but it could also be a machine name, for example. Optionally you can provide the steps and products already to the `new` -command. The products (`GraphitePencil`, made from `Slat` and `Graphite`) have already been +command. The products (`GraphitePencil`, made from `Slat` and `Graphite`) have already been identified, but only `GraphitePencil` is needed so far. Even though, there are four production steps, in the first iteration, only the `Assembling` step should be covered by MORYX. These information result in the following command: -``` -$ moryx new PencilFactory --steps Assembling --products GraphitePencil +```bash +moryx new PencilFactory --steps Assembling --products GraphitePencil ``` > **Note** This training uses a simplified application template that is tailored to this scenario. It is provided by the --branch parameter here. @@ -52,38 +53,37 @@ $ moryx new PencilFactory --steps Assembling --products GraphitePencil This should not only leave you with a solution `PencilFactory.sln` inside the new folder `PencilFactory`. It also does some initial configuration -and ships empty databases. - +and ships empty databases. + That means, you can directly open it in Visual Studio and dig into it. Run the application (press `F5`). -> **Note** Starting it for the first time will restore NuGet packages.That can +> **Note** Starting it for the first time will restore NuGet packages.That can > take a few minutes. ![Application dashboard](./chapter-1/HomePageOfPencilApp.PNG) - -Now that you have a running MORYX instance, you need to create some databases. +Now that you have a running MORYX instance, you need to create some databases. To skip the UI here and speed things up, you'll use the MORYX CLI again. -While the application is still running, `moryx exec post-setup` will create empty +While the application is still running, `moryx exec post-setup` will create empty databases and restart dependent modules. If your application runs on a different host than `https://localhost:5000`, you can specify that by the `--endpoint ` option. -``` -$ moryx exec post-setup +```bash +moryx exec post-setup ``` ## Products At first, you will model [products](https://github.com/PHOENIXCONTACT/MORYX-Framework/blob/main/docs/articles/Products/Concept.md). -Products represent the articles to be manufactured. MORYX differentiates between -`ProductType` and `ProductInstance`. The `ProductType` is what you can order -in a catalog, while the `ProductInstance` is what you would receive after ordering: -an instance of the product with its unique serial number. In order for a `ProductType` -to be produced, it needs a corresponding `ProductInstance`. For further +Products represent the articles to be manufactured. MORYX differentiates between +`ProductType` and `ProductInstance`. The `ProductType` is what you can order +in a catalog, while the `ProductInstance` is what you would receive after ordering: +an instance of the product with its unique serial number. In order for a `ProductType` +to be produced, it needs a corresponding `ProductInstance`. For further information on how to create a product, see [this](https://github.com/PHOENIXCONTACT/MORYX-Framework/blob/main/docs/tutorials/HowToCreateAProduct.md). Let's take a look at the composition of the pencil *Pencilla Inc.* produces. @@ -97,7 +97,7 @@ Let's take a look at the composition of the pencil *Pencilla Inc.* produces. > switched for names, while for everything else the official format is used. If > you are interested, you will find more about [grading and classification here](https://en.wikipedia.org/wiki/Pencil#Grading_and_classification). -From the details above, the `GraphitePencilType` needs +From the details above, the `GraphitePencilType` needs * a color property * a hardness property @@ -116,7 +116,7 @@ public GraphiteHardness Hardness { get; set; } To make the code compile so far, you still need to implement the enums `PencilColor` and `GraphiteHardness`. You can do that easily by moving the cursor to the red underlined name, press -`Ctrl + .` and select `Generate class 'PencilColor' in new file`. Then, go to +`Ctrl + .` and select `Generate class 'PencilColor' in new file`. Then, go to that file, change `class` to `enum` and add the required attributes: ```cs @@ -139,14 +139,13 @@ public enum GraphiteHardness } ``` - ### Create Products -To create a product, you need to run the application now and head to the -**Products UI**. +To create a product, you need to run the application now and head to the +**Products UI**. -Click on the plus button to open the 'Product Importer' menu. This title may -sound a bit confusing, but it lets you add new products. +Click on the plus button to open the 'Product Importer' menu. This title may +sound a bit confusing, but it lets you add new products. > **Note** The naming here comes from the fact, that you wouldn't necessarily add > products here, but 'import' them from other systems. @@ -154,11 +153,11 @@ sound a bit confusing, but it lets you add new products. ![New product](./chapter-1/create-product.png) * Click on the ProductType dropdown and select **GraphitePencilType** -* Fill in the fields - * *Identifier*: `100001` +* Fill in the fields + * *Identifier*: `100001` * *Revision*: `0` * *Name*: `GP-1B` -* Click on **Import**. +* Click on **Import**. * Click on the product you just added: `100001-00 GP-1B` * Click on the edit icon at the top right to edit. * In the `Color` dropdown choose `Green` @@ -170,7 +169,6 @@ sound a bit confusing, but it lets you add new products. Repeat the same steps for a second product: - * `GraphitePencilType` * *Identifier*: `100002` * *Revision*: `0` @@ -178,34 +176,33 @@ Repeat the same steps for a second product: * *Hardness*: `HB` * *Color*: `Brown` -If you did everything correctly, you should end up with something more or less +If you did everything correctly, you should end up with something more or less similar to the image below. ![Products list](./chapter-1/productList.PNG) -Now you should have your products `100001-00 Green Pencil GP-1B` and -`100002-00 Brown Pencil BP-HB`. -The next challenge is to actually let a resource produce the pencils. So far there is -no script that describes how the pencils are produced. -Therefore, the next step is to model a resource after which we can create a **Recipe** and a -[Workplan](https://github.com/PHOENIXCONTACT/MORYX-Framework/blob/dev/docs/articles/Processing/Workplans.md). - +Now you should have your products `100001-00 Green Pencil GP-1B` and +`100002-00 Brown Pencil BP-HB`. +The next challenge is to actually let a resource produce the pencils. So far there is +no script that describes how the pencils are produced. +Therefore, the next step is to model a resource after which we can create a **Recipe** and a +[Workplan](https://github.com/PHOENIXCONTACT/MORYX-Framework/blob/dev/docs/articles/abstractions/processing/Workplans.md). ## Resources We begin with modeling the assembling station, used to assemble the pencils. This is where resources come into play. -Resources represent physical assets and logical objects like robots or drivers -within a cyber physical system. They can be coupled with active devices or represent -passive objects like a table. The main advantage of this concept is that the +Resources represent physical assets and logical objects like robots or drivers +within a cyber physical system. They can be coupled with active devices or represent +passive objects like a table. The main advantage of this concept is that the resource interface and implementation structure is the same for all resources. -So accessing a resource is done in an abstract and general way. Processing, -configuring and visualization of a resource is done in the same way with the -same classes. Extending a default resource can be done in a standard way and +So accessing a resource is done in an abstract and general way. Processing, +configuring and visualization of a resource is done in the same way with the +same classes. Extending a default resource can be done in a standard way and includes expansion of the default UI as well. -Together with the customer you have figured out the following requirements as +Together with the customer you have figured out the following requirements as the first steps to digitalize their factory: * They want to equip their assembling station with a screen @@ -218,10 +215,9 @@ Based on these requirements * Depending on the workers input (*success*, *failure*) the product either moves to the next station or goes into a scrap container - ### Add worker support -The `AssemblingCell`, which you will find in `PencilFactory.Resources.Assembling`, +The `AssemblingCell`, which you will find in `PencilFactory.Resources.Assembling`, already has an instructor. ``` cs @@ -230,14 +226,14 @@ public IVisualInstructor VisualInstructor { get; set; } ``` `IVisualInstructor` -is the interface for a digital resource displaying the visual instructions on a +is the interface for a digital resource displaying the visual instructions on a screen and requires workers to interact by pressing buttons. `ResourceReference` is an attribute that links two or more resources together. It's a mechanism that is used to save and load the source and the target relationship in the database. -You can start now to map the real world into digital twins using MORYX. As a +You can start now to map the real world into digital twins using MORYX. As a recap, the following is planned for the pencil factory: There should be one assembling station (cell), which will have a monitor to show @@ -246,82 +242,80 @@ instructions to a worker. So the following resources are needed: * 1 `AssemblingCell` * 1 `VisualInstructor` -You will set up these in MORYX within the *Resources UI* by clicking the "+" +You will set up these in MORYX within the *Resources UI* by clicking the "+" button and selecting the required cell. - > **Note** Make sure to deselect all cells before adding more, so that they will > be added to the root level and not as children of other resources. Even though, > that wouldn't do any harm. ![Create resources](./chapter-1/create-resources.png) -In the following dialog it is ok to go with just the typename as the cell -identifier. +In the following dialog it is ok to go with just the typename as the cell +identifier. -If you then select the *AssemblingCell* and click the edit button, you can assign +If you then select the *AssemblingCell* and click the edit button, you can assign the previously created *VisualInstructor* to its *Instructor* property. ![Create resources](./chapter-1/assign-instructor.png) - ### Sessions Cell and Instructor are now connected, but they do not interact with each other. -Therefor, you need to implement the cells session handling. But before you do, -you will get a short introduction about how this works in theory and the +Therefor, you need to implement the cells session handling. But before you do, +you will get a short introduction about how this works in theory and the vocabulary that is used within MORYX. -*Sessions* are considered the sum of all tasks a resource (in this case a cell) -has to perform on a single product instance. A session may consist of multiple -*sequences*, which contain a single *activity*. +*Sessions* are considered the sum of all tasks a resource (in this case a cell) +has to perform on a single product instance. A session may consist of multiple +*sequences*, which contain a single *activity*. While *sessions* and *activities* are represented by types, *sequences* are more of a concept. Usually a *session* (and with it a *sequence*) is started by a resource, telling -the process engine, that it is *ReadyToWork*. +the process engine, that it is *ReadyToWork*. -Then with `ActivityStart()` the process engine can tell a resource, that it -should start working on a product. After the resource has finished its job, it +Then with `ActivityStart()` the process engine can tell a resource, that it +should start working on a product. After the resource has finished its job, it will signal this with `ActivityCompleted()`. When the process engine has processed the *ActivityResult* (submitted by `ActivityCompleted`), it will "close" the sequence with `SequenceCompleted()`. Then, the resource could continue the current *session* (`ContinueSession`) with -another *sequence* or start a whole new *session* by signaling `ReadyToWork`. +another *sequence* or start a whole new *session* by signaling `ReadyToWork`. ![Activities, Sequences and Sessions](./chapter-1/SessionsAndSequences.png) Now, you will convert theory into practice and begin with starting a *session*. -To do so, update the `ControlSystemAttached()` method to the following: +To do so, update the `ProcessEngineAttached()` method to the following: ```cs -public override IEnumerable ControlSystemAttached() +public override IEnumerable ProcessEngineAttached() { yield return Session.StartSession(ActivityClassification.Production, ReadyToWorkType.Push); } ``` -`ControlSystemAttached()` gets called in the event of the ControlSystem being +`ProcessEngineAttached()` gets called in the event of the ProcessEngine being attached to the resource, which is, when the application gets started. - -In here you will yield return a new session using `Session.StartSession()` and + +In here you will yield return a new session using `Session.StartSession()` and thus signal `ReadyToWork` to the ProcessEngine. -* `ReadyToWorkType.Pull` is used to retrieve an activity, that is assigned to +* `ReadyToWorkType.Pull` is used to retrieve an activity, that is assigned to the cell. If there is no activity, it would result in a `SequenceCompleted()`. - This is typically used to start activities if a product is already 'in' a + This is typically used to start activities if a product is already 'in' a cell. * `ReadyToWorkType.Push` *won't* result in a `SequenceCompleted()`, but would 'wait' for an activity, like subscribing for push notifications. * `ActivityClassification.Production` is used to notify that the cell is ready to work on an `Activity` of type `production`. -Since that should result in an `ActivityStarted` event, the next thing to - implement will be the `ActivityStarted()` function. Find the comment -`/* Start execution here */` and replace it so that the whole function looks +Since that should result in an `StartActivity()` call, the next thing to +do will be to implement this function. Find the comment +`/* Start execution here */` and replace it so that the whole function looks like this: ```cs @@ -337,10 +331,10 @@ public override void StartActivity(ActivityStart activityStart) } ``` -That wouldn't compile so far, because there is no `CompleteInstruction` right -now, which is provided as a delegate to `Instructor.Execute`. That means, you -have to implement `CompleteInstruction()`, that gets called, when an instruction -has finished, i.e.: When a worker has finished their task. +That wouldn't compile so far, because there is no `InstructionCompleted()` callback right +now, which is provided as a delegate to `VisualInstructor.Execute`. That means, you +have to implement `InstructionCompleted()`, that gets called, when an instruction +has finished, i.e.: When a worker has finished its task. ```cs private void InstructionCompleted(int instructionResult, ActivityStart activity) @@ -351,11 +345,10 @@ private void InstructionCompleted(int instructionResult, ActivityStart activity) } ``` -These previous lines will complete the activity by publishing an `ActivityResult` -to the ControlSystem. +These previous lines will pubish an `ActivityCompleted` result to the ProcessEngine. -And finally, the method that gets called on a cell after completing work is -`SequenceCompleted()`. In here you start a *ReadyToWork* session again, using +And finally, the method that gets called on a cell after completing work is +`SequenceCompleted()`. In here you start a *ReadyToWork* session again, using `PublishReadyToWork()` and update `_currentSession`. ```cs @@ -374,12 +367,12 @@ public override void SequenceCompleted(SequenceCompleted completed) ### Create a *Workplan* In order to produce pencils, you have to establish the connection between cells -and products: MORYX needs to know, how a product flows through the production +and products: MORYX needs to know, how a product flows through the production line. This is not done in the code, but modelled within [Workplans](https://github.com/PHOENIXCONTACT/MORYX-Framework/blob/main/docs/articles/Processing/Workplans.md). This allows you to define in a rather abstract way, *what* needs to be done without going much more into details. MORYX will find the way later, *how* this is done -for a running order. +for a running order. Start the application, go to *Workplans* and click on the plus button. @@ -387,40 +380,39 @@ Start the application, go to *Workplans* and click on the plus button. Drag an *Assembling Task* step to the workplan and connect the inputs and outputs. -The available *Steps* correlate to the code, that has been generated and was +The available *Steps* correlate to the code, that has been generated and was shipped together with the *assembling* resources. - + ![Whole workplan](./chapter-1/new-workplan.png) -You want to configure the text that is displayed on the worker instruction. -Click on the *Assembling Task* step to edit it and configure it as shown in +You want to configure the text that is displayed on the worker instruction. +Click on the *Assembling Task* step to edit it and configure it as shown in the screenshot below. The text to put in could be `Put the graphite into the slats and glue them together. Then cut the result into pieces and shape each of them`. But could be anything you want. ![Configure tasks](./chapter-1/configure-assembling-task.png) -When you are done, modeling your workflow, you have to save the workplan. - +When you are done, modeling your workflow, you have to save the workplan. ### Add a *Recipe* -In order to make the connection between a `Product` and the `Workplan` you just +In order to make the connection between a `Product` and the `Workplan` you just created, you need a *Recipe*. -Go to *Products* and select the *Product* you want to produce, that is the +Go to *Products* and select the *Product* you want to produce, that is the *GraphitePencil*. Click on the *Recipes* tab and the edit button located on the -top right corner. Click the *Add Recipe* button, located on the bottom left -corner to add a new recipe. +top right corner. Click the *Add Recipe* button, located on the bottom left +corner to add a new recipe. ![Add a new recipe](./chapter-1/product1Recipe.PNG) -* Select the **ProductionRecipe**, give the recipe a name `PencilRecipe` and select -your created workplan `Workplan`. +* Select the **ProductionRecipe**, give the recipe a name `PencilRecipe` and select +your created workplan `Workplan`. ![Create a new recipe](./chapter-1/pencilRecipe.PNG) -* Click on **Create** for the recipe to show up in the products UI. -* For the recipe to be automatically selected when the product is produced, select +* Click on **Create** for the recipe to show up in the products UI. +* For the recipe to be automatically selected when the product is produced, select `Default` Classification. * Save your changes by clicking on the save icon located at the top right corner. @@ -436,17 +428,16 @@ To create a new `Order`. Navigate to the *Orders* UI. ![Orders](./chapter-1/ordersUI.PNG) +Fill in the details of the `Order`: -Fill in the details of the `Order`: - -* *Order Number*: ` 000001` . -* *Operation Number*: ` 0001`. -* *Product*: ` 100001-00 GP-1B` -* *Recipe*: ` Pencil Recipe` -* *Amount*: ` 5` +* *Order Number*: `000001` . +* *Operation Number*: `0001`. +* *Product*: `100001-00 GP-1B` +* *Recipe*: `Pencil Recipe` +* *Amount*: `5` Click on the plus button to add this as an operation to the order. One order -can have multiple operations, but this is not needed here. Click *CREATE* to +can have multiple operations, but this is not needed here. Click *CREATE* to create the order. * Click on *BEGIN* to start the production of the order. @@ -473,11 +464,10 @@ You can manually select it by clicking on the Settings icon at the top right. Use the `SUCCESS` and `FAILED` action to make the products flow through the production line - ## Troubleshooting Here you will find a list of common problems, that might occur, and how to fix -them. +them. ### Encountering database issues after setup section @@ -490,16 +480,21 @@ consider checking the databases in the command center. There you may have to create the missing databases. If the issue occurs during the APD at a later stage due to messing up the order of steps or making changes to classes, of existing entries, you may also need to delete the DB. + #### Step 1: Open the Command Center + ![Open the Command Center](./chapter-1/commandCenter.png) #### Step 2: Check the databases and create missing ones + ![2. Check the databases and create missing ones](./chapter-1/commandCenterDB.png) #### Step 3: Reincarnate the failed services + ![3. Reincarnate the failed services](./chapter-1/commandCenterModules.png) ### I cannot find a resource in the add dialog + If you cannot find an expected resource in the Add Resource dialog, this is usually because the reference to the corresponding assembly has not been loaded. Moryx uses reflection to scan the public classes that inherit from Resource. The scan is only performed for resources in the current AppDomain (in this case PencilFactory.App). -To fix the problem, you should add any missing project or package references and then check again if the resource you expected is now found. \ No newline at end of file +To fix the problem, you should add any missing project or package references and then check again if the resource you expected is now found. diff --git a/chapter-2-drivers.md b/chapter-2-drivers.md index 1a5db37..517c727 100644 --- a/chapter-2-drivers.md +++ b/chapter-2-drivers.md @@ -1,91 +1,98 @@ # Driver -In this chapter you will implement the ColorizingCell and the TestingCell. -Both of them are automatic cells, which don't need any user interaction. + +In this chapter you will implement the ColorizingCell and the TestingCell. +Both of them are automatic cells, which don't need any user interaction. Correspondingly there are no visual instructions. Instead there is some kind of hardware, which needs to be connected to MORYX. -For the cell to communicate with the hardware a [Driver](https://github.com/PHOENIXCONTACT/MORYX-Framework/blob/dev/docs/tutorials/HowToBuildADriver.md) is needed. +For the cell to communicate with the hardware a [Driver](https://github.com/PHOENIXCONTACT/MORYX-Framework/blob/dev/docs/tutorials/HowToBuildADriver.md) is needed. In here the communication is encapsulated. -As there are many different ways to communicate, there are also many different implementations of drivers. +As there are many different ways to communicate, there are also many different implementations of drivers. Common interfaces for drivers are `IMessageDriver` and `IInOutDriver`. + * The `IMessageDriver` is used for message based protocols. The driver is able to send and receive messages. When a new message is received, an event gets invoked. A typical protocol would be MQTT. -* The `IInOutDriver` can read and write variables on a server. A typical protocol is OPC UA. +* The `IInOutDriver` can read and write variables on a server. A typical protocol is OPC UA. ## Simulated InOutDriver + You will start with the ColorizingCell. Since the cell isn't finished yet, the manufacturer wants you to simulate the communication first. Use the CLI to add the Colorizing step to the project. + +```bash +moryx add step Colorizing ``` -$ moryx add step Colorizing -``` + Now, in order to use simulation, add the package `Moryx.Drivers.Simulation` to the project `PencilFactory.Resources`. The ColorizingCell is using a protocol, where it can read and write variables on the physical cell. 1. When the physical cell is ready to work, it will set the input `Ready` to `true`. -2. The digital twin will send a `ReadyToWork`. -3. When the cell receives an activity, set the output `ProcessStart` to `true`. -4. Read the result from the input `ProcessResult`. +2. The digital twin will send a `Ready` input changed event. +3. When the cell receives an activity, set the output `ProcessStart` to `true`. +4. Read the result from the input `ProcessResult`. For a protocol like that the `IInOutDriver` makes the most sense. Open the ColorizingCell and replace the already generated `IMessageDriver` by an `IInOutDriver`. Also add constants for the names of the variables to read and write. ```cs [ResourceRegistration] -public class ColorizingCell : Cell +public class ColorizingCell : Cell, IStateContext { private const string ProcessStart = "ProcessStart"; private const string ProcessResult = "ProcessResult"; - private const string ReadyToWork = "Ready"; + private const string Ready = "Ready"; [ResourceReference(ResourceRelationType.Driver)] - public IInOutDriver Driver { get; set; } + public IInOutDriver Driver { get; set; } ... } ``` -In order to recognize, when an input changes, subscribe to that in `OnInitialize` and when the driver is set. If you don't also subscribe to the event in the setter of the driver, you will always have to restart the system after changing the driver of a cell. +In order to recognize, when an input changes, subscribe to that in `OnInitializeAsync` and when the driver is set. If you don't also subscribe to the event in the setter of the driver, you will always have to restart the system after changing the driver of a cell. Adjust the Driver variable and functions to match the following. ```cs -private IInOutDriver _driver; - [ResourceReference(ResourceRelationType.Driver)] -public IInOutDriver Driver +public IInOutDriver Driver { - get { return _driver; } + get => field set { - _driver = value; - if (_driver != null) + if (field?.Input != null) { - _driver.Input.InputChanged += OnInputChanged; + field.Input.InputChanged -= OnInputChanged; + } + + field = value; + + if (field?.Input != null) + { + field.Input.InputChanged += OnInputChanged; } } } ``` ```cs -protected override void OnInitialize() +protected override void OnInitializeAsync() { ... - if (_driver != null) + if (Driver?.Input != null) { - _driver.Input.InputChanged += OnInputChanged; + Driver.Input.InputChanged += OnInputChanged; } } ``` - - In the method `OnInputChanged` you will check, if the value of `Ready` has changed. If it is true, send a `ReadyToWork` to the ProcessEngine. Replace the contents of the function with the following two code segments. ```cs private void OnInputChanged(object sender, InputChangedEventArgs args) { - if (args.Key.Equals(ReadyToWork) && _driver.Input[ReadyToWork] && !(_currentSession is ActivityStart)) + if (args.Key == Ready && (bool)args.Value && _currentSession is not ActivityStart) { var rtw = Session.StartSession(ActivityClassification.Production, ReadyToWorkType.Pull); _currentSession = rtw; @@ -96,16 +103,16 @@ private void OnInputChanged(object sender, InputChangedEventArgs args) } ``` -If the changed input is `ProcessResult`, read the result from the input and publish it as `ActivityCompleted`. Also set the input `ProcessStart` back to false, so that the physical cell is able to detect when to start the next process. If you don't reset the value of `ProcessStart`, the physical cell is not able to recognize the specific moment an activity should start. Some physical cells also only recognize rising or falling edges. Constant values would trigger nothing. +If the changed input is `ProcessResult`, read the result from the input and publish it as `ActivityCompleted`. Also set the ouput `ProcessStart` back to false, so that the physical cell is able to detect when to start the next process. If you don't reset the value of `ProcessStart`, the physical cell is not able to recognize the specific moment an activity should start. Some physical cells also only recognize rising or falling edges. Constant values would trigger nothing. ```cs private void OnInputChanged(object sender, InputChangedEventArgs args) { ... - else if (args.Key.Equals(ProcessResult) && _currentSession is ActivityStart activitySession) + else if (args.Key == ProcessResult && _currentSession is ActivityStart activitySession) { - _driver.Output[ProcessStart] = false; - var processResult = _driver.Input[ProcessResult]; + Driver.Output[ProcessStart] = false; + var processResult = (bool)Driver.Input[ProcessResult]; var result = activitySession.CreateResult(processResult ? (int)ColorizingActivityResults.Success : (int)ColorizingActivityResults.Failed); _currentSession = result; @@ -115,20 +122,21 @@ private void OnInputChanged(object sender, InputChangedEventArgs args) ``` In order to start an activity on the physical cell when an activityStart is received, set `ProcessStart` to `true`. + ```cs public override void StartActivity(ActivityStart activityStart) { _currentSession = activityStart; switch (activityStart.Activity) { - case Colorizing​Activity activity: - _driver.Output[ProcessStart] = true; + case Colorizing​Activity: + Driver.Output[ProcessStart] = true; break; } } ``` -The `SessionCompleted` can be implemented in the same way as in the AssemblingCell. +The `SequenceCompleted` can be implemented in the same way as in the AssemblingCell. ```cs public override void SequenceCompleted(SequenceCompleted completed) @@ -141,20 +149,20 @@ public override void SequenceCompleted(SequenceCompleted completed) } ``` -The same applies for `ControlSystemAttached`. +The same applies for `ProcessEngineAttached`. ```cs -public override IEnumerable ControlSystemAttached() +public override IEnumerable ProcessEngineAttached() { yield return Session.StartSession(ActivityClassification.Production, ReadyToWorkType.Push); } ``` -Now you have to implement the driver. Create a new driver `SimulatedColorizingDriver` in the project `PencilFactory.Resources.Colorizing`, which is derived from `SimulatedInOutDriver` and add the constants for the variable names. +Now you have to implement the driver. Create a new driver `SimulatedColorizingDriver` in the project `PencilFactory.Resources.Colorizing`, which is derived from `SimulatedInOutDriver` and add the constants for the variable names. ```cs [ResourceRegistration] -public class SimulatedColorizingDriver : SimulatedInOutDriver +public class SimulatedColorizingDriver : SimulatedInOutDriver { private const string ProcessStart = "ProcessStart"; private const string ProcessResult = "ProcessResult"; @@ -164,7 +172,7 @@ public class SimulatedColorizingDriver : SimulatedInOutDriver } ``` -A `SimulationDriver` has several states, which are needed in order for the SimulationModule to know what happens. After the system has booted, the driver is in the state `Idle`. Is a product arriving, the cell sends a `ReadyToWork` and the driver changes its state to `Requested`. During production the state is `Executing` and afterward it changes back to `Idle`. +A `SimulatedInOutDriver` has several states, which are needed in order for the SimulationModule to know what happens. After the system has booted, the driver is in the state `Idle`. Is a product arriving, the cell sends a `Ready` and the driver changes its state to `Requested`. During production the state is `Executing` and afterward it changes back to `Idle`. ![States of a SimulationDriver](./chapter-2/SimulationStates.png) diff --git a/chapter-3-basics-II.md b/chapter-3-basics-II.md index 920d2b8..63679fd 100644 --- a/chapter-3-basics-II.md +++ b/chapter-3-basics-II.md @@ -1,4 +1,5 @@ # Basics II - Capabilities and ParameterBinding + In this chapter you will learn how to find the right Cell depending on a product property. The manufacturer realized that only one ColorizingCell, in which the paint always has to be changed, isn't really efficient. He decided to add another one. Now for each color there is one cell. @@ -39,8 +40,9 @@ public class ColorizingCell : Cell } ``` + > Note: -> The private variable with DataMember attribute the public one attributed with EntrySerialize are separated from each other here. +> The private variable with DataMember attribute and the public property attributed with EntrySerialize are separated from each other here. > DataMember attributes are committed to the database before EntrySerialize ones are initialized. > Here this would lead to not executing the setter before writing to the database. @@ -68,21 +70,21 @@ Now MORYX knows, which cell uses which color, but it still doesn't know which co In order to let the activity know, which color it needs, you will use Parameters. -Parameters are the connection between *Tasks* and *Activities*. +Parameters are the connection between *Tasks* and *Activities*. In the production process it looks like this: -[![](https://mermaid.ink/img/pako:eNpdktuO2jAQhl_F8g1BChQngRwuWnV3u1KrHlBBqlTlxsRmsUg8ke3sbpbl3WsnJKDmJrbnm9_zz_iEC2AcZ3hfwktxoMqg7V0ukf204TXxNsad_aq5okaARH1s8gCIoFoBawozmaLZ7GPHB9694tRw9A1206tMMALhABC0VlBwrXM52R644khoBLJsr7pISG2oLPjESnXJo0zkfQc4WmBQmWj0B9SxLqm8wNEIL09fNTLdHRRJ_mpGtCc_nfv_0mW8P9JS83e3X3neF8mm09voVjV9MPa29MjRxi4vQDxemAwmPxdGPAvTXohkJFJvDXVTOmZNFa244QrB3lX5f1I6JpHF6VFIhn5QUxyEfMrlPa3pTpQWRo3uTpyAbcmzYJzdtbm0ygdgaLBIFjcee1Hi2RlD_SGX62ZXCn1AP8GIvSi6eQ_uL4mjfRJ4v7mGRhWuB-6JvNie2hIG_jpyEl5RO2MGkncDuIGvg11iH1dcVVQw-yhP7gnl2JqqeI4zu2R8T5vS5DiXZ4vSxsCmlQXOjK3Mx03NbE8fBH2yTcXZ3vn0cU3lX4BqgOwWZyf8ijMSpvN4kYTxIk5IGCRh5OMWZ7MgiuZhmARJFK6iIIiWq7OP3zoJMk8tmAZxmoZxHEUkOP8DohsVrg?type=png)](https://mermaid.live/edit#pako:eNpdktuO2jAQhl_F8g1BChQngRwuWnV3u1KrHlBBqlTlxsRmsUg8ke3sbpbl3WsnJKDmJrbnm9_zz_iEC2AcZ3hfwktxoMqg7V0ukf204TXxNsad_aq5okaARH1s8gCIoFoBawozmaLZ7GPHB9694tRw9A1206tMMALhABC0VlBwrXM52R644khoBLJsr7pISG2oLPjESnXJo0zkfQc4WmBQmWj0B9SxLqm8wNEIL09fNTLdHRRJ_mpGtCc_nfv_0mW8P9JS83e3X3neF8mm09voVjV9MPa29MjRxi4vQDxemAwmPxdGPAvTXohkJFJvDXVTOmZNFa244QrB3lX5f1I6JpHF6VFIhn5QUxyEfMrlPa3pTpQWRo3uTpyAbcmzYJzdtbm0ygdgaLBIFjcee1Hi2RlD_SGX62ZXCn1AP8GIvSi6eQ_uL4mjfRJ4v7mGRhWuB-6JvNie2hIG_jpyEl5RO2MGkncDuIGvg11iH1dcVVQw-yhP7gnl2JqqeI4zu2R8T5vS5DiXZ4vSxsCmlQXOjK3Mx03NbE8fBH2yTcXZ3vn0cU3lX4BqgOwWZyf8ijMSpvN4kYTxIk5IGCRh5OMWZ7MgiuZhmARJFK6iIIiWq7OP3zoJMk8tmAZxmoZxHEUkOP8DohsVrg) +[![Connection Tasks and Activities](https://mermaid.ink/img/pako:eNpdktuO2jAQhl_F8g1BChQngRwuWnV3u1KrHlBBqlTlxsRmsUg8ke3sbpbl3WsnJKDmJrbnm9_zz_iEC2AcZ3hfwktxoMqg7V0ukf204TXxNsad_aq5okaARH1s8gCIoFoBawozmaLZ7GPHB9694tRw9A1206tMMALhABC0VlBwrXM52R644khoBLJsr7pISG2oLPjESnXJo0zkfQc4WmBQmWj0B9SxLqm8wNEIL09fNTLdHRRJ_mpGtCc_nfv_0mW8P9JS83e3X3neF8mm09voVjV9MPa29MjRxi4vQDxemAwmPxdGPAvTXohkJFJvDXVTOmZNFa244QrB3lX5f1I6JpHF6VFIhn5QUxyEfMrlPa3pTpQWRo3uTpyAbcmzYJzdtbm0ygdgaLBIFjcee1Hi2RlD_SGX62ZXCn1AP8GIvSi6eQ_uL4mjfRJ4v7mGRhWuB-6JvNie2hIG_jpyEl5RO2MGkncDuIGvg11iH1dcVVQw-yhP7gnl2JqqeI4zu2R8T5vS5DiXZ4vSxsCmlQXOjK3Mx03NbE8fBH2yTcXZ3vn0cU3lX4BqgOwWZyf8ijMSpvN4kYTxIk5IGCRh5OMWZ7MgiuZhmARJFK6iIIiWq7OP3zoJMk8tmAZxmoZxHEUkOP8DohsVrg?type=png)](https://mermaid.live/edit#pako:eNpdktuO2jAQhl_F8g1BChQngRwuWnV3u1KrHlBBqlTlxsRmsUg8ke3sbpbl3WsnJKDmJrbnm9_zz_iEC2AcZ3hfwktxoMqg7V0ukf204TXxNsad_aq5okaARH1s8gCIoFoBawozmaLZ7GPHB9694tRw9A1206tMMALhABC0VlBwrXM52R644khoBLJsr7pISG2oLPjESnXJo0zkfQc4WmBQmWj0B9SxLqm8wNEIL09fNTLdHRRJ_mpGtCc_nfv_0mW8P9JS83e3X3neF8mm09voVjV9MPa29MjRxi4vQDxemAwmPxdGPAvTXohkJFJvDXVTOmZNFa244QrB3lX5f1I6JpHF6VFIhn5QUxyEfMrlPa3pTpQWRo3uTpyAbcmzYJzdtbm0ygdgaLBIFjcee1Hi2RlD_SGX62ZXCn1AP8GIvSi6eQ_uL4mjfRJ4v7mGRhWuB-6JvNie2hIG_jpyEl5RO2MGkncDuIGvg11iH1dcVVQw-yhP7gnl2JqqeI4zu2R8T5vS5DiXZ4vSxsCmlQXOjK3Mx03NbE8fBH2yTcXZ3vn0cU3lX4BqgOwWZyf8ijMSpvN4kYTxIk5IGCRh5OMWZ7MgiuZhmARJFK6iIIiWq7OP3zoJMk8tmAZxmoZxHEUkOP8DohsVrg) -As you can see the resource will only get the *Activity*. +As you can see the resource will only get the *Activity*. The *Task*, which is equivalent to the *workplan step*, can be parameterized and parameters can be transferred to the activity. -Under the hood, however, the parameters can do even more: Instances of the -process and product will be provided to the Populate method and can thus -get lots of information. For example, order numbers can be read from the recipe or the ProductType can be read from the process. +Under the hood, however, the parameters can do even more: Instances of the +process and product will be provided to the Populate method and can thus +get lots of information. For example, order numbers can be read from the recipe or the ProductType can be read from the process. -Now add the color as property to the `ColorizingParameters` found in the folder `Activities`. Since the property is automatically set using the product information, it doesn't need to be displayed in the UI. +Now add the color as property to the `ColorizingParameters` found in the folder `Activities`. Since the property is automatically set using the product information, it doesn't need to be displayed in the UI. -When populating parameters, the current object always represents the parameters in the workplan, while `instance` (the parameter of the populate method) holds the parameters passed by the activity. The resource gets them during the `OnActivityStarted` method. +When populating parameters, the current object always represents the parameters in the workplan, while `instance` (the parameter of the populate method) holds the parameters passed by the activity. The resource gets them during the `StartActivity` method. Now you have to set the color of `instance` to the color of the product. In order to get the ProductType, cast the process to a `ProductionProcess` and get the ProductType from the ProductInstance. ```cs @@ -104,10 +106,10 @@ public class ColorizingParameters : VisualInstructionParameters ``` The color in the `Colorizing​Parameters` defines which color is needed for the activity. -This information still has to be passed on to ProcessEngine, which is done, by adjusting the RequiredCapabilities. +This information still has to be passed on to ProcessEngine, which is done, by adjusting the RequiredCapabilities. The ProcessEngine routes a product to a Cell, which can perform the next activity to be done. -Go to the `ColorizingActivity` also found in the folder `Activities` and set the color in the RequiredCapabilities. +Go to the `ColorizingActivity` also found in the folder `Activities` and set the color in the RequiredCapabilities. ```cs [ActivityResults(typeof(Colorizing​ActivityResults))] @@ -123,26 +125,26 @@ public class Colorizing​Activity : Activity Now you have done everything to have separate Cells for each pencil color. Start your project and create two different ColorizingCells, one for each color. Do not forget to configure a driver for each cell just like shown at the end of [chapter-2](chapter-2-drivers.md). - -In the first chapter you set values in your parameters using the workplans UI. In this chapter the color was automatically fetched from the product. +In the first chapter you set values in your parameters using the workplans UI. In this chapter the color was automatically fetched from the product. This concept of not having to set the value of parameters explicitly using the UI, but instead automatically fetching them from somewhere is called `ParameterBinding`. ## When to use ParameterBinding and when write new Capabilities + In order to produce different colored pencils, you added the property `Color` to the `ColorizingCapabilities` and the `ColorizingActivityParameters`. In this paragraph you will learn different approaches and in which scenarios they should be used. In this example both ColorizingCells are identical. The only difference is the color they are setup with. -One approach would be to create a production step for each color. Then you have a `ColorizingGreenActivity` and a `ColorizingBrownActivity`, each with matching Capabilities. -This has the downside of you needing a separate workplan for each color, which would create a lot of boilerplate. -Different steps should be used, when you use them in the same workplan. -Imagine the right half of your pencil is green and the other one brown, but you only have one `ColorizingActivity`. -In order to know which color is still missing on the pencil, you would need to know, if the pencil was already partly colored. -Just the information of the product wouldn't be sufficient anymore. +One approach would be to create a production step for each color. Then you have a `ColorizingGreenActivity` and a `ColorizingBrownActivity`, each with matching Capabilities. +This has the downside of you needing a separate workplan for each color, which would create a lot of boilerplate. +Different steps should be used, when you use them in the same workplan. +Imagine the right half of your pencil is green and the other one brown, but you only have one `ColorizingActivity`. +In order to know which color is still missing on the pencil, you would need to know, if the pencil was already partly colored. +Just the information of the product wouldn't be sufficient anymore. Instead of creating different steps, you could also create different capabilities. You could have made the `ColorizingCapabilities` abstract and then derive `ColorizingGreenCapabilities` and `ColorizingBrownCapabilities` from it. -This should be used, when it's not possible to setup a resource in order to change its characteristic. -Let's look at the example of printing a label. The label should be printed in a specific color and with a specific method (pad printing or laser printing). -Nearly all printers can print any color. In worst case you have to change the color manually, but it is still possible. Changing the printing method is most of the time physically impossible. +This should be used, when it's not possible to setup a resource in order to change its characteristic. +Let's look at the example of printing a label. The label should be printed in a specific color and with a specific method (pad printing or laser printing). +Nearly all printers can print any color. In worst case you have to change the color manually, but it is still possible. Changing the printing method is most of the time physically impossible. In that case you will create abstract `PrintingCapabilities`, which contain the color. ```cs @@ -165,7 +167,7 @@ public abstract class PrintingCapabilities : CapabilitiesBase Now you will derive one capability for each printing method you have. -```cs +```cs public class LaserPrintingCapabilities : PrintingCapabilities { protected override bool ProvidedBy(ICapabilities provided)