Skip to main content
Version: 0.1

Model a motor as a tag type

This guide models one piece of repeated equipment, a motor, as a tag type (UDT) so that every motor on the plant is one instance with two parameters instead of a hand-made set of tags. It is for the engineer who has a PLC connected (see Connect an S7-1500 over OPC UA) and a data block per motor with the same layout.

Prerequisites​

  • An OPC UA connection, named for example Line1_PLC. Member tags of the type refer to the connection by its name.
  • On the PLC, one data block per motor with the same fields; this guide assumes "DB_M01"."Run", "DB_M01"."Status" (a status word) and "DB_M01"."Current" (a real, in A).
  • The engineer role.

What the type looks like​

PartValue
ParametersOPCUA (the connection), ID (the data block name, for example DB_M01)
RunOPC UA member on ns=3;s="{ID}".Run
StatusOPC UA member on ns=3;s="{ID}".Status, the status word
Fault, RemoteDerived members: bit 0 and bit 1 of Status
CurrentOPC UA member on ns=3;s="{ID}".Current, unit A, with an alarm template
Running_hoursExpression member, an example of a value computed from another member

A parameter is written as {Name} wherever the type needs it: in the connection ({OPCUA}), in a node id ("{ID}".Run), in an alarm's name or setpoint. The gateway substitutes the instance's values when it materializes the member tags. See Tag types for the model.

Steps​

1. Create the type in the dashboard​

  1. Open Tags and types. Types live in the Types group of the same page, under the @types folder.
  2. Create a new type. In the Type section fill in Name (Motor) and, if you want, Folder (Equipment). Leave Extends empty; it is for a type that starts from another one.
  3. In Parameters press Add parameter twice: OPCUA and ID. The description under the section says how they are used: "Use them in members as {Name}, for example in a node ID."
  4. Add the members, one at a time, with the add member form (Name, and Referenced type only for a nested type):
    • Run: kind OPC UA, server {OPCUA}, node id ns=3;s="{ID}".Run.
    • Status: kind OPC UA, same server, node id ns=3;s="{ID}".Status.
    • Fault: kind derived, source ./Status, bit 0. The tree shows bit members with a bit circle.
    • Remote: kind derived, source ./Status, bit 1.
    • Current: kind OPC UA, node id ns=3;s="{ID}".Current, engineering unit A.
  5. Set Root member to Run, so an instance's row in the tag table shows whether the motor runs.
  6. On Current, add an alarm named {Description}: Overcurrent, condition greater, setpoint 12, deadband 0.5, on-delay 2000 ms, severity 700.
  7. Save the type.

2. Or create it over the API​

POST /api/v1/udt/types takes the whole type. The member fields are those of a tag (kind, producerModuleId, producerInstanceRef, binding, sourceTag, sourceBit, expression, engUnit, ...), plus memberPath and alarms:

curl -b cookies.txt -H "Origin: https://app.scadex.io" -H "Content-Type: application/json" \
https://app.scadex.io/api/v1/udt/types -d @motor.json
{
"path": "Equipment.Motor",
"name": "Motor",
"parentPath": "Equipment",
"documentation": "A motor with a run bit, a status word and a current.",
"rootMemberKey": "Run",
"parameters": [
{ "name": "OPCUA", "dataType": "OPC-UA Server", "defaultValue": "Line1_PLC" },
{ "name": "ID", "dataType": "String", "defaultValue": "" }
],
"members": [
{ "memberPath": "Run", "kind": "opcua", "producerModuleId": "io.scadex.driver.opcua",
"producerInstanceRef": "{OPCUA}", "binding": { "nodeId": "ns=3;s=\"{ID}\".Run" } },
{ "memberPath": "Status", "kind": "opcua", "producerModuleId": "io.scadex.driver.opcua",
"producerInstanceRef": "{OPCUA}", "binding": { "nodeId": "ns=3;s=\"{ID}\".Status" } },
{ "memberPath": "Fault", "kind": "derived", "sourceTag": "./Status", "sourceBit": 0 },
{ "memberPath": "Remote", "kind": "derived", "sourceTag": "./Status", "sourceBit": 1 },
{ "memberPath": "Current", "kind": "opcua", "producerModuleId": "io.scadex.driver.opcua",
"producerInstanceRef": "{OPCUA}", "binding": { "nodeId": "ns=3;s=\"{ID}\".Current" },
"engUnit": "A", "formatString": "0.0",
"alarms": [
{ "name": "{Description}: Overcurrent", "condition": "greater", "setpoint": 12,
"deadband": 0.5, "onDelayMs": 2000, "severity": 700, "enabled": true }
] },
{ "memberPath": "Running_hours", "kind": "expression", "expression": "return tag('./Run') ? 1 : 0;" }
]
}

Notes on the fields:

  • kind is one of opcua, mqtt, derived, expression, memory, or a nested type through typeRef.
  • sourceTag is a path relative to the member, so ./Status is the sibling. sourceBit is the bit to follow, 0 being the lowest.
  • alarms is a list of alarm definitions as on a tag (name, condition, setpoint, deadband, onDelayMs, severity, enabled). The name, setpoint and deadband may hold {Param} references; {Description} is the instance's description (its first line of notes when it has none, its name when it has neither). See Alarms.
  • The expression member is a placeholder for your own logic: an expression is a JavaScript function body, so it needs return, over tag(path) and tags[path]. See Expression tags.

The reply is the type with its id. GET /api/v1/udt/types lists the types.

3. Create instances​

  1. In the tag tree, on the folder where the motors belong, choose the action that creates a new instance. The New instance pane opens.
  2. Fill in Name (M01), Inside (the parent folder, for example Line1), Type (Choose a type: Equipment.Motor), and under Parameters set ID to DB_M01. "Anything left alone follows the type", so OPCUA keeps its default Line1_PLC.
  3. Press Create instance.

Over the API:

curl -b cookies.txt -H "Origin: https://app.scadex.io" -H "Content-Type: application/json" \
https://app.scadex.io/api/v1/udt/instances \
-d '{"path":"Line1.M01","typeId":"<type id>","parameters":{"ID":"DB_M01"}}'

Repeat for M02 with DB_M02, and so on. Give each instance a description, because the alarm name reads it: C01 Exhaust fan: Overcurrent.

4. See the member tags​

Expand Line1.M01 in the tag tree: Run, Status, Fault, Remote, Current and Running_hours are ordinary tags, owned by the instance, with live values and qualities. The Fault and Remote rows carry the bit circles. The instance's own row shows the value of Run, the root member.

GET /api/v1/udt/instances/<id> returns the instance with memberTags, and extraTags for any tag added to the instance beyond its type (see Tag types).

The alarm on Current exists once per instance and is marked inherited: it is edited on the type, or overridden per instance in the member's overrides, not on the tag.

5. A faceplate screen as a template​

A template is a screen with a parameter. Widgets inside it use the parameter in their tag paths, and a popup or an embedded template fills it in with one instance.

  1. Open Screens and create a screen MotorFaceplate.
  2. Give it a parameter, for example motor, with the type Equipment.Motor. An instance of a type that extends Motor is accepted too.
  3. Bind widgets to {motor}.Run, {motor}.Fault, {motor}.Current. {param} is filled in tag paths only, never in script source.
  4. From a plant screen, open the faceplate as a popup with motor set to Line1.M01.

Templates and popups describes the parameter editor and how a popup is opened; Widgets lists what each widget binds to.

6. Change the type​

Edit the type (add a member Speed, move Current to another node, change the alarm's setpoint) and save. Editing a type re-syncs every instance of it, and of every type that extends it: members the type defines are rebuilt, tags added to an instance by hand are kept. To re-sync one instance by hand: POST /api/v1/udt/instances/<id>/sync.

A type that extends Motor (Extends in the editor, parentTypePath on the API) gets the same members at the same paths and may change single fields of an inherited member with a sparse overrides patch, such as another node id: {"memberPath":"Run","overrides":{"binding":{"nodeId":"ns=4;s={ID}.Drive.Run"}}}.

Where to go next​