Things Module - Pallettizer Integration

In todays tutorial we will explore how the TLP Things module can be used to easily implement integration scenarios that require communication with level2 layer.

The key benefits of this module are:

  • Ease of use and configuration, things can be created from UI or via XML.
  • Thing attributes can be wired to OPC tags or via MQTT topics (will be covered in another post).
  • Possibility to act upon changes in Level2 layer using Thing Triggers.
  • Possibility to act upon external changes using Thing Functions.
  • Templates allow modularizing our things.
  • Automatic tracking of attribute changes for historical and data analysis.
  • Can be used as a standalone IoT solution or integrated with 3rd party systems via our REST API.
  • Fast and easy!

As usual, the best way to explain something is through a simplified real case, so get ready because today we will be communicating with a PALLETIZER! or PALLETISER? anyway… If you are in a hurry, you can see the end result of this tutorial where labels are printed as a result of shrink wrapping pallets.

Palletizer Scenario

In our scenario we will satisfy the following requirements that describe a simple, yet a complete integration between a Palletizer and TLP platform, to add more spice we will also print some labels using TLP Print module.

Table 1: User Requirements
REQ Description
001 Starting a palletizing order shall communicate relevant data such as (order number, batch, customer, packaging layout) to the palletizer controller. Starting an order must be allowed only if the palletizer status is READY
002 Palletizer status changes such as (READY, SETUP, WORK, PAUSE) shall be tracked by the system for historical and data analysis
003 When palettizer shrink wrappes a pallet (i.e. pallet is complete) a label shall be printed. Label layout might be different based on the customer
004 System shall manage multiple palletizers, installing new palletizers with similar characteristics to the facility shall be possible with minimal configurations, no additional developments shall be required

The sequence diagram below describes interactions between the Palletizer and TLP, the process begins when a user starts a production order. To better demonstrate how TLP can easily integrate with 3rd party systems, we will assume that the start order input comes from an external system through a REST request.

Finally, the following table describes the PLC tags exposed by the Palletizer, their data type, which system manages their value and a short description about their purpose.

Table 2: PLC Tags
TAG Name TYPE OWNER Description
Status Short PLC Palletizer satus, possible values are
  • 1 = Ready
  • 2 = Setup
  • 3 = Work
  • 4 = Pause
PalletSwrinkWrapped Boolean PLC Goes ON when the pallet is shrink wrapped thus a label should be printed by TLP
LabelPrinted Boolean TLP Goes ON once the label is printed thus the palletizer can start working on the next pallet
WeightKg Double PLC Actual weight of the pallet
ProdOrder String TLP Name of the production order, displayed on the Palletizer HMI
Batch String TLP Production order batch, displayed on the Palletizer HMI
Material String TLP Production order material, displayed on the Palletizer HMI
PalletLayout short TLP How items should be layed on the pallet

Modelling the Palletizer Thing in TLP

TLP provides the possibility to model physical world objects as Things and manage their state for us, a Thing is composed of three main components:

  • Attributes : for example the palletizer status is modeled as an attribute named Status in our thing, same for all other tags such as PalletShrinkWrapped, ProductionOrder, Batch ... etc. Thing attributes are not always wired to PLC tags, for example we will use an attribute named LabelPrinter to specify on which printer labels should be printed.
  • Triggers : Used to execute logic when the value of an attribute changes, for example when the PalletShrinkWrapped attribute value changes to true the trigger DownloadOrder will be executed to print a label.
  • Functions : Used to execute logic due to external factors, for example when an order is started we want to execute the DownloadOrder function.

If you recall, requirement 004 explicitly mentioned that new palletizers should be easily added to the system, for this reason we will use another feature of TLP known as TLP Templates. Simply put, we will define attributes, triggers, and functions in a template named Palletizer and let our palletizer things extend it.

To satisfy requirement 002 which is related to storing changes we flag attributes: Status, PalletShrinkWrapped, WeightKg, and ProductionOrder as auditable which means that changes are stored in a historical table known as TAVA (Thing Attribute Value Audit). Differently, non-auditable attributes keep track of the last value only.

To create the ThingTemplate and Thing entities you can either use the UI or define them in XML and import them, we generally suggest creating them in XML mainly for the following reasons:

  • You can place the XML files under a version control system, such as git to track changes over time.
  • You can easily export them through your DEV --> TEST --> PROD environments.
  • Its faster ( At least for software engineers 😊).

Here is the XML, it is so simple that we believe there is no need to explain it, you should easily find a one-to-one correlation between the XML and the previous image.

<?xml version="1.0" encoding="UTF-8"?>
<Things>

	<ThingTemplate Name="Palletizer">
		<Attributes>
			<Attribute Name="Status" TagName="Status" DataType="INTEGER" Auditable="true"/>
			<Attribute Name="PalletShrinkWrapped" TagName="PalletClosed" DataType="BOOLEAN" Auditable="true"/>
			<Attribute Name="WeightKg" TagName="WeightKg" DataType="DECIMAL" Auditable="true"/>
			<Attribute Name="ProductionOrder" TagName="ProdOrder.Name" DataType="STRING" Auditable="true"/>
			<Attribute Name="Customer" TagName="ProdOrder.Customer" DataType="STRING" />
			<Attribute Name="Material" TagName="ProdOrder.Material" DataType="STRING" />
			<Attribute Name="Batch" TagName="ProdOrder.Batch" DataType="STRING" />
			<Attribute Name="LabelPrinted" TagName="LabelPrinted" DataType="BOOLEAN"/>
			<Attribute Name="LabelPrinter" DataType="STRING"/>
		</Attributes>

		<Triggers>
			<Trigger Name="PrintLabel">
				<Attribute>PalletShrinkWrapped</Attribute>
				<Processor>it.acme.palletizer.PrintLabelTrigger</Processor>
				<Idx>10</Idx>
				<Enabled>true</Enabled>
			</Trigger>
		</Triggers>
		
		<Functions>
			<Function Name="DownloadOrder">
				<Processor>it.acme.palletizer.DownloadOrderFunction</Processor>
				<Enabled>true</Enabled>
			</Function>
		</Functions>
	</ThingTemplate>

	<Thing Name="Palletizer01">
		<ThingTemplateName>Palletizer</ThingTemplateName>
		<BaseTagName>Acme.Palletizer01.</BaseTagName>
	</Thing>

	<Thing Name="Palletizer02">
		<ThingTemplateName>Palletizer</ThingTemplateName>
		<BaseTagName>Acme.Palletizer02.</BaseTagName>
	</Thing>

</Things>

Tip

Observe how some attributes are wired to the corresponding PLC tags, for example the attribute ProductionOrder is wired to PLC tag ProdOrder.Name.

DownloadOrder Function

From the sequence diagram, when an order is started we should download its details to the palletizer controller. The requirement is satisfied by defining a Thing Function whose logic could be implemented either in java or groovy, moreover a function expects to receive a payload with any required data necessary to complete its action, in our case, since we don’t manage production orders directly in TLP but receive them from an external system, we expect a json string similar to the following:

{
  "ProductionOrder": "PO25-00001",
  "Customer": "Contoso",
  "Batch": "B25080001",
  "Material": "123654"
}

Tip

json is typically the best format to use for representing functions payloads.

The following code is an example of the logic of our function, at high level it initializes a ThingProxy to read current status of the palletizer and make sure its in status READY before sending order details as received from the function payload.

class DownloadOrderFunction implements ThingFunctionProcessor {

	static final int STATUS_READY = 1;

	@Override
	public void execute(FunctionInvocationCtx ctx) {

		var thing = ctx.getThing();
		var proxy = new ThingProxy(ctx.getThing()).init();
		var status = proxy.getAttributeValue("Status").asInteger(0);
		var data = WasabiJsonUtils.jsonToMap(ctx.getPayload());


		if (status != STATUS_READY) {
			log.error("Palletizer {} status {} does not allow downloading orders", thing.getName(), status);
			return;
		}

		// Download production order data
		proxy.setAttributeValue("ProductionOrder", data.get("ProductionOrder"));
		proxy.setAttributeValue("Customer", data.get("Customer"));
		proxy.setAttributeValue("Batch", data.get("Batch"));
		proxy.setAttributeValue("Material", data.get("Material"));

		// Send data to controller
		proxy.flush();
		
	}
}

Testing the DownloadOrder Function

We reached a good point, its time to verify that our Thing is properly configured and that we can download orders to the palletizer, in this short video we will:

  1. Use the curl command to simulate receiving a start order request, the first time it fails because the palletizer Status is 0 which is not READY.
  2. Change the Status of the palletizer to READY and send the start order request again, and observe that the production order related tags are updated.
  3. As last step, we modify the request to download a different production order PO25-00002 and batch B25080002 and execute the start order again.

Note

We used the following curl command to execute the StartOrder function, the URL contains the thing and function names
curl -X POST http://server/api/core/tlp-things/Palletizer01/functions/DownloadOrder/execute -u username:password –json @c:/temp/start-order.json

The following video demonstrats this process:

Printing the label

To print the pallet label we will first design two new labels similar to the following ones and import them to TLP, the report will be named TLP-L-02 while the report instances will be:

  • TLP-L-02__75x25__v1 for pallets produced by Contoso, this version contains the customer name instead of the production order.
  • TLP-L-02__75x25__v2 for all other customers, this version contains the production order instead of the customer.

Now lets see the logic related to the Thing Trigger. The logic is quite simple, when the PalletShrinkWrapped becomes true it will:

  • Initialize a Thing Proxy to read current status of the palletizer.
  • Decide the label version to use based on the customer, we use version v1 if customer is contoso and v2 for all other customres.
  • Fill the report parameters based on data retrieved from the palletizer using the proxy.
  • Generate and print the label on the printer defined in the LabelPrinter attribute of the thing.
  • Update the LabelPrinted attribute value to true.
public class PrintLabelTrigger implements ThingTriggerProcessor {

	@Override
	public void execute(TriggerInvocationCtx ctx) {

		var palletShrinkWrapped = ctx.getOpcValue().asBoolean(false);
		if (!palletShrinkWrapped) {
			return;
		}

		var thing = ctx.getThing();
		var proxy = new ThingProxy(thing).init();

		// Initialize some variables
		var labelPrinter = proxy.getAttributeValue("LabelPrinter").asText("");
		var customer = proxy.getAttributeValue("Customer").asText("");
		var reportInstance = customer.equalsIgnoreCase("contoso")
			? "TLP-L-02__75X25__v1"
			: "TLP-L-02__75X25__v2";

		// Fill report parameters from palletizer attributes
		var reportParams = new HashMap<String, Object>();
		reportParams.put("ProductionOrder", proxy.getAttributeValue("ProductionOrder").asText(""));
		reportParams.put("Batch", proxy.getAttributeValue("Batch").asText(""));
		reportParams.put("Material", proxy.getAttributeValue("Material").asText(""));
		reportParams.put("WeightKg", proxy.getAttributeValue("WeightKg").asText(""));
		reportParams.put("Customer", customer);
		reportParams.put("PrintedBy", thing.getName() + " on ");

		// Print the report
		var printLabelCTX = new GenerateAndPrintReportCtx();
		printLabelCTX.setPrinter(printerCahce.getByIdentifier(labelPrinter));
		printLabelCTX.setReportInstance(reportInstanceCache.getByIdentifier(reportInstance));
		printLabelCTX.addReportParameters(reportParams);

		var cmd = reportService.generateAndPrint(printLabelCTX);
		cmd.publishPrintJobIfRequired();

		// Notify palletizer that label was printed
		proxy.setAttributeValue("LabelPrinted", true);
		proxy.flush();
		
	}
}

The following is a video to demonstrate the live behavior of the solution, as you can see as soon as the PalletShrinkWrapped is set to true a label is automatically printed, moreover we are printing different labels based on the customer.

Auditable attributes

Another powerful feature of TLP Thing module is the ability to track attribute changes, any attribute flagged as auditable will be automatically stored in TAVA table, the following is a snapshot of the WeightKg attribute at the time the previous video was recorded.

The Timestamp is when the tag value changed in the controller, while the UpdatedOn is when the value got actually recorded in TLP, as you can observe it takes < 100 millis to process data changes, considering the fact that the scenario was run on a normal PC we can conclude that updates are processed very quickly.

Summary

What can I say? well we beilive that both modules of TLP the Print Module and Things Module are awesome! you can use them to build complex integration scenarios with level2 easily. Following our open philosophy in TLP the module can be integrated with 3rd party systems via our REST API.

Stay tuned for the next module, we got much more.

— Omar