--- pdfexport: true alias: tutorials-connectiot-configuration-advanced timetoread: true tutorial: full module: Connect IoT (Equipment Integration) description: "This tutorial guides you through advanced IoT configuration, integrating data workflows and displaying temperature values via a GUI" --- # Connect IoT - Advanced Configuration Tutorial This tutorial builds upon the ([Basic](connectiot_configuration_basic.md) and [Intermediate](connectiot_configuration_intermediate.md)) configuration tutorial settings and will attempt to integrate data read from an equipment while applying some workflow logic to the retrieved value. In the basic tutorial we had an OPC-UA integration, which connected to an OPC-UA server, in the intermediate we posted to a Data Collection. In the `Advanced Tutorial`, we will first make the integration configurable in terms of connection settings, then we will address the scenario where we have a setup of a resource where we set the setpoint to 200ºC and validate it. When the setpoint reaches the 200ºC the user will be able to perform a Track In and post the temperature to a Data Collection. After 1 minute it will perform the Track Out of the Material. An additional requirement is the ability to collect temperature values on demand. !!! note During this tutorial, the **Automation Manager** will run in console mode in order to highlight the most important events as they take place. ## MES Model Let's start with building the model for this tutorial: 1. Create a Calendar 2. Create a Facility 3. Create an Area 4. Create a Resource named `Oven` 5. Create a Step named `Oven Step` 6. Create a Service `Oven Service` 1. Add the service to the Resource Services 2. Add the service to the Step Context 7. Create a Flow named `Oven Flow` with the `Oven Step` 8. Create a Product named `Product Oven` 1. Give as default flow path the Flow `Oven Flow` and the Step `Oven Step` 9. Create a Material `Oven Material` 10. Dispatch the `Oven Material` to the Resource `Oven` If the `Oven Material` is not dispatchable to the Resource `Oven`, please revalidate your model. !!! note You can always change the model to what best suits you. This tutorial is not focused in MES modeling so it will be very brief on this topic. Also, feel free to use the model you have from previous tutorials and add the parts that are missing from your model. ## Automation Driver Definition Let's go over the **Automation Driver Definition** `Oven DD` and set the properties. In this case we will not need events, only properties and commands. !!! note Note that to do this in a real life context, it is important to have a general knowledge about the protocol, the equipment itself and its related documentation. ### Properties Go to `Automation Driver Definition`, select the `Oven DD` and then select `Edit`. To add the `TemperatureSetPoint` property: 1. Skip the General Data step 2. Add a new entry to the list of Properties by selecting :material-plus: 3. In the Property details, provide: - A name that represents the Property name - A description - The NodeID - identification of the Property - check the equipment documentation for the actual identification of the property on the equipment - The type (for classification and reporting purposes) - The `Writable` and `Readable` flags - The data type of the Property in OPC UA format - check the equipment documentation for the actual data type of the property on the equipment ![Add TemperatureSetpoint](../../../images/connectiot_adv_dd_prop.png) In this case we have no commands or events. If we chose to add any command of event, they would be defined in the appropriate tabs. ## Automation Controller Let's go over the **Automation Controller** `Oven Controller` and define the logic that will support the described scenario. ### Setup First, let's review how we are addressing the setup. Currently, we have the values statically defined in the workflow, but in order to reuse the same controller for different entities, we need the workflow to be a bit more dynamic. Let's start with the following steps: 1. Drag and drop the following tasks: - [[user-guide-automation-task-core-getconfigurations]]: to retrieve Configurations from the system (`Administration > Configurations`) - [[user-guide-automation-task-core-entityinstance]]: to assess the associated entity 2. Connect the output of `OnSetup` of the `On Equipment Setup` task to the `Entity Instance` active 3. Remove the link of `OnInitialize` to the `connect` link 4. In the `Get Configurations` task: 1. Create input `resourceName` - Inputs in the `Get Configurations` can be used as tokens 2. Create output `Address` 1. Give as path `/Custom/ConnectIoT/OPC-UA/${resourceName}/Address` - notice `${resourceName}` will be replaced by the defined input 2. Type as `String` ![Add Configuration Input](../../../images/connectiot_adv_getconfig_input.png) ![Add Configuration Output](../../../images/connectiot_adv_getconfig_output.png) 1. Link the `Entity Instance` instance to the activate of the `Get Configurations` 2. Link the `Entity Instance` instance to the resource name input of the `Get Configurations` 1. The instance comes with the full object payload and we just need the `Name` 1. Create converter `AnyToAny` 2. Create converter `GetObjectProperty` 1. With path `Name` 2. Type `String` 3. Link the `Get Configurations` success to connect of the `On Equipment Setup` task 4. Go to `Administration > Configuration` 1. Click `Create` 2. Create configuration entries until you have the desired path of `/Custom/ConnectIoT/OPC-UA/Oven` 3. Create the config that will hold the value 1. In the `Name` write `Address` 2. In the `Type` select `String` 3. Set the value with the proper address (i.e `opc.tcp://localhost:48101`) 5. Link the output `Address` of the `Get Configurations` to the `OnEquipmentSetup` `Address` input ![Add Configuration Entry](../../../images/connectiot_adv_config_address.png) Now, the hook to determine the address will no longer be the `Automation Controller` but rather the configuration for a particular resource in the configurations entry. This is a simple way to make our controller fully dynamic and in that way promote its reusability throughout different resources of the same type. There are other mechanisms to hold these values such as saving in `Entity Attributes` or on a separate table, both of these are also out of the box supported by tasks, with the `Entity Instance` task and the `Resolve Table` task. ## On Demand - Show Temperature One of the interesting applications of Connect IoT is providing direct feedback to a GUI, in order to have a more powerful and symbiotic integration between the equipment and the operator. The goal of our GUI is to be able to retrieve and display the latest temperature value. This is a very simple example, but it will provide the basic building blocks of the system. ### Create a DEE - GetCurrentTemperature Let's create a DEE to retrieve the Temperature from Connect IoT. 1. Go to `Administration > DEE Actions` 2. Select `New` 3. Give as Name `GetCurrentTemperature` 4. Give Classification `ConnectIoT` 5. Use the following code: ```csharp // Retrieve Service Provider var serviceProvider = (IServiceProvider)Input["ServiceProvider"]; IResource resource = serviceProvider.GetService(); // Retrieve Resource Name from the Inputs resource.Name = Input["Resource"] as string; resource.Load(); var instance = resource.GetAutomationControllerInstance(); // Validate there's an Automation Controller instance for this resource if (instance == null) { throw new Exception("Resource not connected to any IoT instance"); } else { // Request value from Connect IoT and wait for the reply dynamic payload = instance.SendRequest("Cmf.Request.Temperature", null, 10000); Input.Add("Value", payload["reply"].ToString()); } ``` In the DEE execution, it will receive a resource name, resolve the instance linked to the resource and send a message to Connect IoT and wait for a reply. 1. Go to `BusinessData > Rule` 2. Select `New` 3. Give as Name `GetCurrentTemperature` 4. Give as Scope `ConnectIoT` 5. DEE Action `GetCurrentTemperature` - if it does not appear in the search box, validate that the DEE Classification is correct ![Add Rule GetCurrentTemperature](../../../images/connectiot_adv_rule_getcurrenttemp.png) ### Automation Controller - Retrieve Temperature In the Automation Controller it should now, upon the message received from the DEE, retrieve and reply back with the temperature value. 1. Go to `Oven Controller` 1. In the Workflow, create a new page `On Demand - Get Temperature` 1. Drag and drop the following tasks: - [[user-guide-automation-task-core-systemevent]]: to subscribe to the message bus topic `Cmf.Request.Temperature` - [[user-guide-automation-task-core-getequipmentproperties]]: to get the value of the temperature 1. Go to the `On System Event` settings and for the Action Group, write `Cmf.Request.Temperature`. ![On System Event - Request Temperature](../../../images/connectiot_adv_onsysevent_req_temp.png) 1. In the `Get Equipment Properties Values` task 1. Add as Output, the Automation Property `Temperature` 1. Link the `On System Event` output `data` to the `Get Equipment Properties Values` Activate input 1. Link the `Get Equipment Properties Values` `Temperature` output to the input `reply` 1. Add a converter `AnyToAny` ![On Demand - Get Temperature](../../../images/connectiot_adv_wf_gettemp.png) ### Test with the DEE Start the OPC UA Server mentioned in the previous ([Basic](connectiot_configuration_basic.md) and [Intermediate](connectiot_configuration_intermediate.md)) tutorials and start your Automation Manager, making sure you have the correct address configured in the configuration entry for your resource, which in this case should follow along the lines of `/Custom/ConnectIoT/OPC-UA/Oven/Address`. Go to the DEE that we have created (`GetCurrentTemperature`) and select the Execute button. You can now perform executions of the DEE. Let's add an input `Resource` with Value `Oven` (or whatever Resource you have linked to the controller instance). Select `Execute`. ![Execute DEE - Get Temperature](../../../images/connectiot_adv_exec_dee_getcurrtemp.png) ![Execute DEE - Get Temperature - Result](../../../images/connectiot_adv_exec_dee_getcurrtemp_result.png) Notice how we can easily test our implementation with DEEs. Now let's create a GUI. ### Create a GUI - Retrieve and Show Temperature MES supports an out of the box approach to create UI pages directly, in the system, without the need to code. 1. Go to `Administration > UIPages` 2. Select `New` 1. Give the Name `Collect Temperatures` 2. Press `Create` 3. Select `Edit` 4. Drag and drop the following Widgets: - `Form`: to be able to select a Resource from the system - `Button`: to trigger the action of getting the temperature - Feel free to go to the settings and give it a friendly name like `Get Current Temperature` - `Text`: to trigger the action of getting the temperature - Feel free to go to the settings and give it a friendly name like `Current Temperature` 5. In the `Form` in `Settings > Fields` 1. Add a field `Resource` 1. Type - `ReferenceType` 2. Reference type - `Entity` 3. Reference type name - `Resource` 6. In the `Settings` 1. In `Properties`, we will define the static variables global variables that we will need 1. Add a new property for the GUI to know the DEE to execute 1. Name - `DeeName` 2. Source - `Static` 3. Type - `String` 4. Value - `GetCurrentTemperature` 2. Add a new property for the GUI to know the input of the DEE 1. Name - `Resource` 2. Source - `Static` 3. Type - `String` 4. Value - `Resource` 3. Add a new property for the GUI to know the value of the input of the DEE 1. Name - `[input]Resource` 2. Source - `Static` 3. Type - `String` 4. Value - `Oven` (this is just a default value) 4. Add a new property for the GUI to know the value of the output of the DEE with the temperature 1. Name - `Temperature` 2. Source - `Static` 3. Type - `String` 4. Value - `Value` (this matches the input we added to the DEE `GetCurrentTemperature`) ![UIPage - Properties](../../../images/connectiot_adv_uipg_prop.png) 2. In `Data Sources`, we will define the services we need to call to execute the DEE 1. Add a new datasource for the GUI to Load the DEE to execute 1. Name - `LoadDEE` 2. Type - `ServiceCall` 3. Retrieve data on start - `false` 4. Retrieve data on changes - `false` 5. Select `Settings` 1. Choose `DynamicExecutionEngine` 1. Select `GetActionByName` 2. Add a new datasource for the GUI to execute the DEE 1. Name - `Request Temperature` 2. Type - `ServiceCall` 3. Retrieve data on start - `false` 4. Retrieve data on changes - `false` 5. Show error feedback messages - `true` 6. Select `Settings` 1. Choose `DynamicExecutionEngine` 1. Select `ExecuteAction` ![UIPage - DataSource](../../../images/connectiot_adv_uipg_datasrc.png) 3. Press `Save and Close` 7. In the right pane, select `Links` 1. Drag and drop the `Form` widget 2. Link the `Form` output `field$ResourceChange` to the `Page` input `[input]Resource` 1. Add the converter `entityName`. This will retrieve the Resource name. 3. Drag and drop the `LoadDee` 4. Link the output of `Page` `DeeName` to the input of `LoadDee` `Name` 5. Drag and drop the button `GetCurrentTemperature` 6. Link the button `GetCurrentTemperature` output `OnButtonClick` to the input `refresh` of the `LoadDee`. This means that every time you press the button it will refresh this widget 7. Drag and drop the widget `Request Temperature` 8. Connect the output of the `Page` `[input]Resource` to the Input `Input` of the `Request Temperature` 1. Apply the converter `setMapValue` with converter parameter `Resource`. This will associate the value of the property resource, which in this case is the constant string `Resource` with the value of the `[input]Resource`. Creating a Map of key `Resource` and value, the value that was fed to the `[input]Resource`, in this case the actual resource name. 9. Link the output `output$Action` of the `LoadDee` to 1. `Action` input of the `Request Temperature` 2. `refresh` input of the `Request Temperature` 10. Drag and drop the widget `Text` 11. Link the `output$Output` of the `Request Temperature` to the input `text` of the `Text` widget 12. Apply the converter `anyToStringProperty` with converter parameter `Temperature`. This will look into the output map for a key of the value that is declared in the property `Temperature`, in this case is `Value`. ![UIPage - LINKS](../../../images/connectiot_adv_uipg_links.png) 8. Press `Save` The page is now fully usable. Provide a resource to the form that does not have a controller instance you should see an exception popup as a banner. If we select the `Oven` resource and press the button `Get Current Temperature` we will see now the temperature is show in the text box. ![UIPage - Execution](../../../images/connectiot_adv_uipg_exec.png) !!! note If the DEE has multiple Inputs, the methodology is the same but with multiple links to the input `Input`. ## Set Setpoint - On Resource Begin Setup The use case can be further built upon if we consider that this value was set externally (e.g. through a recipe). Right now the focus is just to show the interaction between the system and Connect IoT. The goal is to have a Setup for a Resource in the MES that can only be complete if the Setpoint is set to 200ºC. We will create a DEE with the value 200ºC statically defined that will send a message to IoT and await confirmation in the Resource Complete Setup action. This value could be changed to come from a recipe, a definition in a table, a configuration or some other definition in the system in order to have the implementation become dynamic. ### Create a DEE - SetSetpointTo200 In order to be able to request an action to be performed appended to a system interaction we will need to create a hook on the action of the Begin Setup. The way to implement this in the MES system is through the use of DEEs that can be appended in the system actions either in `Pre` (before execution), or `Post` (after execution). These actions are within the transaction of the action performed, so if they give an error the whole transaction will rollback and maintain consistency in the system. 1. Go to `Administration > DEE Actions` 2. Select `New` 3. Give as Name `SetSetpointTo200`, for now the classification and action group is not important The DEE code is split between two important sections. The condition phase and the execution phase. Only if the condition phase returns with true, will the execution phase be invoked. In the history of an action in the MES this will also be explicit, if a DEE is appended. For more information on DEEs, see [DEE Actions](../../../../userguide/administration/dee_actions.md). The context of the DEE, that is present on the Inputs dictionary, will depend on where the DEE is hooked. Let's add the Action Group to our DEE. 1. Go to the `Details` tab 2. Press `Add` on the Action Group 3. Search for `ResourceManagement.ResourceManagementOrchestration.BeginSetup.Post` 4. Check it and press `Add` If in step 3. the action group is not present: 1. Go to `Administration > DEE Actions` 2. Select the Settings (three vertical dots) next to the `Action Groups` 3. Select `Add new Action Group` 4. Give as `Name` - `ResourceManagement.ResourceManagementOrchestration.BeginSetup.Post` 5. Select `Create` In order to find the action group where we want to append our DEE, you can consult the [API documentation](https://developer.criticalmanufacturing.com/api), or analyze the history whenever the action that you are interested is executed and it will be apparent what are multiple sub-actions that you can append your business logic. Note also that in the DEE in the code view, in the right pane it `Input Parameters`, it will now show all available parameters. Starting on the code for the `Test Condition Code`. We want to validate that the Inputs that we are interested are correct, to validate we should process and then pass that value to our DEE context. ```csharp /// /// Summary text: Send Setpoint to IoT on Begin Setup /// Actions groups: /// * ResourceManagement.ResourceManagementOrchestration.BeginSetup.Post /// Depends On: /// Is Dependency For: /// Exceptions: /// var serviceProvider = (IServiceProvider)Input["ServiceProvider"]; // Validate input if (Input["BeginSetupInput"] is not BeginSetupInput beginSetupInput) { throw new ArgumentNullCmfException("BeginSetupInput"); } return true; ``` ```csharp // Cmf UseReference("Cmf.Foundation.Common.dll", "Cmf.Foundation.Common.LocalizationService"); UseReference("Cmf.Navigo.BusinessOrchestration.dll", "Cmf.Navigo.BusinessOrchestration.ResourceManagement.InputObjects"); UseReference("Cmf.Common.CustomActionUtilities.dll", "Cmf.Common.CustomActionUtilities"); UseReference("Cmf.Common.CustomActionUtilities.dll", "Cmf.Common.CustomActionUtilities.Abstractions"); // Other Dependencies UseReference("Newtonsoft.Json.dll", "Newtonsoft.Json"); var serviceProvider = (IServiceProvider)Input["ServiceProvider"]; var localizationService = serviceProvider.GetService(); var utilitiesDeeContext = serviceProvider.GetService(); // Collect inputs var resource = (Input["BeginSetupInput"] as BeginSetupInput).Resource as IResource; var instance = resource.GetAutomationControllerInstance(); if (instance == null) { throw new Exception("Resource not connected to any IoT instance"); } else { string commandMessage = JsonConvert.SerializeObject( ( new Dictionary { { "DEE", "SetSetpoint200" }, { "Action", "SetSetpoint"}, { "Value", 200} } ), Newtonsoft.Json.Formatting.Indented); dynamic payload = instance.SendRequest("Cmf.Perform.Action", commandMessage, 10000); if(payload == null) { throw new Exception("Nothing received" ); } else if(!bool.Parse(payload["reply"].ToString())) { throw new Exception("Setpoint is not 200ºC" ); } } ``` Let's take a look at the code execution. The goal is to send a message to Connect IoT, based on the Resource. In order to perform communication to Connect IoT we will use the message bus through a `Send Request`. The Message Bus supports a "fire and forget" method using `Publish` as well as methods that wait for a success acknowledgement which is the case of `Send Request`. The execution will send the message to the Controller Instance linked to the resource and will wait for the acknowledge of a reply. It will only succeed if it receives an expected `true` value. ### Set Setpoint - Automation Controller On the Begin Setup we are now broadcasting a message to the Connect IoT layer. Let's now implement the logic of performing equipment integration actions with that information. In IoT we will receive the message and set the setpoint. 1. Go to `Oven Controller` 2. In the Workflow, create a new page `Manage Setpoint` 3. Drag and drop the following tasks: - [[user-guide-automation-task-core-systemevent]]: to subscribe to the message bus topic `Cmf.Perform.Action` - [Set Equipment Properties Values](../../../../userguide/automation/reference/tasks/core/equipment/actions/task_setequipmentproperties.md): to set the value of temperature setpoint 4. Go to the `On System Event` settings and for the Action Group, write `Cmf.Perform.Action`. 5. In the `Set Equipment Properties Values` 1. Add as Input, the Automation Property `TemperatureSetPoint` 6. Link the `On System Event` output `data` to the `Set Equipment Properties Values` 1. Activate 2. The `TemperatureSetPoint` 1. Apply the converter `Get Object Property` 1. Path `Value` 2. Type `Decimal` 7. Link the `Set Equipment Properties Values` `Success` to the Reply of the `On System Event` task 1. Apply the converter `AnyToAny` ![Controller - Manage Setpoint](../../../images/connectiot_adv_set_setpoint.png) ### Test with the Automation In the Resource `Oven`, select `Begin Setup` and press `Begin`. ![Resource Begin Setup](../../../images/connectiot_adv_set_setpoint_oven_begin.png) ![Resource Begin Setup IoT](../../../images/connectiot_adv_set_setpoint_oven_begin_iot.png) Notice that in UA Expert, the tag for the temperature setpoint is now 200ºC as expected. ![Resource Begin Setup UA Expert](../../../images/connectiot_adv_beginsetup_ua_expert.png) ## Validate Setpoint - On Resource Complete Setup In the **Begin Setup**, we've set the setpoint, but we only want the **Complete Setup** to be possible if the temperature matches the setup. We will then use the same DEE to validate the temperature on the Complete Setup. ### Change a DEE - SetSetpointTo200 Let's add the Action Group to our DEE. 1. Go to the `Details` tab 2. Press `Add` on the Action Group 3. Search for `ResourceManagement.ResourceManagementOrchestration.CompleteSetup.Post` 4. Check it and press `Add` If in step 3. the action group is not present: 1. Go to `Administration > DEE Actions` 2. Select the Settings (three vertical dots) next to the `Action Groups` 3. Select `Add new Action Group` 4. Give as `Name` - `ResourceManagement.ResourceManagementOrchestration.CompleteSetup.Post` 5. Select `Create` Perform the steps described in add the Action Group to the DEE. ```csharp /// /// Summary text: Send Setpoint to IoT on Begin Setup /// Actions groups: /// * ResourceManagement.ResourceManagementOrchestration.BeginSetup.Post /// Depends On: /// Is Dependency For: /// Exceptions: /// var serviceProvider = (IServiceProvider)Input["ServiceProvider"]; // Validate input var hasBeginSetup = Input.ContainsKey("BeginSetupInput") && Input["BeginSetupInput"] is BeginSetupInput; var hasCompleteSetup = Input.ContainsKey("CompleteSetupInput") && Input["CompleteSetupInput"] is CompleteSetupInput; if (!hasBeginSetup && !hasCompleteSetup) { throw new ArgumentNullCmfException("SetupInput"); } return true; ``` ```csharp // Cmf UseReference("Cmf.Foundation.Common.dll", "Cmf.Foundation.Common.LocalizationService"); UseReference("Cmf.Navigo.BusinessOrchestration.dll", "Cmf.Navigo.BusinessOrchestration.ResourceManagement.InputObjects"); // Other Dependencies UseReference("Newtonsoft.Json.dll", "Newtonsoft.Json"); var serviceProvider = (IServiceProvider)Input["ServiceProvider"]; var localizationService = serviceProvider.GetService(); // Collect inputs var hasBeginSetup = Input.ContainsKey("BeginSetupInput") && Input["BeginSetupInput"] is BeginSetupInput; var resource = hasBeginSetup ? (Input["BeginSetupInput"] as BeginSetupInput).Resource:(Input["CompleteSetupInput"] as CompleteSetupInput).Resource; var action = hasBeginSetup ? "BeginSetup": "CompleteSetup"; var message = new Dictionary { { "DEE", "SetSetpoint200" }, { "Value", 200} }; switch(action) { case "BeginSetup": message.Add("Action", "SetSetpoint"); break; case "CompleteSetup": message.Add("Action", "ValidateSetpoint"); break; default: throw new Exception("Unknow Action"); } var instance = resource.GetAutomationControllerInstance(); if (instance == null) { throw new Exception("Resource not connected to any IoT instance"); } else { string commandMessage = JsonConvert.SerializeObject( ( message ), Newtonsoft.Json.Formatting.Indented); dynamic payload = instance.SendRequest("Cmf.Perform.Action", commandMessage, 10000); if(payload == null) { throw new Exception("Nothing received" ); } else if(!bool.Parse(payload["reply"].ToString())) { throw new Exception("Setpoint is not 200ºC"+payload["reply"].ToString() ); } } ``` Notice the big change in the execution. We kept most of our code exactly the same but added a new context: the action. This action will be used to provide context to the Connect IoT layer to know that in one situation it must set the setpoint and in another situation it will validate that the temperature is in the setpoint. ### Validate Setpoint - Automation Controller On the complete setup we are now broadcasting a message to the Connect IoT layer. Let's now validate the setpoint against the temperature. 1. Go to `Oven Controller` 1. In the Workflow, got to page `Manage Setpoint` 1. Drag and drop the following tasks: - [Switch](../../../../userguide/automation/reference/tasks/core/workflow/control_flow/task_switch.md): tto allow us to perform conditional Actions - [Get Equipment Properties Values](../../../../userguide/automation/reference/tasks/core/equipment/actions/task_getequipmentproperties.md): to set the value of temperature setpoint - [Expression Evaluator](../../../../userguide/automation/reference/tasks/core/workflow/parsers/task_expressionevaluator.md): to compare the setpoint against the temperature 1. In the `Switch` task add 1. Input `Action` of Type `String` 2. Add Outputs 1. SetSetpoint 1. Equals - SetSetpoint 2. Name - SetSetpoint 3. Type - Boolean 4. Value - `true` 2. ValidateSetpoint 1. Equals - ValidateSetpoint 2. Name - ValidateSetpoint 3. Type - Boolean 4. Value - `true` 1. Link the output `data` to the `Switch` input Action 1. Apply converter `GetObjectProperty` 1. Path - `Action` 2. Type - `String` 1. Link the output `SetSetpoint` to the `Activate` of the `Set Equipment Properties Values` task 1. Link the output `ValidateSetpoint` to the `Activate` of the `Get Equipment Properties Values` task 1. In the `Expression Evaluator` 1. Check the flag Clear inputs to `false` 2. Add Inputs 1. Setpoint 1. Name - `Setpoint` 2. Type - `Decimal` 3. Default Value - 0 2. Temperature 1. Name - `Temperature` 2. Type - `Decimal` 3. Default Value - 0 3. Add Outputs 1. IsValid 1. Name - `IsValid` 2. Type - `String` 3. Expression: ```javascript abs(Setpoint - Temperature) < 1 ``` Notice that we are not doing an exact match, this is because the temperature oscillates between the setpoint. So we are putting a threshold of accepted values. 1. Link the `$Temperature` output 1. To the input Temperature of the `Validate Setpoint` task 2. To the input Activate of the `Validate Setpoint` task 1. Link the output `data` to the `Expression Evaluator` input `Setpoint` 1. Apply converter `GetObjectProperty` 1. Path - `Value` 2. Type - `Decimal` 1. Link the `Get Equipment Properties Values` to the 1. Input `$Temperature` of the `Expression Evaluator` 2. Input `Activate` of the `Expression Evaluator` 1. Link the `Expression Evaluator` output `IsValid` to the input `reply` of the `On System Event` ![Controller - Manage Setpoint - Validate Setpoint](../../../images/connectiot_adv_mng_setpoint_val.png) ### Test with the Automation In the Resource `Oven`, if you have already set performed the Begin Setup operation on the Resource, perform the Complete Setup. If the temperature is now 1+-200ºC you should be successful, if not it will show an error message. ![Resource Complete Setup](../../../images/connectiot_adv_set_setpoint_oven_complete.png) ![Resource Complete Setup IoT](../../../images/connectiot_adv_set_setpoint_oven_complete_iot.png) ## Post Data - On TrackIn As we saw when we were collecting values, the temperature varies in a threshold of the setpoint. So we want to collect the temperature at the time of the TrackIn, to keep track of the temperature that the material will be exposed at the time of the TrackIn. One of the important things to consider in a DEE is that it will run every time the action is performed in the system. In the case that we are addressing we wish to be more specific and require that the action be performed only in particular steps. In order to achieve this, let´s add an attribute in our Step to flag it as a Step where we want the logic to execute. ### Create a Step Attribute 1. Go to `Administration > Entity Types` 2. Select the `Step` 3. In the Attributes, select `Manage` 4. Press the plus button to add 5. Create a new attribute 1. Name `NotifyEQOnTrackIn` 2. Scalar Type `Bit` 3. Update 6. Press the `Generate` button 7. Go to the `Oven Step` 8. The attribute `NotifyEQOnTrackIn` will now be visible under the list of Attributes 9. `Edit` the attribute and change it to be `true` ### Create a DEE - NotifyIoTOnEquipmentTrackIn 1. Go to `Administration > DEE Actions` 2. Select `New` 3. Give as Name `NotifyIoTOnEquipmentTrackIn`, for now the classification and action group is not important Let's add the Action Group to our DEE. 1. Go to the `Details` tab 2. Press `Add` on the Action Group 3. Search for `BusinessObjects.MaterialCollection.TrackIn.Pre` 4. Check it and press `Add` If in step 3. the action group is not present: 1. Go to `Administration > DEE Actions` 2. Select the Settings (three vertical dots) next to the `Action Groups` 3. Select `Add new Action Group` 4. Give as `Name` - `BusinessObjects.MaterialCollection.TrackIn.Pre` 5. Select `Create` Starting on the code for the `Test Condition Code`. We want to validate that the Inputs that we are interested are correct, then check the Step of the Material to validate whether we should process it and subsequently pass that value to our DEE context. ```csharp /// /// Summary text: Start production on material track in /// Actions groups: /// * BusinessObjects.MaterialCollection.TrackIn.Pre /// Depends On: /// Is Dependency For: /// Exceptions: /// // Retrieve from Dependency Injection Container var serviceProvider = (IServiceProvider)Input["ServiceProvider"]; bool isToExecute = false; // Validate Inputs if (Input["MaterialCollection"] is not IMaterialCollection materialCollection){ throw new ArgumentNullCmfException("MaterialCollection"); } if (Input["Resource"] is not IResource resource) { throw new ArgumentNullCmfException("Resource"); } resource.Load(); var step = materialCollection.FirstOrDefault().Step; step.LoadAttributes(new Collection() {"NotifyEQOnTrackIn"}); // Only notify IoT if it's a notifyIoTStep if (step.Attributes != null && step.Attributes.ContainsKey("NotifyEQOnTrackIn") && (bool)step.Attributes["NotifyEQOnTrackIn"]) { isToExecute = true; } return isToExecute; ``` In the execution we will iterate through the **Materials** and for each one we will send a message to the Controller Instance linked to the **Resource** of the **Material** and wait for success. Bear in mind that if the wait time is very long or if no response is sent back from Connect IoT, the GUI will be in a loading screen and then eventually timeout, consider always replying back and handling the exception. !!! note By using the `resource.GetAutomationControllerInstance();` and using then the instance to do a `instance.SendRequest`, you guarantee that only the controller instance associated to this Resource will be notified. If you want to broadcast every listener consider using the MessageBus native methods. ```csharp // Other Dependencies UseReference("Newtonsoft.Json.dll", "Newtonsoft.Json"); // Retrieve from Dependency Injection Container var serviceProvider = (IServiceProvider)Input["ServiceProvider"]; // Collect inputs var resource = Input["Resource"] as IResource; var materialsToStartProduction = Input["MaterialCollection"] as IMaterialCollection; // Iterate through materials and send a message to IoT foreach (var Material in materialsToStartProduction) { string commandMessage = JsonConvert.SerializeObject( ( new Dictionary { { "DEE", "NotifyIoTOnEquipmentTrackIn" }, { "Action", "PostToDataCollection"} } ), Newtonsoft.Json.Formatting.Indented); var instance = resource.GetAutomationControllerInstance(); if (instance == null) { throw new Exception("Resource not connected to any IoT instance"); } else { dynamic reply = instance.SendRequest("Cmf.Perform.Action", commandMessage, 10000); if(reply == null) { throw new Exception("Nothing received" ); } } } ``` In this example, let's choose a generic topic and a more complex payload. It is also correct to use the topic as the context for the action to be performed like `Cmf.Post.Temperature`. The goal was to demonstrate a more complex payload example. ### Automation Controller - Implement Post Data Now that we have a DEE that will notify Connect IoT whenever there is a TrackIn. We will need to create a listener in the Oven Automation Controller and to perform a Post to the DataCollection. 1. Go to `Oven Controller` 2. In the Workflow, create a new page `Post Data - OnTrackIn` 3. Drag and drop the following tasks: - [On System Event](../../../../userguide/automation/reference/tasks/core/core/task_systemevent.md): to subscribe to the message bus topic `Cmf.Perform.Action` - [Switch](../../../../userguide/automation/reference/tasks/core/workflow/control_flow/task_switch.md): to allow us to perform conditional Actions - [Get Equipment Properties Values](../../../../userguide/automation/reference/tasks/core/equipment/actions/task_getequipmentproperties.md): to retrieve the value of temperature - [Data Collection](../../../../userguide/automation/reference/tasks/mes/task_datacollection.md): to post data to a Data Collection 4. Go to the `On System Event` settings and for the Action Group, write `Cmf.Perform.Action`. 5. Go to the `Switch` settings 1. Add Input `Action` as String 2. Add Outputs 1. Equals to `PostToDataCollection` 2. Name `PostToDataCollection` 3. Type `Boolean` 4. Value `true` 6. Link the `On System Event` output `data` to the `Switch` input `Action` 1. Apply the converter `Get Object Property` 1. Path `Action` 2. Type `String 7. Link the `Switch` output `PostToDataCollection` to the `Activate` of the `Get Equipment Properties Values` 8. Go to the `Get Equipment Properties Values` settings and add in the outputs the Automation Property `Temperature` 9. Go to the `Data Collection` and configure like in the [Intermediate](connectiot_configuration_intermediate.md) Connect IoT Tutorial, but change the `Complex Perform Data Collection Mode` to `Perform To Material` 10. Link the `Get Equipment Properties Values` Temperature output 1. To the `Data Collection` Temperature input 2. To the `Data Collection` active input 11. Link the `Success` output of the `Data Collection` task to the Reply of the `On System Event` task 12. Link the `data` output of the `OnSystemEvent` task to the `material` of the `Data Collection` task 1. Apply the converter `Get Object Property` 1. Path `Material` 2. Type `String` 2. Apply the converter `CreateSystemEntity` 1. entityType `Material` 2. identifier: `Name` ![OnTrackIn - Post to a DataCollection](../../../images/connectiot_adv_postdata_trackin_iot.png) ### Create a Material and Dispatch and TrackIn Create a Material `Oven Material` with the Product `Product Oven` and dispatch it for the Resource `Oven`. Now track in the Material at the Resource `Oven`. ![OnTrackIn - Post to a DataCollection](../../../images/connectiot_adv_postdata_trackin_mes.png) Now, if we go to the collected data of the **Material** it will show the data collection `Temperature Oven` with the value `Temperate`. Currently, the OPC-UA server is sending a temperature value with a large resolution, however if you prefer a more readable value feel free to apply an `Expression Evaluator` task to round up the value. ![OnTrackIn - Post to a DataCollection - Manager Execution](../../../images/connectiot_adv_postdata_trackin_iot_exec.png) ![OnTrackIn - Post to a DataCollection - Collected Data](../../../images/connectiot_adv_postdata_trackin_mes_dc.png) ## Automatic TrackOut - After 1 minute The material was tracked in successfully and we can now say that is being processed. We want to have a limited amount of time during which the material can be exposed to the setpoint temperature and then perform a TrackOut directly from the automation. We can use the default services provided out-of-the-box by the system and custom services, but for this case let's do a similar approach to the GUI. Using a DEE also allows us the opportunity to do some further customization if we wish to. ### Create a DEE - PerformATrackOut Let's create a DEE to retrieve the Temperature from Connect IoT. 1. Go to `Administration > DEE Actions` 2. Select `New` 3. Give as Name `PerformATrackOut` 4. Give as Classification `ConnectIoT` ```csharp // Retrieve Service Provider var serviceProvider = (IServiceProvider)Input["ServiceProvider"]; IMaterial material = serviceProvider.GetService(); // Retrieve Material Name from the Inputs material.Name = Input["Material"] as string; material.Load(); material.TrackOut(); ``` In the DEE execution, which will receive a Material name, load the Material object and execute the TrackOut. 1. Go to `BusinessData > Rule` 2. Select `New` 3. Give as Name `PerformATrackOut` 4. Give as Scope `ConnectIoT` 5. DEE Action `PerformATrackOut` - if it does not appear in the search box, validate that the DEE Classification is correct ### Automation Controller - TrackOut Automatically After 1 min In this implementation we will use the same topic as the TrackIn, we will start a timer that will trigger the Rule `PerformATrackOut` after 1 minute. We will require the Material name, but only after a minute, to perform the TrackOut. Connect IoT executes the tasks asynchronously, depending on activation time. In cases where we know before hand that there is a big time frame difference and that inputs can be overriden while waiting for the termination of other tasks, it's strongly recommended to use either the `Synchronize` task or sub-workflows. The sub-workflows preserve the activation context when activated, so there is no problem to receive the Material and then wait for 1 minute, as the context for that sub-workflow will be preserved and further activations will be new instances of the sub-workflow. The sub-workflow has an execution timeout, so the execution may not exceed that timeout. 1. Go to `Oven Controller` 2. In the Workflow, create a new page `TrackOut Automatically - After 1 min` 3. Drag and drop the following tasks: - [On System Event](../../../../userguide/automation/reference/tasks/core/core/task_systemevent.md): to subscribe to the message bus topic `Cmf.Perform.Action` - [Switch](../../../../userguide/automation/reference/tasks/core/workflow/control_flow/task_switch.md): to allow us to perform conditional Actions - [Workflow](../../../../userguide/automation/reference/tasks/core/workflow/control_flow/task_workflow.md): to execute sub-workflows 4. Go to the `On System Event` settings and for the Action Group, write `Cmf.Perform.Action`. 5. Go to the `Switch` settings 1. Add Input `Action` as String 2. Add Outputs 1. Equals to `PostToDataCollection` 2. Name `PostToDataCollection` 3. Type `Boolean` 4. Value `true` 6. Link the `On System Event` output `data` to the `Switch` input `Action` 1. Apply the converter `Get Object Property` 1. Path `Action` 2. Type `String 7. In the Workflow, create a new page `Sub - TrackOut Automatically - After 1 min` 8. Drag and drop the following tasks: - `Start`: the start context of the sub-workflow - `End`: the end result of the sub-workflow - `Timer`: to allow us to perform conditional Actions - `Execute Action`: to execute sub-workflows 9. In the `Timer` task open the `Settings` 1. Set the `Auto activate` to `false` 2. Set the `interval` to `60000` 3. Set the `Working Mode` to `Number of Occurrences` to `1` 10. Link the output `Success` of the `Timer` to the `Activate` of the `Execute Action` 11. Open the `Execute Action` settings 1. Select the `Rule` - `PerformATrackOut` 2. In the `Inputs` tab 1. Add a new input 1. Name - `Material` 2. Type - `String` 3. Default Value - `N/A` - can be whatever you wish as it wil be overriden 12. Link the output `Material` of the task `Start` to the `Material` Input of the `Execute Action` 13. Link the output `Success` and `Error` to the matching inputs of the `End` task 14. Go to the page `TrackOut Automatically - After 1 min` ![Sub - TrackOut Automatically - After 1 min](../../../images/connectiot_adv_postdata_trackout_subwf.png) 15. In the `Workflow` task settings, select the `Automation Workflow` `Sub - TrackOut Automatically - After 1 min` 16. Link the output `data` of the `On System Event` to the input `Material` of the `Workflow` task ![TrackOut Automatically - After 1 min](../../../images/connectiot_adv_postdata_trackout_wf.png) Interestingly, look that we are not replying back, even though the message was a `SendRequest`, it's not a problem, because the `SendRequest` works as a success on the first reply, and that will be achieved by the workflow `Post Data - OnTrackIn`. In order to test, let´s `Abort` our Material, `Dispatch` and `TrackIn` and let´s see the differences. ![TrackOut Automatically - After 1 min - IoT](../../../images/connectiot_adv_postdata_trackout_iot_exec.png) We can still see the previous reply, but now we register a new handler for our TrackOut. Then, after 1 minute: ![TrackOut Automatically - After 1 min - IoT - Auto](../../../images/connectiot_adv_postdata_trackout_auto_iot_exec.png) ![TrackOut Automatically - After 1 min - IoT - MES](../../../images/connectiot_adv_postdata_trackout_mes_finished.png) You now have a built structure using Connect IoT to track the lifecycle of a Material lifecycle, the lifecycle of a Resource and to collect values. This is the end of the advanced configuration tutorial.